Spring Boot 知识点

约定优于配置

Spring 和 SpringBoot

Spring 缺点:重配置
Spring Boot 优点

  • 起步依赖:将多个 maven 包的坐标整合到一起,并提供一些默认的功能
  • 自动配置:会自动将一些配置类的 bean 注册进 IoC 容器

单元测试

  • @SpringBootTest:标记该类为 SpringBoot 单元测试类,并加载 applicationContext 上下文环境
  • @RunWith (SpringRunner.class):测试启动器,并加载 SpringBoot 测试注解

热部署

依赖

1
2
3
4
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
</dependency>
  1. IDEA 开启自动编译
  2. Ctrl + Shift + Alt + /-> Registry -> compiler.automake.allow.when.app.running √:用于指定 IDEA 在程序运行过程中自动编译

配置

5.1 ConfigurationProperties

依赖包

1
2
3
4
5
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
  -  [@ConfigurationProperties(prefix ](/ConfigurationProperties(prefix ) = "person"):将配置文件中以 person 开头的属性注入到类中  
1
2
3
4
5
6
@Component
@ConfigurationProperties(prefix = "person")
public class Person {
private int id;
// set and get
}
  • @Value

    • 无需 set 方法
    • 不支持 Map、对象及行内写法
      1
      2
      3
      4
      5
      @Component
      public class Person {
      @Value("${person.id}")
      private int id;
      }
  • @PropertySource:加载配置文件

    1
    2
    3
    4
    5
    6
    7
    @Component
    @PropertySource("classpath:text.properties")
    @ConfigurationProperties(prefix = "person")
    public class Person {
    private int id;
    // set and get
    }
  • 随机值设置
    使用 Spring Boot 内嵌的 RandomValuePropertySource 类

    1
    2
    3
    4
    5
    6
    7
    8
    my.secret=${random.value}
    my.number=${random.int}
    my.bignumber=${random.long}
    my.uuid=${random.uuid}
    # 配置小于 10 的随机整数
    my.number.less.than.ten=${random.int(10)}
    # 配置范围在 [1024,65536] 之间的随机整数
    my.number.in.range=${random.int[1024,65536]}
  • 参数引用

    1
    2
    app.name=MyApp 
    app.description=${app.name} is a Spring Boot application

@SpringBootApplication 源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
// 表明该类为配置类
@SpringBootConfiguration
// 启用自动配置功能
@EnableAutoConfiguration
// 包扫描
@ComponentScan(
excludeFilters = {
@Filter(type = FilterType.CUSTOM, classes = {TypeExcludeFilter.class}),
@Filter( type = FilterType.CUSTOM, classes = {AutoConfigurationExcludeFilter.class})
})
public @interface SpringBootApplication {
...
}
  • @SpringBootConfiguration
    组合了 @Configuration,配置类

    1
    2
    3
    4
    5
    6
    7
    @Target({ElementType.TYPE}) 
    @Retention(RetentionPolicy.RUNTIME)
    // 配置 IoC 容器
    @Documented
    @Configuration
    public @interface SpringBootConfiguration {
    }
  • @EnableAutoConfiguration

    • @AutoConfigurationPackage:将 AutoConfigurationPackages.Registrar 类导入到 IoC 容器中。拿到启动类所在的包名
    • @Import (AutoConfigurationImportSelector.class) :将 AutoConfigurationImportSelector 类导入到 IoC 容器中。
      • AutoConfigurationMetadataLoader#loadMetadata:加载 spring-boot-autoconfigure.jar 依赖包中 spring-autoconfigure-metadata.properties 文件中的属性,其定义了各个自动配置类及其加载条件。最终生成 AutoConfigurationMetadata 对象。
      • AutoConfigurationImportSelector#getAutoConfigurationEntry:
        1. AutoConfigurationImportSelector#getAttributes:获得注解的属性
        2. AutoConfigurationImportSelector#getCandidateConfigurations: 获取默认支持的自动配置类名列表
          Spring Boot 在启动的时候,使用内部工具类 SpringFactoriesLoader 查找 classpath 上所有 jar 包中的 META-INF/spring.factories 文件。找到将 key 为 org.springframework.boot.autoconfigure.EnableAutoConfiguration 的键值对,其值为多个工厂类名称。取出生成 自动配置类名列表
        3. AutoConfigurationImportSelector#removeDuplicates:对 自动配置类名列表 去重
        4. AutoConfigurationImportSelector#getExclusions:得到需排除的自动配置类,后续操作对列表集合进行排除
        5. AutoConfigurationImportSelector#filter:根据配置类的 @ConditionalXXX 注解指定的条件对列表集合进行筛选,并对 AutoConfigurationMetadataLoader#loadMetadata 生成的 AutoConfigurationMetadata 对象进行筛选
        6. AutoConfigurationImportSelector#fireAutoConfigurationImportEvents:将自动配置导入事件通知监听器。
          过滤完成后会自动加载类路径下 Jar 包中 META-INF/spring.factories 文件中 AutoConfigurationImportListener 的实现类,并触发 fireAutoConfigurationImportEvents 事件。进行自动配置类的加载创建
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
// 自动配置包:会把 @SpringbootApplication 注解标注的类所在包名拿到,并且对该包及其子包进行扫描,将组件添加到容器中
@AutoConfigurationPackage
// 可以帮助springboot应用将所有符合条件的@Configuration配置都加载到当前SpringBoot创建并使用的IoC容器(ApplicationContext)中
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {

String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";

Class<?>[] exclude() default {};

String[] excludeName() default {};

}

自定义 starter

  • 导入依赖

    1
    2
    3
    4
    5
    6
    7
    <dependencies> 
    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-autoconfigure</artifactId>
    <version>2.2.2.RELEASE</version>
    </dependency>
    </dependencies>
  • SimpleBean

    1
    2
    3
    4
    5
    6
    7
    @EnableConfigurationProperties(SimpleBean.class) 
    @ConfigurationProperties(prefix = "simplebean")
    public class SimpleBean {
    private int id;
    private String name;
    // setter and getter
    }
  • MyAutoConfiguration
    @ConditionalOnClass:当类路径 classpath 下有指定的类的情况下才进行自动配置

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    @Configuration
    @ConditionalOnClass(SimpleBean.class)
    public class MyAutoConfiguration {
    static {
    System.out.println("MyAutoConfiguration init....");
    }
    @Bean public SimpleBean simpleBean(){
    return new SimpleBean();
    }
    }
  • resources 下创建 /META-INF/spring.factories

    1
    org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ com.ccomma.config.MyAutoConfiguration

SpringApplication#run 源码

  • 静态方法 SpringApplication#run
    • 创建 SpringApplication 实例:new SpringApplication (primarySources),其中 primarySources 为启动类的 Class 对象
      1. 存储项目启动类的 Class 对象至属性字段中
      2. 设置应用类型是 SERVLET 应用(Spring 5 之前的传统 MVC 应用)还是 REACTIVE 应用(Spring 5 开始出现的 WebFlux 交互式应用)
      3. SpringApplication#getSpringFactoriesInstances (ApplicationContextInitializer.class):在 spring.factories 中获取以 ApplicationContextInitializer 全类名为键的所有值(值为初始化器的全类名),并创建实例返回。即返回了 ApplicationContextInitializer 这一类的所有初始化器实例。然后进行设置
      4. SpringApplication#getSpringFactoriesInstances (ApplicationListener.class):同上,获取 ApplicationListener 这一键下的所有监听器并进行设置
      5. 初始化 mainApplicationClass 属性:用于推断并设置项目 main () 方法启动的主程序启动类
    • 调用 SpringApplication 实例的 run 方法
      1. 获取并启动监听器:调用 SpringApplication#getRunListeners 方法从 spring.factories 中获取键为 SpringApplicationRunListener.class 的所有值(全类名),放入 SpringApplicationRunListeners 对象中,并调用其 starting 方法启动监听器
      2. 项目运行环境 Environment 的预配置:调用 SpringApplication#prepareEnvironment 方法创建并配置当前 SpringBoot 应用将要使用的 Environment,并遍历调用所有的 SpringApplicationRunListener 的 environmentPrepared () 方法。最后调用 SpringApplication#configureIgnoreBeanInfo 配置环境
        1. SpringApplication#getOrCreateEnvironment:获取或创建环境
        2. SpringApplication#configureEnvironment:配置环境。配置 PropertySources 和 Active Profiles
        3. SpringApplicationRunListeners#environmentPrepared:Listeners 环境准备(广播 ApplicationEnvironmentPreparedEvent 事件)
        4. SpringApplication#bindToSpringApplication:将环境绑定到 SpringApplication 中
      3. 创建 Spring 容器:调用 SpringApplication#createApplicationContext 创建一个 AnnotationConfigServletWebServerApplicationContext 容器
      4. 获得异常报告器(SpringBootExceptionReporter 集合):调用 SpringApplication#getSpringFactoriesInstances 从 spring.factories 中获取键为 BootExceptionReporter.class 的所有类,即所有异常报告器
      5. Spring 容器前置处理:调用 SpringApplication#prepareContext 方法,在容器刷新之前的准备动作,比如触发监听器的响应事件、加载资源、设置上下文环境等。并将启动类注入容器为后续开启自动化配置奠定基础
        1. SpringApplication#load:将启动类注入容器,为后续开启自动化配置奠定基础
      6. 刷新容器:调用 SpringApplication#refreshContext
        1. SpringApplication#refresh:初始化 IoC 容器,包括 Bean 资源的定位、解析、注册等
        2. ConfigurableApplicationContext#registerShutdownHook:注册 JVM 关闭钩子。在 JVM 关机时关闭这个上下文
      7. Spring 容器后置处理:钩子,无实现可拓展
      8. 发出结束执行的事件通知:调用 SpringApplicationRunListeners#started 方法执行所有监听器的 SpringApplicationRunListener#started 方法
      9. SpringApplication#callRunners:获取并执行 ApplicationRunner 和 CommandLineRunner 的所有实现类。Runner 运行器用于在服务启动时进行一些业务初始化操作,这些操作只在服务启动后执行一次。Spring Boot 提供了 ApplicationRunner 和 CommandLineRunner 两种服务接口
      10. 发布应用上下文就绪事件:调用 SpringApplicationRunListeners#running 方法来持续运行配置好的应用上下文 ApplicationContext

缓存管理

  • 注解方式:未使用缓存管理器则默认使用 Simple 缓存组件进行缓存
    • @EnableCaching

      1
      2
      3
      4
      5
      6
      7
      8
      @EnableCaching
      @SpringBootApplication
      public class Springboot04CacheApplication {

      public static void main(String[] args) {
      SpringApplication.run(Springboot04CacheApplication.class, args);
      }
      }
    • @Cacheable:缓存数据,用于查询方法

1
2
3
4
5
6
7
8
@Cacheable(cacheNames = "comment") 
public Comment findById(int comment_id){
Optional<Comment> optional = commentRepository.findCommentById(comment_id);
if(optional.isPresent()){
return optional.get();
}
return null;
}
  -  @CachePut:更新数据库后更新缓存,用于更新方法 
  -  @CacheEvict:删除数据库数据后删除缓存,用于删除方法 
  • API 方式

    • RedisTemplate
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      @Service 
      public class ApiCommentService {
      @Autowired
      private CommentRepository commentRepository;

      @Autowired
      private RedisTemplate redisTemplate;

      public Comment findCommentById(Integer id) {
      Object o = redisTemplate.opsForValue().get("comment_" + id);
      if (o != null) {
      return (Comment) o;
      } else {
      // 缓存中没有,从数据库中查询
      Optional<Comment> byId = commentRepository.findById(id);
      if (byId.isPresent()) {
      Comment comment = byId.get();
      // 将查询结果存入到缓存中
      redisTemplate.opsForValue().set("comment_"+id,comment,1,TimeUnit.DAYS);
      return comment;
      } else {
      return null;
      }
      }
      }

      public Comment updateComment(Comment comment) {
      commentRepository.updateComment(comment.getAuthor(), comment.getaId());
      // 更新数据后进行缓存更新
      redisTemplate.opsForValue().set("comment_"+comment.getId(),comment);
      return comment;
      }

      public void deleteComment(int comment_id) {
      commentRepository.deleteById(comment_id);
      redisTemplate.delete("comment_"+comment_id);
      }
      }
  • 自定义 Redis 缓存序列化机制

    • RedisTemplate#afterPropertiesSet 方法中判断如果默认序列化参数 defaultSerializer 为空,则指定 JdkSerializationRedisSerializer 为默认序列化方式,所以进行缓存的实体类必须实现 Serializable 接口
    • RedisConfig
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      @Configuration 
      public class RedisConfig {

      @Bean
      public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
      RedisTemplate<Object, Object> template = new RedisTemplate();
      template.setConnectionFactory(redisConnectionFactory);
      // 使用 Json 格式序列化对象,对缓存数据 key 和 value 进行转换
      Jackson2JsonRedisSerializer jacksonSeial = new Jackson2JsonRedisSerializer(Object.class);

      // 解决查询缓存转换异常的问题
      ObjectMapper om = new ObjectMapper();
      om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
      om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
      jacksonSeial.setObjectMapper(om);
      // 设置 RedisTemplate 模版 API 的序列化方式为 Jsonz
      template.setDefaultSerializer(jacksonSeial); return template;
      }
      }