SpringBoot启动流程事件源码解析
2020-03-12 本文已影响0人
每天进步一丢儿丢儿
Spring启动流程
启动 => 环境准备阶段(ConfigurableEnvironment) => 上下文准备阶段(ConfigurableApplicationContext) => 上下文加载阶段(ConfigurableApplicationContext) => 启动完成 => 程序运行。
public interface SpringApplicationRunListener {
default void starting() {
}
default void environmentPrepared(ConfigurableEnvironment environment) {
}
default void contextPrepared(ConfigurableApplicationContext context) {
}
default void contextLoaded(ConfigurableApplicationContext context) {
}
default void started(ConfigurableApplicationContext context) {
}
default void running(ConfigurableApplicationContext context) {
}
default void failed(ConfigurableApplicationContext context, Throwable exception) {
}
}
该接口的唯一实现类是:EventPublishingRunListener,其主要作用是用来发布启动过程中对应的各种事件。
SpringBoot启动流程事件发布
1. SpringBoot启动流程与启动事件的对应关系
- starting() 对应 ApplicationStartingEvent
- environmentPrepared() 对应 ApplicationEnvironmentPreparedEvent
- contextPrepared() 对应 ApplicationContextInitializedEvent
- contextLoaded() 对应 ApplicationPreparedEvent
- started() 对应 ApplicationStartedEvent
- running() 对应 ApplicationReadyEvent
- failed() 对应 ApplicationFailedEvent
2. SpringBoot启动事件发布的两种方式
-
通过应用事件广播器发布具体就是SimpleApplicationEventMulticaster类
image.png -
通过ApplicationContext上下文的publishEvent()方法发布
那么为什么启动过程中需要两种不同的事件发布机制呢?那是因为在ApplicationStartedEvent事件以前ApplicationContext还没有准备完成,无法通过ApplicationContext的publishEvent()方法发布启动事件,所以要通过SimpleApplicationEventMulticaster发布事件,具体实现如下:
@Override
public void multicastEvent(ApplicationEvent event) {
multicastEvent(event, resolveDefaultEventType(event));
}
@Override
public void multicastEvent(final ApplicationEvent event, @Nullable ResolvableType eventType) {
ResolvableType type = (eventType != null ? eventType : resolveDefaultEventType(event));
Executor executor = getTaskExecutor();
for (ApplicationListener<?> listener : getApplicationListeners(event, type)) {
if (executor != null) {
executor.execute(() -> invokeListener(listener, event));
}
else {
invokeListener(listener, event);
}
}
}
3. SpringBoot启动流程的细节展示
首先基于源码来看一下SpringApplication调用run()方法后做了那些事情
public ConfigurableApplicationContext run(String... args) {
StopWatch stopWatch = new StopWatch();
stopWatch.start();
ConfigurableApplicationContext context = null;
Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
// 1. 设置java程序运行的服务器环境 、Headless模式是系统的一种配置模式。在该模式下,系统缺少了显示设备、键盘或鼠标
configureHeadlessProperty();
// 2. 获取Spring运行监听器
SpringApplicationRunListeners listeners = getRunListeners(args);
// 3. 发布SpringBoot启动ApplicationStartingEvent事件
listeners.starting();
try {
ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
// 4. SpringBoot启动环境准备阶段,发布ApplicationEnvironmentPreparedEvent事件
ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
configureIgnoreBeanInfo(environment);
Banner printedBanner = printBanner(environment);
// 5. 创建ApplicationContext
context = createApplicationContext();
// 6. 生成ApplicationContext创建失败时的错误报告
exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class,
new Class[] { ConfigurableApplicationContext.class }, context);
// 7. ApplicationContext准备阶段,发布ApplicationContextInitializedEvent事件
prepareContext(context, environment, listeners, applicationArguments, printedBanner);
refreshContext(context);
afterRefresh(context, applicationArguments);
stopWatch.stop();
if (this.logStartupInfo) {
new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
}
//8. 到此SpringBoot的ApplicationContext准备完毕,程序启动。发布ApplicationStartedEvent事件
listeners.started(context);
// 此处的作用是对所有的Runner接口的实现进行调用
callRunners(context, applicationArguments);
}
catch (Throwable ex) {
//9. 发布ApplicationFailedEvent事件,启动失败时会报告失败信息
handleRunFailure(context, ex, exceptionReporters, listeners);
throw new IllegalStateException(ex);
}
try {
//10. 发布ApplicationReadyEvent事件,监听程序运行事件,运行报错时提交错误报告
listeners.running(context);
}
catch (Throwable ex) {
//11. 发布ApplicationFailedEvent事件,运行异常时会报告异常信息
handleRunFailure(context, ex, exceptionReporters, null);
throw new IllegalStateException(ex);
}
return context;
}
以上代码中的注释就是SpringBoot启动过程中各个事件的具体发布情况,其中ApplicationPreparedEvent事件在注释7下面的prepareContext方法的最后两句代码中体现。
image.png
4. SpringBoot启动事件的使用
- ApplicationStartingEvent
- ApplicationEnvironmentPreparedEvent
- ApplicationContextInitializedEvent
- ApplicationPreparedEvent
以上四个启动事件发布的时候由于SpringBoot的IOC容器还未准备完毕,因此想要通过以上事件在系统启动时对系统环境或者应用上下文做一些处理的话,必须通过SpringApplication的addListener方法添加事件,对应的监听器才能生效。
public class MyApplicationEnvironmentPreparedEventListener implements ApplicationListener<ApplicationEnvironmentPreparedEvent> {
@Override
public void onApplicationEvent(ApplicationEnvironmentPreparedEvent event) {
System.out.println("MyApplicationEnvironmentPreparedEventListener: SpringBoot准备应用环境...");
}
}
public class CustomSpringApplication {
public static ConfigurableApplicationContext run(Class source,String... args) {
SpringApplicationBuilder builder = new SpringApplicationBuilder(source);
List<LauncherService> launcherList = new ArrayList<>();
ServiceLoader.load(LauncherService.class).forEach(launcherList::add);
launcherList.forEach(LauncherService::launcher);
// 添加SpringBoot启动事件监听器
builder.listeners(new MyApplicationStartingEventListener(),
new MyApplicationEnvironmentPreparedEventListener(),
new MyApplicationContextInitializedEventListener(),
new MyApplicationPreparedEventListener());
return builder.run(args);
}
}
- ApplicationStartedEvent
- ApplicationReadyEvent
- ApplicationFailedEvent
ApplicationStartedEvent、ApplicationReadyEvent这两个事件发布的时候,SpringBoot运行环境和应用的上下文已经完全准备完毕,因此我们只需要在自定义的事件监听器上添加@Component注解,容器就会自动管理这些监听器。
@Component
public class MyApplicationStartedEventListener implements ApplicationListener<ApplicationStartedEvent> {
private final ServletApplication.ApplicationConfig config;
public MyApplicationStartedEventListener(ServletApplication.ApplicationConfig config) {
this.config = config;
}
@Override
public void onApplicationEvent(ApplicationStartedEvent applicationStartedEvent) {
System.out.println("MyApplicationStartedEventListener: SpringBoot的应用上下文载入完毕...");
config.app();
config.annotations();
}
}