SpringBoot事件与监听机制

背景:最近需要在项目启动前去做初始化脚本,所以看了一下关于springboot的监听机制,做一下记录

通常我们启动应用就使用这么一条命令SpringApplication.run(XXXX.class,args);然后我们的项目就启动了。是不是早就想知道run之后发生了什么?

我们跟踪进去,就会来到下图的run方法。

从图中代码可知:

  1. 构造SpringApplication对象
  2. 调用该对象的run方法
  3. 该对象的run方法返回了实现ConfigurableApplicationContext接口的对象

与我们主题相关的内容在run方法里。

这里面有两个内容,第一初始化SpringApplication,第二是初始化SpringApplication后开始真正运行run方法

 

先看SpringApplication的初始化,其实里面最重要的就是SpringBoot自动装载机制,像监听器Listeners初始化,还有什么InitiaLizers。当然还有很多其他初始化任务。这里我们来重点看一下Listener监听注册的初始化。

  这里我们重点看下setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));,由getSpringFactoriesInstances进去可以看到

 这里的names就是收集了初始化后的ApplicationListener的所有实现的监听器,具体有10个如下图:

 接下来给大家再往下具体分析这个监听器是如何被初始化的,进入SpringFactoriesLoader.loadFactoryNames(type, classLoader)中如下

然后再进入loadSpringFactories方法,如下:

  private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {

            //这里将会最终存放所有初始化后的监听器
            MultiValueMap<String, String> result = cache.get(classLoader);
                if (result != null) {
                return result;
            }

         try {
             Enumeration<URL> urls = (classLoader != null ?
             //下面的的 FACTORIES_RESOURCE_LOCATION =  “META-INF/spring.factories”,这个地方往下就会涉及到Spring的自动装配SPI机制了
             classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
             ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
             result = new LinkedMultiValueMap<>();
             while (urls.hasMoreElements()) {
                       URL url = urls.nextElement();
                       UrlResource resource = new UrlResource(url);
                       Properties properties = PropertiesLoaderUtils.loadProperties(resource);
                       for (Map.Entry<?, ?> entry : properties.entrySet()) {
                                   String factoryClassName = ((String) entry.getKey()).trim();
                                   for (String factoryName : StringUtils.commaDelimitedListToStringArray((String) entry.getValue())) {
                                   result.add(factoryClassName, factoryName.trim());
                        }
                    }
                }    
                     //这里将会吧所有的初始化的监听对象放入缓存中
                     cache.put(classLoader, result);
                     return result;
             }
                catch (IOException ex) {
                               throw new IllegalArgumentException("Unable to load factories from location [" +
                               FACTORIES_RESOURCE_LOCATION + "]", ex);
              }
         }

那么上面的spring.factories文件是在哪里的呢,通过调试得知如下图

由此可知我们可以按照这个path找出这个文件

   现在让我们来看下这个文件到底张什么样的,我们打开这个文章一起来看下

            # PropertySource Loaders
              org.springframework.boot.env.PropertySourceLoader=\
              org.springframework.boot.env.PropertiesPropertySourceLoader,\
              org.springframework.boot.env.YamlPropertySourceLoader

            # Run Listeners
              org.springframework.boot.SpringApplicationRunListener=\
              org.springframework.boot.context.event.EventPublishingRunListener

            # Error Reporters
              org.springframework.boot.SpringBootExceptionReporter=\
              org.springframework.boot.diagnostics.FailureAnalyzers

            # Application Context Initializers
              org.springframework.context.ApplicationContextInitializer=\
              org.springframework.boot.context.ConfigurationWarningsApplicationContextInitializer,\
              org.springframework.boot.context.ContextIdApplicationContextInitializer,\
              org.springframework.boot.context.config.DelegatingApplicationContextInitializer,\
              org.springframework.boot.web.context.ServerPortInfoApplicationContextInitializer

           # Application Listeners
              org.springframework.context.ApplicationListener=\ 
              //以下都是ApplicatinListener的实现类,这些类都将会在上面
              org.springframework.boot.ClearCachesApplicationListener,\
              org.springframework.boot.builder.ParentContextCloserApplicationListener,\
              org.springframework.boot.context.FileEncodingApplicationListener,\
              org.springframework.boot.context.config.AnsiOutputApplicationListener,\
              org.springframework.boot.context.config.ConfigFileApplicationListener,\
              org.springframework.boot.context.config.DelegatingApplicationListener,\
              org.springframework.boot.context.logging.ClasspathLoggingApplicationListener,\
              org.springframework.boot.context.logging.LoggingApplicationListener,\
              org.springframework.boot.liquibase.LiquibaseServiceLocatorApplicationListener

            # Environment Post Processors
              org.springframework.boot.env.EnvironmentPostProcessor=\
              org.springframework.boot.cloud.CloudFoundryVcapEnvironmentPostProcessor,\
              org.springframework.boot.env.SpringApplicationJsonEnvironmentPostProcessor,\
              org.springframework.boot.env.SystemEnvironmentPropertySourceEnvironmentPostProcessor

            # Failure Analyzers
              org.springframework.boot.diagnostics.FailureAnalyzer=\
              org.springframework.boot.diagnostics.analyzer.BeanCurrentlyInCreationFailureAnalyzer,\
              org.springframework.boot.diagnostics.analyzer.BeanDefinitionOverrideFailureAnalyzer,\
              org.springframework.boot.diagnostics.analyzer.BeanNotOfRequiredTypeFailureAnalyzer,\
              org.springframework.boot.diagnostics.analyzer.BindFailureAnalyzer,\
              org.springframework.boot.diagnostics.analyzer.BindValidationFailureAnalyzer,\
              org.springframework.boot.diagnostics.analyzer.UnboundConfigurationPropertyFailureAnalyzer,\
              org.springframework.boot.diagnostics.analyzer.ConnectorStartFailureAnalyzer,\
              org.springframework.boot.diagnostics.analyzer.NoSuchMethodFailureAnalyzer,\
              org.springframework.boot.diagnostics.analyzer.NoUniqueBeanDefinitionFailureAnalyzer,\
              org.springframework.boot.diagnostics.analyzer.PortInUseFailureAnalyzer,\
              org.springframework.boot.diagnostics.analyzer.ValidationExceptionFailureAnalyzer,\
              org.springframework.boot.diagnostics.analyzer.InvalidConfigurationPropertyNameFailureAnalyzer,\
              org.springframework.boot.diagnostics.analyzer.InvalidConfigurationPropertyValueFailureAnalyzer

            # FailureAnalysisReporters
              org.springframework.boot.diagnostics.FailureAnalysisReporter=\
              org.springframework.boot.diagnostics.LoggingFailureAnalysisReporter

这里我们来具体看下ApplictionListener加载的那些类,因为这些跟我们接下来要讲的CnfigFileListener会有关系。其实很简单,在MultiValueMap<String, String> result = cache.get(classLoader);的结果集别刻意看出,因为最终初始化的所有对象都将add到这个result中去。看下结果图如下:

那么有了以上的基础我们可以自己定义一些事件,让容器在初始化spring容器之前为我们做些事情:

我们尝试自己创建实现ApplictionListener:


import com.mgk.demov1.annotation.Student;
import com.sun.org.apache.bcel.internal.generic.SWITCH;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.context.event.*;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationEvent;
import org.springframework.context.ApplicationListener;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.event.ContextClosedEvent;
import org.springframework.context.event.ContextRefreshedEvent;
import org.springframework.context.event.ContextStartedEvent;
import org.springframework.context.event.ContextStoppedEvent;
 
public class MyListenery implements ApplicationListener {
    @Override
    public void onApplicationEvent(ApplicationEvent event) {
//        ApplicationStartingEvent//启动开始的时候执行的事件
//        ApplicationEnvironmentPreparedEvent//上下文创建之前运行的事件
//        ApplicationContextInitializedEvent//
//        ApplicationPreparedEvent//上下文创建完成,注入的bean还没加载完成
//        ContextRefreshedEvent//上下文刷新
//        ServletWebServerInitializedEvent//web服务器初始化
//        ApplicationStartedEvent//
//        ApplicationReadyEvent//启动成功
//        ApplicationFailedEvent//在启动Spring发生异常时触发
        switch (event.getClass().getSimpleName()){
            case "ApplicationStartingEvent":
                System.out.println("启动开始的时候执行的事件");
                break;
            case "ApplicationEnvironmentPreparedEvent":
                System.out.println("上下文创建之前运行的事件");
                break;
            case "ApplicationContextInitializedEvent":
                System.out.println("上下文初始化");
                break;
            case "ApplicationPreparedEvent":
                System.out.println("上下文创建完成,注入的bean还没加载完成");
                break;
            case "ContextRefreshedEvent":
                System.out.println("上下文刷新");
                if( event instanceof ContextRefreshedEvent){
                    Object stu = ((ContextRefreshedEvent) event).getApplicationContext().getBean("stu");
                    System.out.println(stu);
                }
                break;
            case "ApplicationStartedEvent":
                System.out.println("ApplicationStartedEvent");
                break;
            case "ApplicationReadyEvent":
                System.out.println("启动成功");
                break;
            case "ApplicationFailedEvent":
                break;
        }
    }
}

也可以单独去实现其中一个监听事件,来解决业务具体操作

import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationListener;
import org.springframework.context.event.ContextRefreshedEvent;
 
public class MyListenerv2 implements ApplicationListener<ContextRefreshedEvent> {
 
    @Override
    public void onApplicationEvent(ContextRefreshedEvent contextRefreshedEvent) {
        System.out.println(contextRefreshedEvent);
    }

主程序这边可以添加多个自定义监听顺序自上而下

  SpringApplication app = new SpringApplication(Demov1Application.class);
        app.addListeners(new MyListenery());
        app.addListeners(new MyListenerv2());
        app.run(args);

springboot支持的事件类型如下:

  • ApplicationFailedEvent:该事件在springboot启动失败是调用
  • ApplicationPreparedEvent:上下文context准备时触发
  • ApplicationReadyEvent:上下文已经准备完毕的时候触发
  • ApplicationStartedEvent:spring boot 启动监听类
  • SpringApplicationEvent:获取SpringApplication
  • ApplicationEnvironmentPreparedEvent:环境事先准备

哪些场景会用到

1.启动前环境检测?

2.启动时配置初始化?

3.启动后数据初始化?

...

应用的场景很多,可以发挥我们的想象。

我们还看到springboot启动是还有很多其它很多bean也可以实现类是的监听功能,在事件发生的时候

事件回调机制

代码样例

分别对4个类继承

HelloApplicationContextInitializer

HelloApplicationRunner
@Component
public class HelloApplicationRunner implements ApplicationRunner {
    @Override
    public void run(ApplicationArguments args) throws Exception {
        System.out.println("运行ApplicationRunner:ApplicationRunner...run....");
    }
}

 HelloCommandLineRunner类

@Component
public class HelloCommandLineRunner implements CommandLineRunner {
    @Override
    public void run(String... args) throws Exception {
        System.out.println("运行LineRunner:CommandLineRunner...run..."+ Arrays.asList(args));
    }
}
HelloSpringApplicationRunListener类
package com.limp.listerner;
 
import org.springframework.boot.SpringApplication;
import org.springframework.boot.SpringApplicationRunListener;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.core.env.ConfigurableEnvironment;
 
public class HelloSpringApplicationRunListener implements SpringApplicationRunListener {
 
    //必须有的构造器
    public HelloSpringApplicationRunListener(SpringApplication application, String[] args){
 
    }
 
    @Override
    public void starting() {
        System.out.println("运行RunListener:SpringApplicationRunListener...starting...");
    }
 
    @Override
    public void environmentPrepared(ConfigurableEnvironment environment) {
        Object o = environment.getSystemProperties().get("os.name");
        System.out.println("获取系统环境:SpringApplicationRunListener...environmentPrepared.."+o);
    }
 
    @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) {
 
    }
 
    @Override
    public void running(ConfigurableApplicationContext context) {
 
    }
 
    @Override
    public void failed(ConfigurableApplicationContext context, Throwable exception) {
 
    }
 
 
}

注意上面2个类需要META-INF/spring.factories添加如下配置才能生效

org.springframework.context.ApplicationContextInitializer=\
com.limp.listerner.HelloApplicationContextInitializer
 
org.springframework.boot.SpringApplicationRunListener=\
com.limp.listerner.HelloSpringApplicationRunListene

开始测试

启动应用...运行结果如下

运行RunListener:SpringApplicationRunListener...starting...
获取系统环境:SpringApplicationRunListener...environmentPrepared..Windows 10
.....
运行Initializer:ApplicationContextInitializer...initialize...org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext@557cd14f: startup date [Thu Jan 01 08:00:00 CST 1970]; root of context hierarchy
 
运行ApplicationRunner:ApplicationRunner...run....
运行LineRunner:CommandLineRunner...run...[]

参考文献:

SpringBoot启动及配置文件加载原理分析: https://www.cnblogs.com/dszazhy/p/11513012.html

如何自定义启动监听:https://blog.csdn.net/zzhuan_1/article/details/85312053

原理是什么?https://www.cnblogs.com/dszazhy/p/11513012.html

 

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值