Spring Boot 之主启动类

主启动类

文章目录

    • 主启动类
      • @SpringBootApplication
        • 作用
        • @SpringBootConfiguration
        • @EnableAutoConfiguration
          • @AutoConfigurationPackage
          • @Import
            • 自动装配流程
            • 自动配置原理
        • @ComponentScan
      • run()
      • ApplicationContext
      • 启动后执行
        • @PostConstruct
        • 两个接口
        • ApplicationListener

@SpringBootApplication

作用

  • SpringBoot 项目中都有主启动类用来启动项目

    • 启动类上使用 @SpringBootApplication 注解
    • 标注这是 SpringBoot 应用
  • @SpringBootApplication

    • 注解在主类上,复合注解:由多个注解组成
      1. @SpringBootConfiguration
      2. @EnableAutoConfiguration
      3. @ComponentScan
    • 主类定义在主包中,和各模块同级
    • 可以自动创建主类所在包及子包的 bean 对象
  • 主类作用

    1. 判断应用类型:普通项目 或 Web 项目
    2. 查找并加载所有可用初始化器,设置到 initializers 属性
    3. 加载所有监听器程序,设置到 listeners 属性
    4. 判断并设置 main() 方法定义类,找到运行主类
  • Spring Boot 自动装配流程

    • Spring Boot 之主启动类_第1张图片
package li_maven;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

/**
 * 主类,启动项目;包括 Tomcat 服务器
 * @SpringBootApplication 注解中包含多个注解
 * 可以将本类当作配置文件使用。 使用 @Bean 注解声明对象并放入容器
 * 可以自动配置对象并放到容器:例如 MyBatis 需要的对象
 * 扫描注解创建对象:默认扫描范围 此注解所在类所在的包及子包
 */
@SpringBootApplication
public class SpringBootDemoApplication {

    /**
     * 执行程序启动:执行 SpringApplication.run() 方法
     * 方法内传参:本类 Class 对象,args 
     * 固定执行写法
     */
    public static void main(String[] args) {
        SpringApplication.run(SpringBootDemoApplication.class, args);
    }

}

@SpringBootConfiguration

  • 包含 @Configuration 注解
    • 可以作为配置类、配置文件使用
    • 可使用 @Bean 声明对象注入到容器
    • 包含 @Component 注解
      • 说明此类也是 spring 组件

@EnableAutoConfiguration

  • 启用自动配置

    • 将 Java 对象配置好注入到 Spring 容器中
    • 例如:自动创建 MyBatis 对象放到容器
  • 复合注解,包含以下注解:实现自动装配

    • @AutoConfigurationPackage
    • @Import({AutoConfigurationImportSelector.class})
  • 加载自动配置类的时候,并非将 spring.factories 的配置全部加载进来

    • 而是通过 @Conditional 等注解的判断进行动态加载
@AutoConfigurationPackage
  • 包含 @AutoConfigurationPackage:自动导入配置包

    • 将主配置类所在的包下面所有组件都扫描注冊到 spring 容器
  • 包含 @Import({AutoConfigurationPackages.Registrar.class})

    • @Import 为 spring 的注解:导入一个配置文件
      • 在 SpringBoot 中是给容器导入一个组件
      • 导入的组件由 AutoConfigurationPackages.class 的内部类 Registrar.class 执行逻辑来决定如何导入
    • Registrar 实现了 ImportBeanDefinitionRegistrar
      • 可以被注解 @Import 导入到 spring 容器
@Import
  • @Import({AutoConfigurationImportSelector.class})
    • 开启自动配置类的导包的选择器
    • 即:带入哪些类,有选择性的导入
  • @Import 支持导入的三种方式
    1. 带有 @Configuration 注解的配置类
    2. ImportSelector 的实现
    3. ImportBeanDefinitionRegistrar 的实现
自动装配流程

自动装配在 AutoConfigurationImportSelector 类中进行以下流程

  1. selectImports(AnnotationMetadata annotationMetadata):选择需要导入的组件

    • 调用 getAutoConfigurationEntry() 方法
  2. getAutoConfigurationEntry(AnnotationMetadata annotationMetadata)

    • 根据导入的配置类的 AnnotationMetadata 返回 AutoConfigurationImportSelector.AutoConfigurationEntry
    • 调用 getCandidateConfigurations()方法找到所有候选的配置类
  3. getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes)

    • 找到所有候选的配置类

      • 经过去重,去除需要的排除的依赖
      • 最终的组件才是环境需要的所有组件
    • 有自动配置,不需要手写配置的值;配置类有默认值

    • 调用 SpringFactoriesLoader.loadFactoryNames() 方法找到需要配置的组件

    • 断言判断调用方法返回的结果

      • loadFactoryNames() 方法找到自动的配置类返回才不会报错
  4. loadFactoryNames(Class factoryType, @Nullable ClassLoader classLoader)

    • 传入参数:this.getSpringFactoriesLoaderFactoryClass()

      • 返回值:EnableAutoConfiguration.class

        • @SpringBootApplication 注解下标识的同一个注解
      • 即获取一个能加载自动配置类的类

        • SpringBoot 默认自动配置类为 EnableAutoConfiguration
      • this.getBeanClassLoader()

        • 返回值:this.beanClassLoader
    • String factoryTypeName = factoryType.getName();

      • factoryTypeEnableAutoConfiguration.class
    • ClassLoader classLoaderToUse = classLoader

      • 当传入类加载器为空时:
        • classLoaderToUse = SpringFactoriesLoader.class.getClassLoader()
    • 返回值:调用 loadSpringFactories() 转为 List 集合返回

      • 方法返回值 Map 集合
      • 调用 getOrDefault(factoryTypeName, Collections.emptyList())
        • factoryTypeName 值是 EnableAutoConfiguration
        • 获取对应的 value
          • META-INF/spring.factories 文件下
          • keyorg.....EnableAutoConfiguration 得值对应的 value
        • value 不存在时返回默认值空数组
  5. loadSpringFactories(ClassLoader classLoader):返回 Map 集合

    • 先从缓存中根据传进来的类加载器加载组件得到 Map result
      • result 不为 null 时直接返回
      • 为 null 时创建新的 HashMap result
    • Enumeration urls = classLoader.getResources("META-INF/spring.factories")
      • 固定资源路径:META-INF/spring.factories
      • 加载引入 jar 包当前类路径下的 META-INF/spring.factories 文件
      • 将所有资源加载到 Propeties 配置文件中
        • 从其中获取对应的自动装配类
      • 获取到组件后放入 result 集合中(此时强转为 List)
        • key:文件中定义的一些标识工厂类
        • value:能自动配置的一些工厂实现的类
          • value 用 List 保存并去重
      • 将 classLoader 作为 key,result 作为 value 放入缓存
        • 下一次加载时可以直接使用
      • 返回 result
    • 方法作用
      • 加载所有依赖的路径 META-INF/spring.factories 文件
      • 通过 map 结构保存
        • key:文件中定义的一些标识工厂类
        • value:能自动配置的一些工厂实现的类
          • value 用 list 保存并去重
    • SpringFactoriesLoader 工厂加载机制
      • Spring内部提供的一个约定俗成的加载方式
      • 只需要在模块的 META-INF/spring.factories 文件
        • Properties 格式的文件中
          • key:接口、注解、或抽象类的全名
          • value:以,分隔的实现类
        • 使用 SpringFactoriesLoader 实现相应的实现类注入 Spirng 容器
      • 加载所有 jar 包下的 classpath 路径下的 META-INF/spring.factories 文件
        • 这样文件不止一个
自动配置原理
  1. SpringBoot 启动的时候加载主配置类

    • @EnableAutoConfiguration 注解开启自动配置功能
  2. @EnableAutoConfiguration 作用

    • 利用 EnableAutoConfigurationImportSelector 给容器中导入一些组件
    • 查看 selectImports() 方法的内容
      • List configurations = getCandidateConfigurations(annotationMetadata, attributes);
        • 获取候选的配置
      • SpringFactoriesLoader.loadFactoryNames()
        • 扫描所有jar包类路径下 META-INF/spring.factories
        • 把扫描到的文件的内容包装成 properties 对象
        • properties 中获取 EnableAutoConfiguration.class 类(类名)对应的值
        • 然后把他们添加在容器中
    • 将类路径 META-INF/spring.factories 里面配置的所有 EnableAutoConfiguration 的值加入到了容器
    • 这样的 xxxAutoConfiguration 类都是容器中的一个组件,都加入到容器中,用他们来做自动配置
  3. 对每一个自动配置类进行自动配置功能

    • HttpEncodingAutoConfiguration 为例解释自动配置原理
      • Http 编码自动配置
    • 所有配置文件中能配置的属性都是在 xxxxProperties 类中封装
      • 配置文件的配置可以参照某个功能对应的属性类
    • 在配置文件中设置属性之后会自动装配到配置类中
      • 实现对配置文件的设置
    @Configuration 						// 表示这是一个配置类,和配置文件一样,也可以给容器中添加组件
    /* 
    启动指定类的 ConfigurationProperties 功能;
    将配置文件中对应的值和 HttpEncodingProperties 绑定起来;
    并把 HttpEncodingProperties 加入到ioc容器中 
    */
    @EnableConfigurationProperties(HttpEncodingProperties.class) 
    /* 
    Spring 底层 @Conditional 注解(Spring注解版),
    如果满足指定的条件,整个配置类里面的配置就会生效;
    判断当前应用是否是 web 应用,如果是,当前配置类生效 
    */
    @ConditionalOnWebApplication 
    // 判断当前项目有没有 CharacterEncodingFilter:SpringMVC中进行乱码解决的过滤器;
    @ConditionalOnClass(CharacterEncodingFilter.class) 
    /* 
    判断配置文件中是否存在配置:spring.http.encoding.enabled,如果不存在,判断也成立
    即使配置文件中不配置 pring.http.encoding.enabled=true,也默认生效的
    */
    @ConditionalOnProperty(prefix = "spring.http.encoding", value = "enabled", matchIfMissing = true) 
    public class HttpEncodingAutoConfiguration {
    
        // 和 SpringBoot 的配置文件映射
        private final HttpEncodingProperties properties;
    
        // 只有一个有参构造器的情况下,参数的值就会从容器中拿
        public HttpEncodingAutoConfiguration(HttpEncodingProperties properties) {
            this.properties = properties;
        }
    
        @Bean // 给容器中添加一个组件,组件的某些值需要从 properties 中获取
        @ConditionalOnMissingBean(CharacterEncodingFilter.class) // 判断容器是否有这个组件
        public CharacterEncodingFilter characterEncodingFilter() {
            CharacterEncodingFilter filter = new OrderedCharacterEncodingFilter();
            filter.setEncoding(this.properties.getCharset().name());
            filter.setForceRequestEncoding(this.properties.shouldForce(Type.REQUEST));
            filter.setForceResponseEncoding(this.properties.shouldForce(Type.RESPONSE));
            return filter;
        }
    }
    
  • 当需要其他的配置时
    • 如监听相关配置:listenter
    • 传入不同的参数,获取相关的 listenter 配置
  • 只有自动配置类进入到容器中以后,这个自动配置类才开始进行启动
  • 通过 @Conditional 等注解的判断进行动态加载
    • 加载自动配置类的时候,并非将 spring.factories 的配置全部加载进来

@ComponentScan

  • 组件扫描器,配置用于 Configuration 类的组件扫描指令
    • 类似
    • basePackageClassesbasePackages 来定义要扫描的特定包
  • 默认扫描 @ComponentScan 所在类的同级包和子包
    • 没有定义特定的包,将从声明该注解的类的包开始扫描

run()

  • SpringApplication.run() 方法启动流程
  • 作用
    • 判断应用类型:普通项目 或 Web 项目
    • 查找并加载所有可用初始化器,设置到 initializers属性
    • 加载所有监听器程序,设置到 listeners属性
    • 判断并设置 main() 方法定义类,找到运行主类

Spring Boot 之主启动类_第2张图片

ApplicationContext

  • 手动获取容器对象

    • SpringApplication.run() 方法返回 Spring 容器对象

      • 返回值类型:ConfigurableApplicationContext
      • 此接口继承 ApplicationContext 接口
    • 再获取业务 bean 进行调用

//持久层类,注解创建对象放到容器中
@Repository
class Demo{
    //类定义略
}

//主类,启动时扫描同级、下级目录注解对象放到容器
@SpringBootApplication
public class DemoApplication{
    public static void main(String[] args){
        //获取容器对象
        ApplicationContext context = SpringApplication.run( DemoApplication.class, args);
        //从容器中的得到 bean 对象
        Demo demo = (Demo)context.getBean("demo");
        System.out.println(demo);
    }
}

启动后执行

  • 开发中可能需要在容器启动后执行一些内容
    • 比如:读取配置文件、数据库连接之类
  • 四种实现方式
    1. 注解:@PostConstruct
    2. 实现 CommandLineRunner 接口
    3. 实现 ApplicationRunner 接口
    4. 实现 ApplicationListener 接口
  • 执行顺序
    1. 注解 @PostConstruct 始终最先执行
    2. 监听 ApplicationStartedEvent 事件
    3. ApplicationRuner 默认优先执行
    4. CommandLineRunner 默认后执行
      • 若两个接口都指定了 @Order 则按数字大小执行
      • 数字越小优先级越高
    5. 监听 ApplicationReadyEvent 事件

@PostConstruct

  • Java 自带注解,注解在方法上会在项目启动时执行该方法
    • Spring 容器初始化时执行
  • 执行顺序
    1. 构造函数
    2. @Autowired
    3. @PostConstruct
  • 注意事项
  • 方法不能有参数
  • 方法返回值为 void
  • 方法不能抛出已检查异常
  • 方法是非静态方法
  • 方法只会被执行一次
  • 耗时长的逻辑应放到独立线程,减少容器初始化时间
  • 容器加载过程
    • SpringBoot 将标记 Bean 的相关注解的类或接口自动初始化全局单一实例
      • 当未标记初始化顺序时按照默认顺序进行初始化
      • 初始化过程中执行一个 Bean 的构造方法后会执行该类存在的 @PostConstruce 方法,之后再初始化下一个 Bean
    • @PostConstruct 方法中逻辑处理时间过长时会增加 SpringBoot 初始化 Bean 时间,增加应用启动时间
      • 只有 Bean 初始化完成后才会打开端口服务,所以初始化完成前应用不可访问

两个接口

  • SpringBoot 提供两个接口实现这种需求,容器启动成功后的最后一步回调

    • CommandLineRunner
      • run 方法参数:String 数组
    • ApplicationRunner
      • run 方法参数:ApplicationArguments
      • 参数格式:K=V
    • 实现接口中重写 run() 方法实现即可
      • 两个接口的 run 方法参数不同,效果相同
    • 容器对象创建之后自动执行 run 方法
      • 完成容器对象创建之后的自定义操作
        • 容器对象创建的同时会将容器中的对象创建
        • run 方法中可以获取容器中的对象使用
      • 控制执行顺序
        1. 通过 @Order 注解指定优先级
          • 数字越小优先级越高
        2. 通过 Ordered 接口控制执行顺序
      • 方法执行时项目已初始化完毕,可以正常提供服务
  • 示例

    • package com.example.demo2;
      import org.springframework.boot.CommandLineRunner;
      import org.springframework.boot.SpringApplication;
      import org.springframework.boot.autoconfigure.SpringBootApplication;
      /**
       * 实现 CommandLineRunner 接口,Order 设置为 1 使优先级最高
       */
      @SpringBootApplication
      @Order(value = 1)
      public class Demo2Application implements CommandLineRunner {
          public static void main(String[] args) {
              SpringApplication.run(Demo2Application.class, args);
          }
          // 覆写接口的 run 方法,在容器创建后自动执行
          @Override
          public void run(String... args) throws Exception {
      
          }
      }
      
      package li_maven.springboot;
      
      import org.springframework.boot.ApplicationArguments;
      import org.springframework.boot.ApplicationRunner;
      import org.springframework.boot.SpringApplication;
      import org.springframework.boot.autoconfigure.SpringBootApplication;
      /**
       * 实现 ApplicationRunner 接口,Order 设置为 2 优先级次高
       */
      @Order(value = 2)
      @SpringBootApplication
      public class Application implements ApplicationRunner {
          
          public static void main(String[] args) {
              SpringApplication.run(Application.class, args);
          }
          
          @Override
          public void run(ApplicationArguments args) throws Exception {
              System.out.println("启动后自动执行");
          }
      }
      
      

ApplicationListener

  • 和另两个接口实现一样都不影响服务,可以正常提供服务

  • 监听事件通常是 ApplicationStartedEventApplicationReadyEnvent

    • 其他事件可能无法注入 Bean
  • 使用

    • package li_maven.springboot;
      
      import org.springframework.boot.SpringApplication;
      import org.springframework.boot.autoconfigure.SpringBootApplication;
      import org.springframework.context.ApplicationEvent;
      import org.springframework.context.ApplicationListener;
      
      @SpringBootApplication
      public class Application implements ApplicationListener {
          
          public static void main(String[] args) {
              SpringApplication.run(Application.class, args);
          }
           
          @Override
          public void onApplicationEvent(ApplicationEvent event) {
              
          }
      }
      
      

你可能感兴趣的:(java,spring,boot)