##在spring cloud启动方法中,发现listeners.starting()后,try里的方法环境配置等依次循环执行了多次,事实上是触发了不同事件的onApplicationEvent方法,来看下这些依次怎么个顺序以及发生了什么事
public ConfigurableApplicationContext run(String... args) {
...
SpringApplicationRunListeners listeners = this.getRunListeners(args);
listeners.starting();
Collection exceptionReporters;
try {
ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
ConfigurableEnvironment environment = this.prepareEnvironment(listeners, applicationArguments);
this.configureIgnoreBeanInfo(environment);
...
}
点入断点进入,第一个是
第一个是org.springframework.boot.context.logging.LoggingApplicationListener
查看其事件方法
/**
* 容器启动的时的事件
* @param event
*/
private void onApplicationStartingEvent(ApplicationStartingEvent event) {
// 获得loggingSystem对象
this.loggingSystem = LoggingSystem
.get(event.getSpringApplication().getClassLoader());
// 进行日志初始化
this.loggingSystem.beforeInitialize();
}
作用是:在容器启动的时候,根据容器的类加载器,初始化日志系统,然后执行日志系统的第一步初始化。
接来下是BackgroundPreinitializer:对于一些耗时的任务使用一个后台线程尽早触发它们开始执行初始化,这是Springboot的缺省行为。这些初始化动作也可以叫做预初始化。 设置系统属性spring.backgroundpreinitializer.ignore为true可以禁用该机制。 该机制被禁用时,相应的初始化任务会发生在前台线程。
@Override
public void onApplicationEvent(SpringApplicationEvent event) {
if (event instanceof ApplicationStartingEvent
&& preinitializationStarted.compareAndSet(false, true)) {
//如果当前事件是ApplicationStartingEvent,并且预初始化任务尚未执行
// 则 :将preinitializationStarted设置为预初始化任务开始执行;
// 开始执行预初始化任务;
performPreinitialization();
}
if ((event instanceof ApplicationReadyEvent
|| event instanceof ApplicationFailedEvent)
&& preinitializationStarted.get()) {
try {
preinitializationComplete.await();
}
catch (InterruptedException ex) {
Thread.currentThread().interrupt();
}
}
}
private void performPreinitialization() {
try {
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
runSafely(new ConversionServiceInitializer());
runSafely(new ValidationInitializer());
runSafely(new MessageConverterInitializer());
runSafely(new MBeanFactoryInitializer());
runSafely(new JacksonInitializer());
runSafely(new CharsetInitializer());
preinitializationComplete.countDown();
}
public void runSafely(Runnable runnable) {
try {
runnable.run();
}
catch (Throwable ex) {
// Ignore
}
}
}, "background-preinit");
thread.start();
}
catch (Exception ex) {
// This will fail on GAE where creating threads is prohibited. We can safely
// continue but startup will be slightly slower as the initialization will now
// happen on the main thread.
preinitializationComplete.countDown();
}
}
接着是DelegatingApplicationListener:委派监听器,委派给那些在环境属性context.listener.classes指定的那些监听器。
它本身没有实质性的处理某事件,而是在应用环境准备就绪事件发生时,通过环境中的配置的context.listener.classes,去搜集相应的监听器。如果收集到,就会创建一个简单事件广播器实例,放到类属性上,同时,还会把收集到的监听器,绑定到该广播器上。
该监听器的另一个特性时,无论发生何事件,只要广播器实例不为空,就利用该广播器广播该事件。
也就是说,这个监听器,使得我们不需要在spring.factories文件中作文章,而是通过context.listener.classes这个配置,就可以使得我们自定义的监听器发挥作用。
@Override
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);
}
}
接下来是LiquibaseServiceLocatorApplicationListener:iquibase是一个用于数据库重构和迁移的开源工具,通过日志文件的形式记录数据库的变更,然后执行日志文件中的修改,将数据库更新或回滚到一致的状态。 它的目标是提供一种数据库类型无关的解决方案,通过执行schema类型的文件来达到迁移。 在我们日常的开发中数据库的变化非常频繁,维护sql脚本的次数也比较多,经常遇到的情况是,同事A新增了一个张表,或者为某个表增加了一个字段,在本地开发测试完成后,升级到生产系统。由于忘记同步数据库,则服务不能正常使用。如果公司有单独的DBA去维护这个sql那自然没啥问题,这个场景下对于表结构的维护就达不到自动化运维效果了。
好了,终于开始执行下一步了:ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
ConfigurableEnvironment environment = this.prepareEnvironment(listeners, applicationArguments);
好的,你会发现listner为什么执行到一半就回到主步骤了呢,答案见晓:lisnter.start所触发的事件只有以上4个,而prepareEnvironment会接着触发prepareEnvironmentEvent事件。事件执行时获取的的ApplicationListener是来源于this.retrieverCache,而该属性下有map包含了俩组ApplicationListener(listener.start事件),前面看到执行的便是其中一组,执行完后再去执行第二组的ApplicationListener(prepareEnvironment事件),而在这个第2组事件,有一个BootstrapApplicationListener,我们往下看
依次:
BootstrapApplicationListener:加载Bootstrap容器所需要的的环境类配置以及配置类。具体执行内容请看我的另一篇博客spring cloud,sping boot,微服务,tomcat容器关系及创建执行顺序详解其大致处理就是加载过程会重新触发run方法,再启动一遍run源码,所以又重新回到了主步骤。
LoggingSystemShutdown:应该是日志框架的初始化操作,没太弄懂,保留意见
ConfigFileApplicationListene:主要作用就是读取应用的配置文件并add到Environment的PropertySources列表里
AnsiOutputApplicationListener,Ansi输出应用监听器。该监听器的作用是,当收到应用环境准备就绪事件时,对Ansi输出的相关状态进行设置,并绑定到应用环境中。
LoggingApplicationListener:日志的默认配置环境加载等
ClassPathLoggingApplicationListener :日志相关
中间还有backgroundpreinitializerApplicationListener
和DelegatingApplicationListener,这里应该会发现执行过而跳过
FileEncodingApplicationListener,文件编码应用监听器。该监听器实质作用是在收到应用环境准备就绪事件时,对配置中关于文件编码的配置作一个校验,判断配置中的文件编码是否和JVM系统的file.encoding一致。无配置或JVM系统的file.encoding无效的情况下,都不会报异常,但是,当JVM中的file.encoding有效,且在配置中包含了spring.mandatory-file-encoding,而二者又不一致时,就会报IllegalStateException异常。