背景:最近需要在项目启动前去做初始化脚本,所以看了一下关于springboot的监听机制,做一下记录
通常我们启动应用就使用这么一条命令SpringApplication.run(XXXX.class,args);然后我们的项目就启动了。是不是早就想知道run之后发生了什么?
我们跟踪进去,就会来到下图的run方法。
从图中代码可知:
- 构造SpringApplication对象
- 调用该对象的run方法
- 该对象的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