YUKIPEDIA's blog

一个普通的XMUER

《Summer Pockets》久島鴎推し


Springboot框架学习笔记

目录

SpringBoot概念与功能

概念

  • SpringBoot提供一种快速使用Spring的方式
  • 基于约定优于配置的思想
  • 不必在配置与逻辑业务之间进行思维切换,全身心投入到逻辑业务的代码编写

功能

  • 自动配置:SpringBoot的自动配置是一个运行时(准确来说是程序启动时)的过程,这些过程均由SpringBoot自动完成
  • 起步依赖:将具备某种功能的坐标打包到一起,并提供一些默认的功能

  • 辅助功能:提供一些大型项目中的非功能性特性,如嵌入式服务器、安全、指标等

SpringBoot配置

配置文件分类

SpringBoot是基于约定的,很多配置都有默认值,如果想使用自己的配置替换默认配置的话,可以使用application.properties或者application.yml(application.yaml)进行配置

  • properties
server.port=8080
  • yml
server
 port: 8080
  • SpringBoot提供了2种配置文件类型:properties和yml/yaml
  • 默认配置文件名称:application
  • 在同一级目录下优先级为:properties > yml > yaml

yaml文件

yaml全称是YAML Ain’t Markup Language。yaml是一种直观的能被电脑识别的数据序列化格式,并容易理解阅读。

yaml基本语法

  • 大小写敏感
  • 数据值前必须有空格,作为分隔符
  • 使用缩进表示层级关系
  • 缩进时不允许使用Tab键,只允许使用空格(不同操作系统Tab对应的空格数可能不同,导致层级混乱)
  • 所进的空格数目不重要,只要相同层级的元素左侧对齐即可
  • #表示注释,从这个字符一直到行尾,都会被解析器忽略
server
	port: 8080
	address: 127.0.0.1
	
name: abc

yaml数据格式

  • 对象(map):键值对的集合
person:
	name: zhangsan
# 行内写法
person: {name: zhangsan}
  • 数组:一组按次序排列的值
address:
	- beijing
	- shanghai
# 行内写法
address: [beijing,shanghai]
  • 纯量:单个的、不可再分的值(可理解为常量)
msg1: 'hello \n world' # 单引号忽略转义字符
msg2: "hello \n world" # 双引号识别转义字符
  • 参数引用
name: lisi

person:
	name: ${name} # 引用上面定义的name的值

读取配置文件内容

  • @Value
@Value("${name}")
private String name;

@Value("${person.name}")
private String name2;
  • Environment
@Autowired
private Environment env;

System.out.println(env.getProperty("person.name"));
System.out.println(env.getProperty("address[0]"));
  • @ConfigurationProperties

创建对象与yml配置文件的内容进行绑定

@Component
@ConfigurationProperties(prefix = "person")
public class Person {

    private String name;
    private int age;
    private String[] address;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public String[] getAddress() {
        return address;
    }

    public void setAddress(String[] address) {
        this.address = address;
    }

    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}
person:
  name: zhangsan
  age: 20
  address:
    - beijing
    - shanghai

profile

在开发SpringBoot应用时,通常同一套程序会被安装到不同环境,比如开发、测试、生产等。不同环境下的配置不同,若每次打包时都要修改配置文件会非常麻烦。profile功能就是来进行动态配置切换的

  • profile配置方式

    • 多profile文件方式:提供多个配置文件,每个代表一个环境

      • application-dev.properties/yml 开发环境
      • application-test.properties/yml 测试环境
      • application-pro.properties/yml 生产环境
    • yml多文档方式

      • 在yml中使用 — 分隔不同配置
      ---
      server:
        port: 8081
          
      spring:
        profiles: dev
      ---
      server:
        port: 8082
          
      spring:
        profiles: test
      ---
      server:
        port: 8083
          
      spring:
        profiles: pro
      ---
      spring:
        profiles:
          active: dev
      
  • profile激活方式

    • 配置文件:在配置文件中配置
    spring.profiles.active=dev
    
    • 虚拟机参数:在VM options指定
    -Dspring.profiles.active=dev
    
    • 命令行参数
    java -jar xxx.jar --spring.profiles.active=dev
    

内部配置加载顺序

SpringBoot程序启动时,会从以下位置加载配置文件

  • file:./config/:当前项目的/config目录下
  • file:./:当前项目的根目录
  • classpath:/config/:classpath的/config目录
  • classpath:/:classpath的根目录

加载顺序为上文的排列顺序,高优先级配置的属性会生效

img

外部配置加载顺序

参考Springboot加载外部配置文件的方法_new defaultresourceloader().getresource-CSDN博客

SpringBoot原理分析

SpringBoot自动配置

Condition

通过Conditon这个功能,可以实现选择性的创建Bean操作

  • 自定义条件
    • 定义条件类:自定义类实现Condition接口,重写matches方法,在matches方法中进行逻辑判断,返回boolean值。以下为matches方法的两个参数
      • context:上下文对象,可以获取属性值,获取类加载器,获取BeanFactory等。具体来说,通过context对象,可以获取环境、IoC容器、ClassLoader对象等等
      • metadata:元数据对象,用于获取注解属性
      //获取注解属性值value
      Map<String, Object> map = 			 metadata.getAnnotationAttributes(ConditionOnClass.class.getName());
      
    • 判断条件:在初始化Bean时,使用@Conditional(条件类.class)注解
  • SpringBoot提供的常用条件注解
    • ConditionalOnProperty:判断配置文件中是否有对应属性和值才初始化Bean
    • ConditionalOnClass:判断环境中是否有对应字节码文件才初始化Bean
    • ConditionalOnMissingBean:判断环境中没有对应Bean才初始化Bean

切换内置web服务器

SpringBoot的web环境中默认使用tomcat作为内置服务器,其实SpringBoot提供了4种内置服务器供选择,我们可以修改maven工程中的pom配置文件进行很方便的切换

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
    <!--排除tomcat依赖-->
    <exclusions>
        <exclusion>
            <artifactId>spring-boot-starter-tomcat</artifactId>
            <groupId>org.springframework.boot</groupId>
        </exclusion>
    </exclusions>
</dependency>

    <!--引入jetty的依赖-->
<dependency>
    <artifactId>spring-boot-starter-jetty</artifactId>
    <groupId>org.springframework.boot</groupId>
</dependency>

@Enable*注解

SpringBoot提供了很多Enable开头的注解,这些注解都是用于动态启用某些功能的。其底层原理是使用@Import注解导入一些配置类,实现动态加载

在一个工程中想使用其他工程定义的bean时,可以采用以下几种方法

  • 使用@ComponentScan扫描bean对应的包
@ComponentScan("com.itheima.config")
  • 使用@Import注解,加载类。这些类都会被Spring创建并放入IoC容器
@Import(UserConfig.class)
  • 可以对Import注解进行封装

自定义封装的EnableUser注解类

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(UserConfig.class)
public @interface EnableUser {
}

调用@EnableUser注解

@EnableUser
public class SpringbootEnableApplication {

    public static void main(String[] args) {
        ConfigurableApplicationContext context = SpringApplication.run(SpringbootEnableApplication.class, args);

        Object user = context.getBean("user");
        System.out.println(user);

    }
}

@Import注解

@Enable*底层依赖于@Import注解导入一些类,使用@Import导入的类会被Spring加载到IoC容器中。而@Import提供4种用法

  • 导入Bean

下面的代码直接导入Userbean

@Import(User.class)
public class SpringbootEnableApplication {

    public static void main(String[] args) {
        ConfigurableApplicationContext context = SpringApplication.run(SpringbootEnableApplication.class, args);

        // 按类型获取,不能按名称获取,若使用按名称获取可能会找不到
        User user = context.getBean(User.class);
        System.out.println(user);

    }
}
  • 导入配置类
@Import(UserConfig.class)
public class SpringbootEnableApplication {

    public static void main(String[] args) {
        ConfigurableApplicationContext context = SpringApplication.run(SpringbootEnableApplication.class, args);

		// UserConfig配置类中有两个bean:User和Role,均能被获取到
        User user = context.getBean(User.class);
        System.out.println(user);

        Role role = context.getBean(Role.class);
        System.out.println(role);
    }
}

对于这种导入方法,配置类UserConfig上面的注解@Configuration是可以省略的

//@Configuration
public class UserConfig {

    @Bean
    public User user() {
        return new User();
    }

    @Bean
    public Role role() {
        return new Role();
    }
}
  • 导入ImportSelector实现类,一般用于加载配置文件中的类

实现类如下

public class MyImportSelector implements ImportSelector {
    @Override
    public String[] selectImports(AnnotationMetadata importingClassMetadata) {
        // 复写接口方法,导入两个bean:User和Role
        return new String[]{"com.itheima.domain.User", "com.itheima.domain.Role"};
    }
}

再导入实现类即可

@Import(MyImportSelector.class)
public class SpringbootEnableApplication {

    public static void main(String[] args) {
        ConfigurableApplicationContext context = SpringApplication.run(SpringbootEnableApplication.class, args);

		// MyImportSelector实现类中有两个bean:User和Role,均能被获取到
        User user = context.getBean(User.class);
        System.out.println(user);

        Role role = context.getBean(Role.class);
        System.out.println(role);
    }
}

这种方式和上一种方式类似,但这种方式在配置实现类中是以字符串实现的,所以可以用这个方式加载一些配置文件的bean

  • 导入ImportBeanDefinitionRegistrar实现类

创建ImportBeanDefinitionRegistrar的实现类

public class MyImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {
    @Override
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
        // 获取User类的定义
        AbstractBeanDefinition beanDefinition = BeanDefinitionBuilder.rootBeanDefinition(User.class).getBeanDefinition();
        // 注册以user为名称,User类的定义为定义的bean
        registry.registerBeanDefinition("user", beanDefinition);
    }
}

导入ImportBeanDefinitionRegistrar实现类

@Import({MyImportBeanDefinitionRegistrar.class})
public class SpringbootEnableApplication {

    public static void main(String[] args) {
        ConfigurableApplicationContext context = SpringApplication.run(SpringbootEnableApplication.class, args);

		// ImportBeanDefinitionRegistrar中只有User的bean,没有Role的bean
        // 所以输出role时会报错
        User user = context.getBean(User.class);
        System.out.println(user);

        Role role = context.getBean(Role.class);
        System.out.println(role);
    }
}

@EnableAutoConfiguration注解

  • @EnableAutoConfiguration注解内部使用@Import(AutoConfigurationImportSelector.class)来加载配置类
  • 配置文件位置:META-INF/spring.factories,该配置文件中定义了大量的配置类,当SpringBoot应用启动时,会自动加载这些配置类,初始化bean
  • 并不是所有的bean都会被初始化,在配置类中使用Condition来加载满足条件的bean

说明:spring.factories是SpringBoot中用于自动配置的一种机制,位于META-INF目录下。spring.factories文件采用了 Java properties 文件的格式,每一行表示一个自动配置类及其对应的条件

SpringBoot监听机制

SpringBoot的监听机制,其实是对java提供的事件监听机制的封装

java监听机制

  • java中的事件监听机制定义了以下几个角色

    • 事件:Event,继承java.util.EventObject类的对象

    • 事件源:Source,任意对象Object

    • 监听器:Listener,实现java.util.EventListener对象

SpringBoot监听机制

SpringBoot在项目启动时,会对几个监听器进行回调,我们可以实现这些监听器接口,在项目启动时完成一些操作

监听器接口:ApplicationContextInitializer、SpringApplicationRunListener、CommandLineRunner、ApplicationRunner

  • ApplicationContextInitializer
    • 这个接口允许在应用程序上下文被创建之前对其进行定制和配置
    • 可以用来做一些初始化的工作,例如添加自定义属性源、激活特定的Spring配置文件等
    • 通过实现这个接口,可以在Spring应用程序上下文被创建之前对其进行自定义设置
补充:在java中,"上下文对象"通常是指一种数据结构或对象,用于存储和传递在特定环境中的相关信息或状态。这个概念在不同的技术和框架中有不同的具体实现和用法。

在一些情况下,上下文对象可以是一个普通的 Java 对象,用于存储和传递方法调用或处理过程中的相关信息。例如,在多线程编程中,可以创建一个包含线程状态、线程局部变量等信息的上下文对象,以便在方法调用之间传递这些信息。

在一些框架和技术中,上下文对象可能具有更具体的含义和功能。例如:
1.Servlet 上下文对象:在 Java Web 开发中,Servlet 上下文对象是用于存储 Servlet 环境相关信息的对象。它可以通过 ServletContext 接口来访问,其中包含了 Servlet 容器的一些配置信息、Servlet 的初始化参数等。

2.Spring 应用程序上下文对象:在 Spring 框架中,应用程序上下文对象是 Spring IoC 容器中的一个重要组成部分,用于管理和维护 Bean 的定义、依赖关系等信息。它可以通过 ApplicationContext 接口来访问,其中包含了应用程序中所有 Bean 的定义、配置信息等。

3.数据库连接上下文对象:在使用 JDBC 进行数据库操作时,可以创建一个数据库连接上下文对象,用于管理数据库连接的状态、事务信息等。

总的来说,上下文对象是一种在特定环境中存储和传递相关信息的通用机制,在不同的技术和框架中具有不同的实现和用法。它可以帮助组织和管理程序执行过程中的状态和信息,提高代码的灵活性和可维护性。
  • SpringApplicationRunListener
    • 这个接口用于监听SpringBoot应用程序的启动过程中的事件,例如应用程序启动、运行失败等
    • 可以用来实现一些高级的应用程序启动时的逻辑,例如记录日志、发送通知等
    • 通过实现这个接口,可以在应用程序启动的各个阶段添加自定义行为
  • CommandLineRunner
    • 这是一个函数式接口,用于在SpringBoot应用程序启动后立即执行一些逻辑
    • 它提供了一个‘run’方法,该方法会在SpringBoot应用程序启动后立即执行,且可以访问应用程序启动时的命令行参数
    • 可以用来实现一些与命令行相关的初始化工作,例如加载数据、执行特定任务等
补充:函数式接口(Functional Interface)是指仅包含一个抽象方法的接口。Java 8 引入了函数式接口的概念,以支持函数式编程风格和Lambda表达式。函数式接口通常用于表示可以作为Lambda表达式传递的类型,或者作为方法引用的目标类型。

函数式接口具有以下特征:
1.只包含一个抽象方法:函数式接口只能包含一个未实现的抽象方法。它可以包含多个默认方法或静态方法,但只能有一个抽象方法。
2.可以使用@FunctionalInterface注解:虽然不是必须的,但通常将函数式接口标记为@FunctionalInterface注解,以便编译器检查它是否符合函数式接口的要求。如果一个接口标记为@FunctionalInterface,但它不符合函数式接口的要求(例如包含多个抽象方法),编译器将产生错误。
3.Lambda表达式和方法引用的目标类型:函数式接口可以被用作Lambda表达式的类型。Lambda表达式提供了一种简洁的语法来实现函数式接口的抽象方法。同样,函数式接口也可以是方法引用的目标类型。
  • ApplicationRunner
    • ‘CommandLineRunner’类似,也是用于在SpringBoot应用程序启动后立即执行一些逻辑的接口
    • 不同之处在于,‘ApplicationRunner’‘run’方法接受的参数是‘ApplicationArguments’,而不是简单的字符串数组
    • ‘ApplicationArguments’提供了更丰富的访问应用程序启动时的参数信息的能力,例如访问非标准选项、参数值的类型转换等

要使上面四种监听器接口中的ApplicationContextInitializer和SpringApplicationRunListener生效,需要在配置META-INF文件夹和其中的spring.factories

SpringBoot启动流程分析

参考12-SpringBoot流程分析-初始化_哔哩哔哩_bilibili的P29-P30

SpringBoot监控

SpringBoot自带监控功能Actuator,可以帮助实现对程序内部运行情况监控,比如监控状况、bean加载情况、配置属性、日志信息等

使用步骤

  • 导入依赖坐标
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
  • 访问

    http://localhost:8080/actuator
    

SpringBoot项目部署

SpringBoot项目开发完毕后,支持两种方式部署到服务器:

  • jar包
  • war包