javascript
SpringBoot事件与监听机制
背景:最近需要在項(xiàng)目啟動(dòng)前去做初始化腳本,所以看了一下關(guān)于springboot的監(jiān)聽(tīng)機(jī)制,做一下記錄
通常我們啟動(dòng)應(yīng)用就使用這么一條命令SpringApplication.run(XXXX.class,args);然后我們的項(xiàng)目就啟動(dòng)了。是不是早就想知道run之后發(fā)生了什么?
我們跟蹤進(jìn)去,就會(huì)來(lái)到下圖的run方法。
從圖中代碼可知:
與我們主題相關(guān)的內(nèi)容在run方法里。
這里面有兩個(gè)內(nèi)容,第一初始化SpringApplication,第二是初始化SpringApplication后開(kāi)始真正運(yùn)行run方法
?
先看SpringApplication的初始化,其實(shí)里面最重要的就是SpringBoot自動(dòng)裝載機(jī)制,像監(jiān)聽(tīng)器Listeners初始化,還有什么InitiaLizers。當(dāng)然還有很多其他初始化任務(wù)。這里我們來(lái)重點(diǎn)看一下Listener監(jiān)聽(tīng)注冊(cè)的初始化。
? 這里我們重點(diǎn)看下setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));,由getSpringFactoriesInstances進(jìn)去可以看到
?這里的names就是收集了初始化后的ApplicationListener的所有實(shí)現(xiàn)的監(jiān)聽(tīng)器,具體有10個(gè)如下圖:
?接下來(lái)給大家再往下具體分析這個(gè)監(jiān)聽(tīng)器是如何被初始化的,進(jìn)入SpringFactoriesLoader.loadFactoryNames(type, classLoader)中如下
然后再進(jìn)入loadSpringFactories方法,如下:
private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {//這里將會(huì)最終存放所有初始化后的監(jiān)聽(tīng)器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”,這個(gè)地方往下就會(huì)涉及到Spring的自動(dòng)裝配SPI機(jī)制了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());}}} //這里將會(huì)吧所有的初始化的監(jiān)聽(tīng)對(duì)象放入緩存中cache.put(classLoader, result);return result;}catch (IOException ex) {throw new IllegalArgumentException("Unable to load factories from location [" +FACTORIES_RESOURCE_LOCATION + "]", ex);}}那么上面的spring.factories文件是在哪里的呢,通過(guò)調(diào)試得知如下圖
由此可知我們可以按照這個(gè)path找出這個(gè)文件
? ?現(xiàn)在讓我們來(lái)看下這個(gè)文件到底張什么樣的,我們打開(kāi)這個(gè)文章一起來(lái)看下
# PropertySource Loadersorg.springframework.boot.env.PropertySourceLoader=\org.springframework.boot.env.PropertiesPropertySourceLoader,\org.springframework.boot.env.YamlPropertySourceLoader# Run Listenersorg.springframework.boot.SpringApplicationRunListener=\org.springframework.boot.context.event.EventPublishingRunListener# Error Reportersorg.springframework.boot.SpringBootExceptionReporter=\org.springframework.boot.diagnostics.FailureAnalyzers# Application Context Initializersorg.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 Listenersorg.springframework.context.ApplicationListener=\ //以下都是ApplicatinListener的實(shí)現(xiàn)類,這些類都將會(huì)在上面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 Processorsorg.springframework.boot.env.EnvironmentPostProcessor=\org.springframework.boot.cloud.CloudFoundryVcapEnvironmentPostProcessor,\org.springframework.boot.env.SpringApplicationJsonEnvironmentPostProcessor,\org.springframework.boot.env.SystemEnvironmentPropertySourceEnvironmentPostProcessor# Failure Analyzersorg.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# FailureAnalysisReportersorg.springframework.boot.diagnostics.FailureAnalysisReporter=\org.springframework.boot.diagnostics.LoggingFailureAnalysisReporter這里我們來(lái)具體看下ApplictionListener加載的那些類,因?yàn)檫@些跟我們接下來(lái)要講的CnfigFileListener會(huì)有關(guān)系。其實(shí)很簡(jiǎn)單,在MultiValueMap<String, String> result = cache.get(classLoader);的結(jié)果集別刻意看出,因?yàn)樽罱K初始化的所有對(duì)象都將add到這個(gè)result中去。看下結(jié)果圖如下:
那么有了以上的基礎(chǔ)我們可以自己定義一些事件,讓容器在初始化spring容器之前為我們做些事情:
我們嘗試自己創(chuàng)建實(shí)現(xiàn)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 {@Overridepublic void onApplicationEvent(ApplicationEvent event) { // ApplicationStartingEvent//啟動(dòng)開(kāi)始的時(shí)候執(zhí)行的事件 // ApplicationEnvironmentPreparedEvent//上下文創(chuàng)建之前運(yùn)行的事件 // ApplicationContextInitializedEvent// // ApplicationPreparedEvent//上下文創(chuàng)建完成,注入的bean還沒(méi)加載完成 // ContextRefreshedEvent//上下文刷新 // ServletWebServerInitializedEvent//web服務(wù)器初始化 // ApplicationStartedEvent// // ApplicationReadyEvent//啟動(dòng)成功 // ApplicationFailedEvent//在啟動(dòng)Spring發(fā)生異常時(shí)觸發(fā)switch (event.getClass().getSimpleName()){case "ApplicationStartingEvent":System.out.println("啟動(dòng)開(kāi)始的時(shí)候執(zhí)行的事件");break;case "ApplicationEnvironmentPreparedEvent":System.out.println("上下文創(chuàng)建之前運(yùn)行的事件");break;case "ApplicationContextInitializedEvent":System.out.println("上下文初始化");break;case "ApplicationPreparedEvent":System.out.println("上下文創(chuàng)建完成,注入的bean還沒(méi)加載完成");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("啟動(dòng)成功");break;case "ApplicationFailedEvent":break;}} }也可以單獨(dú)去實(shí)現(xiàn)其中一個(gè)監(jiān)聽(tīng)事件,來(lái)解決業(yè)務(wù)具體操作
import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationListener; import org.springframework.context.event.ContextRefreshedEvent;public class MyListenerv2 implements ApplicationListener<ContextRefreshedEvent> {@Overridepublic void onApplicationEvent(ContextRefreshedEvent contextRefreshedEvent) {System.out.println(contextRefreshedEvent);}主程序這邊可以添加多個(gè)自定義監(jiān)聽(tīng)順序自上而下
SpringApplication app = new SpringApplication(Demov1Application.class);app.addListeners(new MyListenery());app.addListeners(new MyListenerv2());app.run(args);springboot支持的事件類型如下:
- ApplicationFailedEvent:該事件在springboot啟動(dòng)失敗是調(diào)用
- ApplicationPreparedEvent:上下文context準(zhǔn)備時(shí)觸發(fā)
- ApplicationReadyEvent:上下文已經(jīng)準(zhǔn)備完畢的時(shí)候觸發(fā)
- ApplicationStartedEvent:spring boot 啟動(dòng)監(jiān)聽(tīng)類
- SpringApplicationEvent:獲取SpringApplication
- ApplicationEnvironmentPreparedEvent:環(huán)境事先準(zhǔn)備
哪些場(chǎng)景會(huì)用到
1.啟動(dòng)前環(huán)境檢測(cè)?
2.啟動(dòng)時(shí)配置初始化?
3.啟動(dòng)后數(shù)據(jù)初始化?
...
應(yīng)用的場(chǎng)景很多,可以發(fā)揮我們的想象。
我們還看到springboot啟動(dòng)是還有很多其它很多bean也可以實(shí)現(xiàn)類是的監(jiān)聽(tīng)功能,在事件發(fā)生的時(shí)候
事件回調(diào)機(jī)制
代碼樣例
分別對(duì)4個(gè)類繼承
HelloApplicationContextInitializer HelloApplicationRunner @Component public class HelloApplicationRunner implements ApplicationRunner {@Overridepublic void run(ApplicationArguments args) throws Exception {System.out.println("運(yùn)行ApplicationRunner:ApplicationRunner...run....");} }?HelloCommandLineRunner類
@Component public class HelloCommandLineRunner implements CommandLineRunner {@Overridepublic void run(String... args) throws Exception {System.out.println("運(yùn)行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 {//必須有的構(gòu)造器public HelloSpringApplicationRunListener(SpringApplication application, String[] args){}@Overridepublic void starting() {System.out.println("運(yùn)行RunListener:SpringApplicationRunListener...starting...");}@Overridepublic void environmentPrepared(ConfigurableEnvironment environment) {Object o = environment.getSystemProperties().get("os.name");System.out.println("獲取系統(tǒng)環(huán)境:SpringApplicationRunListener...environmentPrepared.."+o);}@Overridepublic void contextPrepared(ConfigurableApplicationContext context) {System.out.println("SpringApplicationRunListener...contextPrepared...");}@Overridepublic void contextLoaded(ConfigurableApplicationContext context) {System.out.println("SpringApplicationRunListener...contextLoaded...");}@Overridepublic void started(ConfigurableApplicationContext context) {}@Overridepublic void running(ConfigurableApplicationContext context) {}@Overridepublic void failed(ConfigurableApplicationContext context, Throwable exception) {}}注意上面2個(gè)類需要META-INF/spring.factories添加如下配置才能生效
org.springframework.context.ApplicationContextInitializer=\ com.limp.listerner.HelloApplicationContextInitializerorg.springframework.boot.SpringApplicationRunListener=\ com.limp.listerner.HelloSpringApplicationRunListene開(kāi)始測(cè)試
啟動(dòng)應(yīng)用...運(yùn)行結(jié)果如下
運(yùn)行RunListener:SpringApplicationRunListener...starting... 獲取系統(tǒng)環(huán)境:SpringApplicationRunListener...environmentPrepared..Windows 10 ..... 運(yùn)行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運(yùn)行ApplicationRunner:ApplicationRunner...run.... 運(yùn)行LineRunner:CommandLineRunner...run...[]參考文獻(xiàn):
SpringBoot啟動(dòng)及配置文件加載原理分析:?https://www.cnblogs.com/dszazhy/p/11513012.html
如何自定義啟動(dòng)監(jiān)聽(tīng):https://blog.csdn.net/zzhuan_1/article/details/85312053
原理是什么?https://www.cnblogs.com/dszazhy/p/11513012.html
?
總結(jié)
以上是生活随笔為你收集整理的SpringBoot事件与监听机制的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 网络调试指令ping、telnet、cu
- 下一篇: SpringAOP中通过JoinPoin