写点什么

SpringBoot 框架原理,你不知道的事件回调机制

发布于: 2021 年 04 月 18 日
SpringBoot框架原理,你不知道的事件回调机制

几个重要的事件回调机制

ApplicationContextInitializer

  • ApplicationContextInitializer 来源于 Spring 框架

  • 主要作用就是在 ConfigurableApplicationContext 类型或者子类型的 ApplicationContext refresh 之前

  • 允许对 ConfigurableApplicationContext 的实例做进一步的设置和处理

  • ApplicationContextInitializer 接口:

  • 是在 Spring 容器刷新之前执行的一个回调函数

  • 是在 ConfigurableApplicationContext refresh() 方法之前,即在 Spring 框架内部执行 ConfigurableApplicationContext refresh() 方法或者 SpringBoot run() 方法之前调用

  • 作用是初始化 Spring ConfigurableApplicationContext 的回调接口

  • 通常用于需要对应用上下文进行初始化的 web 应用程序中: 比如根据上下文环境注册属性或者激活概要文件

使用分析
  • ApplicationContextInitializer 接口的典型应用场景:

  • web 应用程序的应用上下文进行初始化

  • 比如:

  • 注册属性源 property sources

  • 针对上下文的环境信息 environment 激活相应的 profile

  • 在一个 SpringBoot 的应用程序中:

  • classpath 上有很多 jar 包,有些 jar 包需要在 ConfigurableApplicationContext refresh() 方法调用之前对应用上下文做一些初始化动作

  • 因此会提供自己的 ApplicationContextInitializer 实现类,然后配置在自己的 META-INF/spring.factories 属性文件中

  • 这样相应的 ApplicationContextInitializer 实现类就会被 SpringApplication initialize() 方法发现

  • SpringApplication initialize() 方法,在 SpringApplication 的构造函数内执行,从而确保在 SpringApplication run() 方法之前完成

  • 然后在应用上下文创建之后,应用上下文刷新之前的准备阶段被调用

SpringBoot 内置的 ApplicationContextInitializer
  • 使用 SpringBoot web 应用默认使用的 ApplicationContextInitializer 的实现:

  • DelegatingApplicationContextInitializer:

  • 使用环境属性 context.initializer.classes 指定的初始化容器 initializer 进行初始化工作,如果没有指定则不进行任何操作

  • 使得可以在 application.properties 中可以自定义实现类配置

  • ContextIdApplicationContextInitializer:

  • 参照环境属性,设置 Spring 应用上下文的 ID

  • ID 值的设置会参照环境属性:

  • spring.application.name

  • vcap.application.name

  • spring.config.name

  • spring.application.index

  • vcap.application.instance_index

  • 如果这些属性都没有 ,ID 使用 application

  • ConfigurationWarningApplicationContextInitializer:

  • 对于一般配置错误在日志中做出警告

  • ServerPortInfoApplicationContextInitializer:

  • 将内置 servlet 容器实际使用的监听端口写入到 environment 环境属性中

  • 这样属性 local.server.port 就可以直接通过 @Value 注入到测试中或者通过环境属性 environment 获取

  • SharedMetadataReaderFactoryContextInitializer:

  • 创建一个 SpringBoot ConfigurationClassPostProcessor 共用的 CachingMetadataReaderFactory 对象

  • 实现类为 ConcurrentReferrenceCachingMetadataReaderFactory

  • ConditionEvaluationReportLoggingListener:

  • ConditionEvaluationReport 写入日志

  • ApplicationContextInitializer Spring 中允许在上下文刷新之前做自定义操作,如果需要对 Spring 的上下文进行深度整合,可以借助 ApplicationContextInitializer 进行很好的实现

  • spring-test 包里有一个注解 org.springframework.test.context.ContextConfiguration 中有一个属性可以指定 ApplicationContextInitializer 辅助集成测试时自定义对上下文进行预处理

扩展实现方式

编程方式


  • 先定义 ApplicationContextInitializer:


// @Order(66) - @Order的值越小就越早执行. 标注在类上, 不是方法上@Order(66)public class customerApplicationContextInitializer implements ApplicationContextInitializer {  @Override  public void initialize(ConfigurableApplicationContext applicationContext) {    // 输出容器中有多少个bean    System.out.println("Bean的数量为: " + applicationContext.getBeanDefinitionCount());    /*     * 输出容器中所有bean的beanName     */     System.out.println(applicationContext.getBeanDefinitionCount + "个Bean的名称:");     String[] beanDefinitionNames = applicationContext.getBeanDefinitionNames();     for (String beanName : beanDefinitionNames) {       System.out.println(beanName);     }  }}
复制代码


  • 在启动类中手动增加 initializer:


@SpringBootApplication@EnableConfigServer@EnableDiscoveryClientpublic class ConfigServer {  public static void mian(String[] args) {    SpringApplication springApplication = new SpringApplication(ConfigServer.class);    // 添加自定义的ApplicationContextInitializer实现类的实例用来注册ApplicationContextInitializer    springApplication.addInitializers(new customerApplicationContextInitializer());    ConfigurableApplicationContext applicationContext = springApplication.run(args);    applicationContext.close();  }}
复制代码


添加配置方式


  • 添加配置的方式是通过 DelegatingApplicationContextInitializer 初始化类中的 initialize() 方法获取到 application.properties context.initializer.class 对应的类并执行对应的 initialize() 方法

  • 只需要将实现了 ApplicationContextInitializer 的类添加到 application.properties 即可

  • 先定义一个实现了 ApplocationContextInitializer 的类

  • 然后在 application.properties 中定义:


  context.initializer.class= com.oxford.customerApplicationContextInitializer
复制代码


spring.factories 方式


  • SpringApplicationRunListener

  • ApplicationContextInitializer,SpringApplicationRunListener 需要配置在 META-INF/spring.factories 中

  • ApplicationRunner

  • CommandLineRunner

  • ApplicationRunner,CommandLineRunner 需要放在 IOC 容器中

启动流程

  • 创建 SpringApplication 对象

  • 调用 initialize(sources)方法创造对象

  • 保存主配置类

  • 判断当前是否为一个 web 应用

  • 从类路径下找到 META-INF/spring.factories 配置的所有 ApplicationContextInitializer,然后保存起来

  • 从从类路径下找到 META-INF/spring.factories 配置的所有 ApplicatListener

  • 从多个配置类中找到有 main 方法的主配置类

  • 运行 run 方法


public ConfigurableApplicationContext run(String... args) {        StopWatch stopWatch = new StopWatch();            stopWatch.start();    // 停止监听        ConfigurableApplicationContext context = null;    // 声明一个IOC容器        Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList();        this.configureHeadlessProperty();        SpringApplicationRunListeners listeners = this.getRunListeners(args);            listeners.starting();
Collection exceptionReporters; try { ApplicationArguments applicationArguments = new DefaultApplicationArguments(args); ConfigurableEnvironment environment = this.prepareEnvironment(listeners, applicationArguments); this.configureIgnoreBeanInfo(environment); Banner printedBanner = this.printBanner(environment); context = this.createApplicationContext(); exceptionReporters = this.getSpringFactoriesInstances(SpringBootExceptionReporter.class, new Class[]{ConfigurableApplicationContext.class}, context); this.prepareContext(context, environment, listeners, applicationArguments, printedBanner); this.refreshContext(context); this.afterRefresh(context, applicationArguments); stopWatch.stop(); if (this.logStartupInfo) { (new StartupInfoLogger(this.mainApplicationClass)).logStarted(this.getApplicationLog(), stopWatch); }
listeners.started(context); this.callRunners(context, applicationArguments); } catch (Throwable var10) { this.handleRunFailure(context, var10, exceptionReporters, listeners); throw new IllegalStateException(var10); }
try { listeners.running(context); return context; } catch (Throwable var9) { this.handleRunFailure(context, var9, exceptionReporters, (SpringApplicationRunListeners)null); throw new IllegalStateException(var9); } }
复制代码


  • 获取 SpringApplicationRunListeners,从类路径下 META-INF/spring.factories

  • 回调所有的获取 SpringApplicationRunListener.starting()方法

  • 封装命令行参数

  • 准备环境 prepareEnvironment,创建环境完成后回调 SpringApplicationRunListeners.environmentPrepared():表示环境准备完成


private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments) {        ConfigurableEnvironment environment = this.getOrCreateEnvironment();        this.configureEnvironment((ConfigurableEnvironment)environment, applicationArguments.getSourceArgs());        listeners.environmentPrepared((ConfigurableEnvironment)environment);        this.bindToSpringApplication((ConfigurableEnvironment)environment);        if (!this.isCustomEnvironment) {            environment = (new EnvironmentConverter(this.getClassLoader())).convertEnvironmentIfNecessary((ConfigurableEnvironment)environment, this.deduceEnvironmentClass());        }
ConfigurationPropertySources.attach((Environment)environment); return (ConfigurableEnvironment)environment; }
复制代码


  • 创建 ApplicationContext:决定创建 web 的 IOC 还是普通的 IOC


protected ConfigurableApplicationContext createApplicationContext() {        Class<?> contextClass = this.applicationContextClass;        if (contextClass == null) {            try {                switch(this.webApplicationType) {                case SERVLET:                    contextClass = Class.forName("org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext");                    break;                case REACTIVE:                    contextClass = Class.forName("org.springframework.boot.web.reactive.context.AnnotationConfigReactiveWebServerApplicationContext");                    break;                default:                    contextClass = Class.forName("org.springframework.context.annotation.AnnotationConfigApplicationContext");                }            } catch (ClassNotFoundException var3) {                throw new IllegalStateException("Unable create a default ApplicationContext, please specify an ApplicationContextClass", var3);            }        }
return (ConfigurableApplicationContext)BeanUtils.instantiateClass(contextClass); }
复制代码


  • 准备上下文环境 prepareContext:将 environment 保存到 IOC 中,并且调用 applyInitializers():回调之前保存的所有的 ApplicationContextInitializer 的 initialize 方法.然后回调 SpringApplicationRunListener 的 contextPrepared 方法


private void prepareContext(ConfigurableApplicationContext context, ConfigurableEnvironment environment, SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments, Banner printedBanner) {        context.setEnvironment(environment);        this.postProcessApplicationContext(context);        this.applyInitializers(context);        listeners.contextPrepared(context);        if (this.logStartupInfo) {            this.logStartupInfo(context.getParent() == null);            this.logStartupProfileInfo(context);        }
ConfigurableListableBeanFactory beanFactory = context.getBeanFactory(); beanFactory.registerSingleton("springApplicationArguments", applicationArguments); if (printedBanner != null) { beanFactory.registerSingleton("springBootBanner", printedBanner); }
if (beanFactory instanceof DefaultListableBeanFactory) { ((DefaultListableBeanFactory)beanFactory).setAllowBeanDefinitionOverriding(this.allowBeanDefinitionOverriding); }
Set<Object> sources = this.getAllSources(); Assert.notEmpty(sources, "Sources must not be empty"); this.load(context, sources.toArray(new Object[0])); listeners.contextLoaded(context); }
复制代码


  • prepareContext 运行完成以后回调所有的 SpringApplicationRunListeners 的 contextLoaded()方法

  • 刷新容器 refreshContext,IOC 容器初始化.在 web 应用中还会创建嵌入式的 tomcat.在 refreshContext,是扫描,创建.加载所有组件的地方(配置类,组件,自动配置)


private void refreshContext(ConfigurableApplicationContext context) {        this.refresh(context);        if (this.registerShutdownHook) {            try {                context.registerShutdownHook();            } catch (AccessControlException var3) {            }        }
}
复制代码


  • 调用 callRunner()从 IOC 容器中获取所有的 ApplicationRunner 和 CommandLineRunner.先回调 ApplicationRunner,后回调 CommandLineRunner

  • 最后回调 SpringApplicationRunListeners 的 listeners.running(context)

  • 整个 SpringBoot 应用启动完成以后返回启动的 IOC 容器

事件监听机制

  • ApplicationContextInitializer


public class HelloApplicationContextInitializer implements ApplicationContextInitializer<ConfigurableApplicationContext > {    @Override    public void initialize(ConfigurableApplicationContext configurableApplicationContext) {        System.out.println("ApplicationContextInitializer...initialize"+configurableApplicationContext);    }}
复制代码


  • SpringApplicationRunListener


 public class HelloSpringApplicationRunListener implements SpringApplicationRunListener {    @Override    public void starting() {        System.out.println("SpringApplicationRunListener...starting...");    }
@Override public void environmentPrepared(ConfigurableEnvironment environment) { Object o=environment.getSystemProperties().get("os.name"); System.out.println("SpringApplicationRunListener...environmentPrepared..."); }
@Override public void contextPrepared(ConfigurableApplicationContext context) { System.out.println("SpringApplicationRunListener...contextPrepared..."); }
@Override public void contextLoaded(ConfigurableApplicationContext context) { System.out.println("SpringApplicationRunListener...contextLoaded..."); }
@Override public void started(ConfigurableApplicationContext context) { System.out.println("SpringApplicationRunListener...started..."); }
@Override public void running(ConfigurableApplicationContext context) { System.out.println("SpringApplicationRunListener...running..."); }
@Override public void failed(ConfigurableApplicationContext context, Throwable exception) { System.out.println("SpringApplicationRunListener...failed..."); }}
复制代码


  • ApplicationContextInitializer,SpringApplicationRunListener 需要配置在 META-INF/spring.factories 中


org.springframework.context.ApplicationContextInitializer=\com.web.springboot.listener.HelloApplicationContextInitializer
org.springframework.context.SpringApplicationRunListener=\com.web.springboot.listener.HelloSpringApplicationRunListener
复制代码


  • ApplicationRunner


 @Component      // 容器中的类 public class HelloApplicationRunner implements ApplicationRunner {    @Override    public void run(ApplicationArguments args) throws Exception {        System.out.println("ApplicationRunner...run...");    }}
复制代码


CommandLineRunner


 @Component      // 容器中的类 public class HelloCommandLineRunner implements CommandLineRunner {    @Override    public void run(String... args) throws Exception {        System.out.println("CommandLineRunner...run"+ Arrays.asList(args));    }}
复制代码


  • ApplicationRunner,CommandLineRunner 需要放在 IOC 容器中-==@Component==

SpringBoot 自定义 starter

  • starter:

  • 这个场景需要的依赖是什么?

  • 如何编写自动配置?


  @Configuration      // 指定这个类是一个自动配置类  @ConditionalOnXxx()    // 在指定条件成立的情况下自动配置类生效  @AutoConfigureOrder()   // 指定自动配置类的顺序  @AutoConfigureAfter()  // 指定自动配置在特定的类之后      @Bean      // 给容器中添加组价  ( @ConfigurationProperties 结合相关 XxxProperties类来绑定相关的配置 )  @EnableConfigurationProperties  // 让XxxProperties类生效加入到容器中
复制代码


  • 配置自动装配 Bean:将标注 @Configuration 的自动配置类,放在 classpath 下的==META-INF/spring.factories==文件中才能加载


  org.springframework.boot.autoconfigure.AutoConfigurationImportListener=\  org.springframework.boot.autoconfigure.condition.ConditionEvaluationReportAutoConfigurationImportListener
复制代码


  • 模式:

  • 启动器: 启动器是一个空 jar 文件,==仅提供辅助性依赖管理,依赖导入==,这些依赖用于自动装配或者其它类库.


    官方命名空间:        - 前缀: spring-boot-starter-        - 模式: spring-boot-starter-模块名    自定义命名空间:          - 前缀: -spring-boot-starter-        - 模式: 模块名-spring-boot-starter
复制代码


- 专门写一个自动配置模块- 启动器依赖自动配置模块,使用时只需要引入启动器( starter )
复制代码

总结

  • 官方文档

  • 源码

发布于: 2021 年 04 月 18 日阅读数: 79
用户头像

一位攻城狮的自我修养 2021.04.06 加入

分享技术干货,面试题和攻城狮故事。 你的关注支持是我持续进步的最大动力! https://github.com/ChovaVea

评论

发布
暂无评论
SpringBoot框架原理,你不知道的事件回调机制