关联博文:
Spring中事件监听(通知)机制详解与实践SpringBoot中事件广播体系梳理SpringBoot启动过程广播的事件有什么作用?
前面我们分析了SpringBoot的启动流程,其广播了诸多事件,本文我们尝试总结一下这些事件所带来的影响。
事件梳理
在启动过程中主要广播了如下事件:
- listeners.starting()方法广播
ApplicationStartingEvent
事件 - environmentPrepared()方法广播
ApplicationEnvironmentPreparedEvent
事件 - contextPrepared()方法广播
ApplicationContextInitializedEvent
事件 - contextLoaded()方法广播
ApplicationPreparedEvent
事件 -
AbstractApplicationContext
的finishRefresh()方法广播ContextRefreshedEvent
事件 -
ServletWebServerApplicationContext
的finishRefresh()方法startWebServer后会广播ServletWebServerInitializedEvent
事件 - listeners.started()方法广播
ApplicationStartedEvent
事件 - listeners.running()方法广播
ApplicationReadyEvent
事件
如下七种事件均是SpringApplicationEvent事件体系。
- ApplicationStartingEvent:应用启动事件
- ApplicationEnvironmentPreparedEvent:准备环境事件
- ApplicationContextInitializedEvent:应用初始化事件
- ApplicationPreparedEvent:应用准备好事件
- ApplicationStartedEvent:应用启动事件
- ApplicationReadyEvent:应用就绪事件
- ApplicationFailedEvent:应用失败事件
environmentPrepared方法在创建并配置环境的过程中,contextPrepared()和contextLoaded()则是在prepareContext过程中。
其中如下三个在SpringApplication的run方法主流程中:
- listeners.starting();
- listeners.started(context);
- listeners.running(context);
如下四种是ApplicationContextEvent 体系:
- ContextRefreshedEvent (应用刷新事件)
- ContextStartedEvent (应用启动事件)
- ContextClosedEvent (应用关闭事件)
- ContextStoppedEvent (应用停止事件)
事件 | 感兴趣的监听器 | 有用的监听器 |
ApplicationStartingEvent | RestartApplicationListener LoggingApplicationListener BackgroundPreinitializer DelegatingApplicationListener LiquibaseServiceLocatorApplicationListener | RestartApplicationListener LoggingApplicationListener BackgroundPreinitializer |
ApplicationEnvironmentPreparedEvent | RestartApplicationListener ConfigFileApplicationListener AnsiOutputApplicationListener LoggingApplicationListener BackgroundPreinitializer ClasspathLoggingApplicationListener DelegatingApplicationListener FileEncodingApplicationListener | ConfigFileApplicationListener AnsiOutputApplicationListener LoggingApplicationListener ClasspathLoggingApplicationListener FileEncodingApplicationListener |
ApplicationContextInitializedEvent | RestartApplicationListener BackgroundPreinitializer DelegatingApplicationListener | |
ApplicationPreparedEvent | RestartApplicationListener CloudFoundryVcapEnvironmentPostProcessor ConfigFileApplicationListener LoggingApplicationListener BackgroundPreinitializer DelegatingApplicationListener DevToolsLogFactory$Listener | RestartApplicationListener CloudFoundryVcapEnvironmentPostProcessor ConfigFileApplicationListener LoggingApplicationListener DevToolsLogFactory$Listener |
ServletWebServerInitializedEvent | RestartApplicationListener SpringApplicationAdminMXBeanRegistrar DelegatingApplicationListener ServerPortInfoApplicationContextInitializer | SpringApplicationAdminMXBeanRegistrar ServerPortInfoApplicationContextInitializer |
ApplicationStartedEvent | RestartApplicationListener BackgroundPreinitializer DelegatingApplicationListener | |
ApplicationReadyEvent | RestartApplicationListener SpringApplicationAdminMXBeanRegistrar BackgroundPreinitializer DelegatingApplicationListener ConditionEvaluationDeltaLoggingListener | RestartApplicationListener SpringApplicationAdminMXBeanRegistrar BackgroundPreinitializer ConditionEvaluationDeltaLoggingListener |
【1】ApplicationStartingEvent
对该事件感兴趣的监听器有如下五个:
0 = {RestartApplicationListener@1953}
1 = {LoggingApplicationListener@1954}
2 = {BackgroundPreinitializer@1955}
3 = {DelegatingApplicationListener@1956}
4 = {LiquibaseServiceLocatorApplicationListener@1957}
① RestartApplicationListener
其对ApplicationStartingEvent的处理则是实例化Restarter。
② LoggingApplicationListener
如下所示,初始化loggingSystem ,本文这里获取的是LogbackLoggingSystem。beforeInitialize这里会对日志适配器、桥接器进行处理。
private void onApplicationStartingEvent(ApplicationStartingEvent event) {
this.loggingSystem = LoggingSystem.get(event.getSpringApplication().getClassLoader());
this.loggingSystem.beforeInitialize();
}
③ BackgroundPreinitializer
如果是ApplicationStartingEvent事件时,这个玩意会尝试以多线程方式进行一些"基础设置服务类"
的预先配置。
具体信息可以参考博文:SpringBoot中事件广播体系梳理,这里不再赘述。
④ DelegatingApplicationListener
如下所示,当ApplicationStartingEvent事件过来时,这个监听器并无作为。
public void onApplicationEvent(ApplicationEvent event) {
if (event instanceof ApplicationEnvironmentPreparedEvent) {
List<ApplicationListener<ApplicationEvent>> delegates = getListeners(
((ApplicationEnvironmentPreparedEvent) event).getEnvironment());
if (delegates.isEmpty()) {
return;
}
this.multicaster = new SimpleApplicationEventMulticaster();
for (ApplicationListener<ApplicationEvent> listener : delegates) {
this.multicaster.addApplicationListener(listener);
}
}
if (this.multicaster != null) {
this.multicaster.multicastEvent(event);
}
}
其getListeners方法则是尝试从环境中获取属性context.listener.classes
对应的监听器配置并见实例化。
⑤ LiquibaseServiceLocatorApplicationListener
Liquibase是一个用于跟踪、管理和应用数据库变化的开源的数据库重构工具。它将所有数据库的变化(包括结构和数据)都保存在XML文件中,便于版本控制。本文这里该监听器毫无作为。
@Override
public void onApplicationEvent(ApplicationStartingEvent event) {
if (ClassUtils.isPresent("liquibase.servicelocator.CustomResolverServiceLocator",
event.getSpringApplication().getClassLoader())) {
new LiquibasePresent().replaceServiceLocator();
}
}
【2】ApplicationEnvironmentPreparedEvent
对该事件感兴趣的监听器有如下五个:
0 = {RestartApplicationListener@2009}
1 = {ConfigFileApplicationListener@2419}
2 = {AnsiOutputApplicationListener@2420}
3 = {LoggingApplicationListener@2013}
4 = {BackgroundPreinitializer@2421}
5 = {ClasspathLoggingApplicationListener@2422}
6 = {DelegatingApplicationListener@2084}
7 = {FileEncodingApplicationListener@2423}
① RestartApplicationListener、BackgroundPreinitializer
其并没有对ApplicationEnvironmentPreparedEvent事件做处理。
② ConfigFileApplicationListener
获取配置的EnvironmentPostProcessor,添加当前实例后,遍历触发每个EnvironmentPostProcessor 的postProcessEnvironment方法。
private void onApplicationEnvironmentPreparedEvent(ApplicationEnvironmentPreparedEvent event) {
List<EnvironmentPostProcessor> postProcessors = loadPostProcessors();
postProcessors.add(this);
AnnotationAwareOrderComparator.sort(postProcessors);
for (EnvironmentPostProcessor postProcessor : postProcessors) {
postProcessor.postProcessEnvironment(event.getEnvironment(), event.getSpringApplication());
}
}
这里获取的EnvironmentPostProcessor 有如下8个:
0 = {SystemEnvironmentPropertySourceEnvironmentPostProcessor@2463}
1 = {SpringApplicationJsonEnvironmentPostProcessor@2464}
2 = {CloudFoundryVcapEnvironmentPostProcessor@2465}
3 = {ConfigFileApplicationListener@2419}
4 = {SafetyEncryptProcessor@2466}
5 = {DevToolsHomePropertiesPostProcessor@2467}
6 = {DevToolsPropertyDefaultsPostProcessor@2468}
7 = {DebugAgentEnvironmentPostProcessor@2469}
③ AnsiOutputApplicationListener
该监听器是根据spring.output.ansi.enabled
参数配置AnsiOutput
@Override
public void onApplicationEvent(ApplicationEnvironmentPreparedEvent event) {
ConfigurableEnvironment environment = event.getEnvironment();
Binder.get(environment).bind("spring.output.ansi.enabled", AnsiOutput.Enabled.class)
.ifBound(AnsiOutput::setEnabled);
AnsiOutput.setConsoleAvailable(environment.getProperty("spring.output.ansi.console-available", Boolean.class));
}
④ LoggingApplicationListener
如下所示,判断并获取loggingSystem,然后进行日志的初始化任务,比如设置日志级别。
private void onApplicationEnvironmentPreparedEvent(ApplicationEnvironmentPreparedEvent event) {
if (this.loggingSystem == null) {
this.loggingSystem = LoggingSystem.get(event.getSpringApplication().getClassLoader());
}
initialize(event.getEnvironment(), event.getSpringApplication().getClassLoader());
}
protected void initialize(ConfigurableEnvironment environment, ClassLoader classLoader) {
new LoggingSystemProperties(environment).apply();
this.logFile = LogFile.get(environment);
if (this.logFile != null) {
this.logFile.applyToSystemProperties();
}
this.loggerGroups = new LoggerGroups(DEFAULT_GROUP_LOGGERS);
initializeEarlyLoggingLevel(environment);
initializeSystem(environment, this.loggingSystem, this.logFile);
initializeFinalLoggingLevels(environment, this.loggingSystem);
registerShutdownHookIfNecessary(environment, this.loggingSystem);
}
⑤ ClasspathLoggingApplicationListener
其对ApplicationEnvironmentPreparedEvent
和ApplicationFailedEvent
事件感兴趣,用来打印线程上下文类加载器( the thread context class loader (TCCL))的classpath,在debug 级别。
⑥ DelegatingApplicationListener
虽然这个监听器对ApplicationEnvironmentPreparedEvent
感兴趣,但是需要配置了context.listener.classes
才可以起作用,否则这个监听器并无作为。
如果配置了context.listener.classes
,那么将会将事件(ApplicationEvent类型)广播给这些监听器。也就是说在application.yml
或者在application.properties
配置文件中通过context.listener.classes
配置监听类,但是需要注意,这种配置无非监听ApplicationStartingEvent事件。
⑦ FileEncodingApplicationListener
如果配置了spring.mandatory-file-encoding
那么将会比较encoding 是否与desired 相等,不相等则抛出异常。
如果没有配置spring.mandatory-file-encoding
,直接返回。
String encoding = System.getProperty("file.encoding");
String desired = environment.getProperty("spring.mandatory-file-encoding");
【3】ApplicationContextInitializedEvent
准备应用上下文时会广播该事件,对该事件感兴趣的监听器如下:
0 = {RestartApplicationListener@2006}
1 = {BackgroundPreinitializer@2403}
2 = {DelegatingApplicationListener@2082}
① RestartApplicationListener、BackgroundPreinitializer
其不会对ApplicationContextInitializedEvent事件感兴趣。
② DelegatingApplicationListener
这个监听器前面已经讲过,本文这里该监听器毫无作为。所以也就是说,这个事件广播,通常是没有作用的。
【4】ApplicationPreparedEvent
也就是说上下文已经准备好了,对该事件感兴趣的监听器如下:
0 = {RestartApplicationListener@2006}
1 = {CloudFoundryVcapEnvironmentPostProcessor@3730}
2 = {ConfigFileApplicationListener@2401}
3 = {LoggingApplicationListener@2014}
4 = {BackgroundPreinitializer@2403}
5 = {DelegatingApplicationListener@2082}
6 = {DevToolsLogFactory$Listener@3731}
① RestartApplicationListener
该监听器会触发Restarter.getInstance().prepare(event.getApplicationContext());
,最终会为当前应用上下文设置ResourceLoader(ClassLoaderFilesResourcePatternResolver)
,并将当前应用上下文添加到Restarter实例的List<ConfigurableApplicationContext> rootContexts
中。
② CloudFoundryVcapEnvironmentPostProcessor
这个监听器的响应事件方法如下所示,只是转换了logger的所属。该监听器侧重于作为一个EnvironmentPostProcessor
使用,在ConfigFileApplicationListener
监器的onApplicationEnvironmentPreparedEvent
方法中会触发CloudFoundryVcapEnvironmentPostProcessor
的postProcessEnvironment
方法。
@Override
public void onApplicationEvent(ApplicationPreparedEvent event) {
logger.switchTo(CloudFoundryVcapEnvironmentPostProcessor.class);
}
③ ConfigFileApplicationListener
如下所示,当ApplicationPreparedEvent
事件广播时,该监听器会将PropertySourceOrderingPostProcessor
添加到应用上下文的List<BeanFactoryPostProcessor> beanFactoryPostProcessors
集合中。
private void onApplicationPreparedEvent(ApplicationEvent event) {
this.logger.switchTo(ConfigFileApplicationListener.class);
addPostProcessors(((ApplicationPreparedEvent) event).getApplicationContext());
}
protected void addPostProcessors(ConfigurableApplicationContext context) {
context.addBeanFactoryPostProcessor(new PropertySourceOrderingPostProcessor(context));
}
④ LoggingApplicationListener
LoggingApplicationListener
如下所示,这里会注册日志相关的一些bean。
private void onApplicationPreparedEvent(ApplicationPreparedEvent event) {
ConfigurableListableBeanFactory beanFactory = event.getApplicationContext().getBeanFactory();
// springBootLoggingSystem
if (!beanFactory.containsBean(LOGGING_SYSTEM_BEAN_NAME)) {
beanFactory.registerSingleton(LOGGING_SYSTEM_BEAN_NAME, this.loggingSystem);
}
// springBootLogFile
if (this.logFile != null && !beanFactory.containsBean(LOG_FILE_BEAN_NAME)) {
// this.logFile指向了我们的日志文件如./recommend_logs
beanFactory.registerSingleton(LOG_FILE_BEAN_NAME, this.logFile);
}
// springBootLoggerGroups
if (this.loggerGroups != null && !beanFactory.containsBean(LOGGER_GROUPS_BEAN_NAME)) {
beanFactory.registerSingleton(LOGGER_GROUPS_BEAN_NAME, this.loggerGroups);
}
}
⑤ DevToolsLogFactory$Listener
从延迟日志记录切换到立即日志记录到指定目标。
BackgroundPreinitializer
、DelegatingApplicationListener
(本文)其对ApplicationPreparedEvent
事件并无作用。
【5】ContextRefreshedEvent
该事件在finishRefresh方法会广播出来,对其感兴趣的监听器如下所示:
0 = {RestartApplicationListener@2006}
1 = {DelegatingApplicationListener@2082}
2 = {LocalDevToolsAutoConfiguration$LiveReloadServerEventListener@9352}
3 = {ConditionEvaluationReportLoggingListener$ConditionEvaluationReportListener@9353}
4 = {ClearCachesApplicationListener@9354}
5 = {SharedMetadataReaderFactoryContextInitializer$SharedMetadataReaderFactoryBean@9355}
6 = {PluginRegistryFactoryBean@9356}
7 = {PluginRegistryFactoryBean@9357}
8 = {PluginRegistryFactoryBean@9358}
9 = {PluginRegistryFactoryBean@9359}
10 = {PluginRegistryFactoryBean@9360}
11 = {PluginRegistryFactoryBean@9361}
12 = {PluginRegistryFactoryBean@9362}
13 = {PluginRegistryFactoryBean@9363}
14 = {PluginRegistryFactoryBean@9364}
15 = {PluginRegistryFactoryBean@9365}
16 = {PluginRegistryFactoryBean@9366}
17 = {PluginRegistryFactoryBean@9367}
18 = {PluginRegistryFactoryBean@9368}
19 = {PluginRegistryFactoryBean@9369}
20 = {PluginRegistryFactoryBean@9370}
21 = {PluginRegistryFactoryBean@9371}
22 = {PluginRegistryFactoryBean@9372}
23 = {ResourceUrlProvider@9373}
24 = {PluginRegistryFactoryBean@9374}
25 = {DelegatingApplicationListener@7876}
可以看到这里有很多PluginRegistryFactoryBean,实例如下所示,这里我们暂不分析。
【6】ServletWebServerInitializedEvent
finishRefresh()
方法startWebServer
后会广播 ServletWebServerInitializedEvent
事件。对该事件感兴趣的监听器如下:
0 = {RestartApplicationListener@2006}
1 = {SpringApplicationAdminMXBeanRegistrar@9498}
2 = {DelegatingApplicationListener@2082}
3 = {ServerPortInfoApplicationContextInitializer@9499}
4 = {DelegatingApplicationListener@7876}
RestartApplicationListener、DelegatingApplicationListener本文这里对该事件毫无作用。
① SpringApplicationAdminMXBeanRegistrar
设置SpringApplicationAdminMXBeanRegistrar.embeddedWebApplication
为true。
② ServerPortInfoApplicationContextInitializer
如下所示,为环境设置属性-值。默认情况属性是local.server.port
。
@Override
public void onApplicationEvent(WebServerInitializedEvent event) {
String propertyName = "local." + getName(event.getApplicationContext()) + ".port";
setPortProperty(event.getApplicationContext(), propertyName, event.getWebServer().getPort());
}
【7】ApplicationStartedEvent
对该事件感兴趣的监听器如下所示:
0 = {RestartApplicationListener@2006}
1 = {BackgroundPreinitializer@2403}
2 = {DelegatingApplicationListener@2082}
3 = {DelegatingApplicationListener@7876}
RestartApplicationListener
、DelegatingApplicationListener
及BackgroundPreinitializer
在本文环境下对该事件毫无作用。
【8】ApplicationReadyEvent
对该事感兴趣的监听器如下所示:
0 = {RestartApplicationListener@2006}
1 = {SpringApplicationAdminMXBeanRegistrar@9635}
2 = {BackgroundPreinitializer@2403}
3 = {DelegatingApplicationListener@2082}
4 = {DelegatingApplicationListener@7876}
5 = {ConditionEvaluationDeltaLoggingListener@9636}
① RestartApplicationListener
触发Restarter.getInstance().finish();
,当源日志延迟时,从源日志重播到目标日志并设置finished标志位true。
this.logger = DeferredLog.replay(this.logger, LogFactory.getLog(getClass()));
this.finished = true;
② SpringApplicationAdminMXBeanRegistrar
对ApplicationReadyEvent事件的响应仅仅是设置了ready标志为true。
this.ready = true;
③ BackgroundPreinitializer
如下所示,当ApplicationStartingEvent事件广播时,默认情况下BackgroundPreinitializer会以后台多线程方式实例化一些"基础设置服务类"
。当ApplicationReadyEvent或者ApplicationFailedEvent事件广播时,如果这里preinitializationStarted为true,那么就除非闭锁preinitializationComplete的等待方法,直到前面提到的多线程执行完。其实通常来讲,这里不会等待,只是一种保障措施。毕竟ApplicationStartingEvent离ApplicationReadyEvent蛮远的。
④ ConditionEvaluationDeltaLoggingListener