上一篇: Spring Developer Tools 源码分析:四、类加载器 。
本篇是下一篇 Restarter 的前置内容,这里介绍的 ApplicationListener 事件触发过程是针对整个 Spring Boot 的过程。
Spring Developer Tools 通过 ApplicationListener
不同阶段的事件来控制 Restarter
的运行。 Restarter
包含了获取 main
方法类,初始化监控资源路径等功能。
devtools 项目的 "META-INF/spring.factories"
文件中,和这里相关的配置如下:
# Application Listeners org.springframework.context.ApplicationListener=/ org.springframework.boot.devtools.restart.RestartApplicationListener
RestartApplicationListener
类定义如下(使用了最高的优先级):
public class RestartApplicationListener implements ApplicationListener<ApplicationEvent>, Ordered {
接口实现方法如下:
@Override public void onApplicationEvent(ApplicationEvent event) { if (event instanceof ApplicationStartingEvent) { onApplicationStartingEvent((ApplicationStartingEvent) event); } if (event instanceof ApplicationPreparedEvent) { onApplicationPreparedEvent((ApplicationPreparedEvent) event); } if (event instanceof ApplicationReadyEvent || event instanceof ApplicationFailedEvent) { Restarter.getInstance().finish(); } if (event instanceof ApplicationFailedEvent) { onApplicationFailedEvent((ApplicationFailedEvent) event); } }
在这里可以看到 RestartApplicationListener
会监控 4 类事件,分别如下:
ApplicationStartingEvent
启动时,这个事件会尽可能早的触发。 ApplicationPreparedEvent
: ApplicationContext
已完全准备但未刷新(refresh) 时发布的事件。bean 的定义将会被加载,并且 Environment
已经可以在这个阶段使用了。 ApplicationReadyEvent
已经准备好提供服务。 ApplicationFailedEvent
启动失败。 下面用尽可能短的代码和语言来说明这些事件的触发过程。
在 SpringApplication
构造方法中,有如下代码:
public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) { //省略其他 setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class)); //省略其他 }
这里会从 "META-INF/spring.factories"
文件夹获取所有 ApplicationListener
类的实现,由于 Spring 还没启动,所以这里只能通过读取资源文件来获取一些启动更早的接口。获取所有的实现接口后,会 set
到当前类的 listeners
中。
在这一步,就能获取到 RestartApplicationListener
。
接下来是 SpringApplication
的 run
方法。
public ConfigurableApplicationContext run(String... args) { //省略部分 SpringApplicationRunListeners listeners = getRunListeners(args);//1 listeners.starting();//ApplicationStartingEvent - ① try { //省略 ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);//ApplicationEnvironmentPreparedEvent //省略 context = createApplicationContext(); //省略 prepareContext(context, environment, listeners, applicationArguments, printedBanner);//ApplicationPreparedEvent - ② refreshContext(context); //省略 listeners.started(context);//ApplicationStartedEvent //省略 } catch (Throwable ex) { handleRunFailure(context, ex, exceptionReporters, listeners);//ApplicationFailedEvent - ④ throw new IllegalStateException(ex); } try { listeners.running(context);//ApplicationReadyEvent - ③ } //省略 return context; }
上面代码只留下了和 listeners
相关的主干代码。你可以发现这里都是和 SpringApplicationRunListeners
有关的代码,和前面提到的 ApplicationListener
不是一个接口,这之间是什么关系呢。
在 //1
处,通过 getRunListeners
获取的 SpringApplicationRunListeners
(代理类),从这个方法来看,这里也是从 spring-boot 项目下的 "META-INF/spring.factories"
获取的所有的 SpringApplicationRunListener
实现类。在这个配置文件中有下面的配置:
# Run Listeners org.springframework.boot.SpringApplicationRunListener=/ org.springframework.boot.context.event.EventPublishingRunListener
在 SpringApplicationRunListeners
中会实际执行所有 runlistener 的方法。在 EventPublishingRunListener
中,会通过 SpringApplication.getListeners()
获取一开始的 listener
,在后续触发事件时会执行。除此之外还要特别注意 EventPublishingRunListener
中的 contextLoaded
方法,代码如下:
@Override public void contextLoaded(ConfigurableApplicationContext context) { for (ApplicationListener<?> listener : this.application.getListeners()) { if (listener instanceof ApplicationContextAware) { ((ApplicationContextAware) listener).setApplicationContext(context); } context.addApplicationListener(listener); } this.initialMulticaster.multicastEvent( new ApplicationPreparedEvent(this.application, this.args, context)); }
这里有两个特殊处理,首先如果实现了 ApplicationContextAware
接口,就会在此时去调用该方法。还有就是会把 listener
添加到已经创建的 context
中,添加到 context
是非常重要的一个步骤。 EventPublishingRunListener
只会在 Spring Boot 启动过程中处理事件,当 run
方法执行完毕后,后续的事件都是在 context
范围内的,这里加入 context
后,就能处理其他感兴趣的事件了。
前面自动配置中介绍的 @EventListener
会在所有 单例
的 bean 创建完成后,通过 EventListenerMethodProcessor
处理注解形式的事件监听,这些方法也都会通过代理转换为 ApplicationListener
,然后加入到 context
中。
具体哪个阶段触发哪个事件,大家可以根据上面的主干代码去细看(代码注释已经标明了会触发的事件),下一篇回到 Restarter
继续。