【SpringBoot】【二】 SpringApplicationRunListeners 监听器执行过程详解
1 前言
我们看到 SpringBoot 启动的时候,会在每个时机执行监听器,这节我们就来看看,加载监听器的过程我们就不说了哈,上节说过了哈,本节我们主要看:
(1)SpringApplicationRunListeners 的创建过程
(2)监听器的执行时机有哪些
(3)监听器的执行过程
三个方面来看哈。
2 使用
在看之前,我们先来看看使用体验一下,使用方式上大概可分三种:
(1)spring.factories 配置的
(2)@Component 方式的
(3)@EventListener 方式的
其中方式2、方式3差不多类似都是容器初始化后才能进行的,而方式一是跟着SpringBoot启动的时候就创建出来了,我们来看看。
2.1 走 spring.factories 配置的
首先看下我的配置:
# My Application Listeners
org.springframework.context.ApplicationListener=\
com.kuku.demo.listener.MySpringFactoryApplicationListener,\
com.kuku.demo.listener.MySpringFactoryApplicationListenerWithReadyEvent
# My Run Listeners
org.springframework.boot.SpringApplicationRunListener=com.kuku.demo.listener.MySpringFactoryApplicationRunListener
我配置了两个监听器和一个执行监听器,两个监听器 MySpringFactoryApplicationListener 是范围比较广的,接收的是 ApplicationEvent事件,MySpringFactoryApplicationListenerWithReadyEvent 是只针对某个事件 ApplicationReadyEvent 监听的,说白了就是粒度不同哈。
两个监听器:
/** * @author xjx * @description * 监听 ApplicationEvent 事件的监听器 * 只要是属于事件ApplicationEvent的都会进行回调 */ public class MySpringFactoryApplicationListener implements ApplicationListener { @Override public void onApplicationEvent(ApplicationEvent event) { System.out.println("我执行了"); } } /** * @author xjx * @description * 监听具体的某一个事件的监听器 这里举例:ApplicationReadyEvent */ public class MySpringFactoryApplicationListenerWithReadyEvent implements ApplicationListener<ApplicationReadyEvent> { @Override public void onApplicationEvent(ApplicationReadyEvent event) { System.out.println("我执行了"); } }
一个执行监听器:
/** * @author xjx * @description * 这个是自定义创建执行监听器的类 6个时机自己想干点什么就干 * 不过要注意每个时机的容器内容初始化情况 */ public class MySpringFactoryApplicationRunListener implements SpringApplicationRunListener { /** * !!! 构造方法必须这么写 * 下边创建对象的时候会说 * @param application * @param args */ public MySpringFactoryApplicationRunListener(SpringApplication application, String[] args) { } @Override public void starting() { System.out.println("starting"); } @Override public void environmentPrepared(ConfigurableEnvironment environment) { System.out.println("environmentPrepared"); } @Override public void contextPrepared(ConfigurableApplicationContext context) { System.out.println("contextPrepared"); } @Override public void contextLoaded(ConfigurableApplicationContext context) { System.out.println("contextLoaded"); } @Override public void started(ConfigurableApplicationContext context) { System.out.println("started"); } @Override public void running(ConfigurableApplicationContext context) { System.out.println("running"); } @Override public void failed(ConfigurableApplicationContext context, Throwable exception) { System.out.println("failed"); } }
执行效果:
2.2 @Component 容器注入方式的
我们接着来看下容器注入方式的:
/** * @author xjx * @description */ @Component public class MyComponentApplicationListener implements ApplicationListener { @Override public void onApplicationEvent(ApplicationEvent event) { System.out.println("Component型 我执行了"); } }
来看下效果:
2.3 @EventListener 方式
我们接着看下 @EventListener 方式的,其实跟 @Component一样,也要考虑容器是否初始化:
@SpringBootApplication public class DemoApplication { public static void main(String[] args) { ConfigurableApplicationContext run = SpringApplication.run(DemoApplication.class, args); } @EventListener public void starting(ApplicationStartingEvent event) { System.out.println("starting"); } @EventListener public void environmentPrepared(ApplicationEnvironmentPreparedEvent event) { System.out.println("environmentPrepared"); } @EventListener public void contextPrepared(ApplicationContextInitializedEvent event) { System.out.println("contextPrepared"); } @EventListener public void contextLoaded(ApplicationPreparedEvent event) { System.out.println("contextLoaded"); } @EventListener public void started(ApplicationStartedEvent event) { System.out.println("started"); } @EventListener public void running(ApplicationReadyEvent event) { System.out.println("running"); } }
可以看到我们也是配置了6个时机,我们看下执行效果:
其实也是只会执行最后两个时机,因为前边阶段容器还未初始化,还未被解析,好啦那我们接下来看看源码。
3 SpringApplicationRunListeners 的创建
创建的入口是在 SpringApplication 的 run方法中,通过 getRunListeners 方法来创建的。
那我们来看看 getRunListeners 方法:
private SpringApplicationRunListeners getRunListeners(String[] args) { Class<?>[] types = new Class<?>[] { SpringApplication.class, String[].class }; // 也是通过该方法 getSpringFactoriesInstances 指定 SpringApplicationRunListener 类型来加载的 // 注意下参数 this 就是当前的 SpringApplication 对象,用于获取监听器集合 return new SpringApplicationRunListeners(logger, getSpringFactoriesInstances(SpringApplicationRunListener.class, types, this, args)); }
方法 getSpringFactoriesInstances 我们就不细看了哈,上节加载监听器和初始化器的时候有详细说过哈,这里就不重复说了哈,可以看到参数 this,args 这就是为什么我们边上边自定义执行监听器的构造方法要固定参数吧,因为根据这两个参数找构造方法来实例化执行监听器类的。我们直接看看 spring.factories 配置的执行监听器的类:
可以看到就配置了一个 EventPublishingRunListener,等下执行的过程就是该类执行相关的方法,我们看下该类的构造函数:
/** * @param application SpringApplication * @param args 启动参数 */ public EventPublishingRunListener(SpringApplication application, String[] args) { this.application = application; this.args = args; // 初始化一个默认的事件广播器 this.initialMulticaster = new SimpleApplicationEventMulticaster(); // 这里会把 SpringApplication 的监听器集合都放进广播器里 for (ApplicationListener<?> listener : application.getListeners()) { this.initialMulticaster.addApplicationListener(listener); } }
最后就是实例化 SpringApplicationRunListeners:
// 这里的 listeners 就是有一个EventPublishingRunListener对象的集合 SpringApplicationRunListeners(Log log, Collection<? extends SpringApplicationRunListener> listeners) { this.log = log; this.listeners = new ArrayList<>(listeners); }
好了,到这里我们的监听器的执行者就创建完了。
4 监听器的执行时机
执行时机有 6种情况: starting、environmentPrepared、contextPrepared、contextLoaded、started、running,
我们结合 SpringBoot 的启动过程,再来看看执行时机:
好啦,执行时机我们看了,接下来就来看下具体监听器的执行过程哈。
5 监听器的执行过程
我们可以看到上边的6个时机执行,都是 SpringApplicationRunListeners 去执行的,我们进去看看:
public void starting() { for (SpringApplicationRunListener listener : this.listeners) { listener.starting(); } }
我这里只贴了其中一个时机的哈,都是循环每个执行监听器来执行的。那我们进入 EventPublishingRunListener看下如何执行的。
public class EventPublishingRunListener implements SpringApplicationRunListener, Ordered { private final SpringApplication application; private final String[] args; private final SimpleApplicationEventMulticaster initialMulticaster; /** * @param application SpringApplication * @param args 启动参数 */ public EventPublishingRunListener(SpringApplication application, String[] args) { this.application = application; this.args = args; // 初始化一个默认的事件广播器 this.initialMulticaster = new SimpleApplicationEventMulticaster(); // 这里会把 SpringApplication 的监听器集合都放进广播器里 for (ApplicationListener<?> listener : application.getListeners()) { this.initialMulticaster.addApplicationListener(listener); } } @Override public int getOrder() { return 0; } @Override public void starting() { // 通过默认的广播器来广播事件 this.initialMulticaster.multicastEvent(new ApplicationStartingEvent(this.application, this.args)); } @Override public void environmentPrepared(ConfigurableEnvironment environment) { // 通过默认的广播器来广播事件 this.initialMulticaster .multicastEvent(new ApplicationEnvironmentPreparedEvent(this.application, this.args, environment)); } @Override public void contextPrepared(ConfigurableApplicationContext context) { // 通过默认的广播器来广播事件 this.initialMulticaster .multicastEvent(new ApplicationContextInitializedEvent(this.application, this.args, context)); } @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)); } @Override public void started(ConfigurableApplicationContext context) { // 这里就不一样了,这个是依托于Spring上下文中的广播器来广播的 context.publishEvent(new ApplicationStartedEvent(this.application, this.args, context)); } @Override public void running(ConfigurableApplicationContext context) { // 这里就不一样了,这个是依托于Spring上下文中的广播器来广播的 context.publishEvent(new ApplicationReadyEvent(this.application, this.args, context)); } }
可以看到前四个时机:starting、environmentPrepared、contextPrepared、contextLoaded,都是使用默认广播器来进行事件广播执行的,说白了就是绝对是同步执行的,而后两个:started、running,是用Spring中上下文的广播器进行事件广播的可能是同步也可能是异步的,当我们注入一个广播器并且带线程池的话就是异步的了。
我们先来看下广播器广播事件:
@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); } } }
我们继续看下执行监听器的方法:
protected void invokeListener(ApplicationListener<?> listener, ApplicationEvent event) { ErrorHandler errorHandler = getErrorHandler(); if (errorHandler != null) { try { // 调用监听器 doInvokeListener(listener, event); } catch (Throwable err) { errorHandler.handleError(err); } } else { doInvokeListener(listener, event); } } @SuppressWarnings({"rawtypes", "unchecked"}) private void doInvokeListener(ApplicationListener listener, ApplicationEvent event) { try { // 执行监听器的 onApplicationEvent 方法 listener.onApplicationEvent(event); } catch (ClassCastException ex) { String msg = ex.getMessage(); if (msg == null || matchesClassCastMessage(msg, event.getClass()) || (event instanceof PayloadApplicationEvent && matchesClassCastMessage(msg, ((PayloadApplicationEvent) event).getPayload().getClass()))) { // Possibly a lambda-defined listener which we could not resolve the generic event type for // -> let's suppress the exception. Log loggerToUse = this.lazyLogger; if (loggerToUse == null) { loggerToUse = LogFactory.getLog(getClass()); this.lazyLogger = loggerToUse; } if (loggerToUse.isTraceEnabled()) { loggerToUse.trace("Non-matching event type for listener: " + listener, ex); } } else { throw ex; } } }
那我们再来看看我们自定义的广播器是如何初始化进去的:
就是在上下文刷新的时候,初始化的我们进去看看:
protected void initApplicationEventMulticaster() { // 获取bean工厂 ConfigurableListableBeanFactory beanFactory = getBeanFactory(); // 判断是否有用户自定义的广播器 applicationEventMulticaster if (beanFactory.containsLocalBean(APPLICATION_EVENT_MULTICASTER_BEAN_NAME)) { // 有的话就用 用户注入的广播器 this.applicationEventMulticaster = beanFactory.getBean(APPLICATION_EVENT_MULTICASTER_BEAN_NAME, ApplicationEventMulticaster.class); if (logger.isTraceEnabled()) { logger.trace("Using ApplicationEventMulticaster [" + this.applicationEventMulticaster + "]"); } } else { // 没有的话 就还是初始化一个普通的广播器 this.applicationEventMulticaster = new SimpleApplicationEventMulticaster(beanFactory); beanFactory.registerSingleton(APPLICATION_EVENT_MULTICASTER_BEAN_NAME, this.applicationEventMulticaster); if (logger.isTraceEnabled()) { logger.trace("No '" + APPLICATION_EVENT_MULTICASTER_BEAN_NAME + "' bean, using " + "[" + this.applicationEventMulticaster.getClass().getSimpleName() + "]"); } } }
看到这里执行就看的差不多,就是有一点我暂时没怎么看懂就是广播器的 multicastEvent方法,广播一个事件,getApplicationListeners 获取当前这个事件可以执行的监听器集合的方法没怎么看懂哈,这里暂时留着哈,有理解的小伙伴还麻烦告诉我下,主要是 ResolvableType以及涉及的几个集合没太搞懂哈。
总体大致画个图方便理解哈:
6 小结
好了,到这里 SpringApplicationRunListeners 的执行时机就看的差不多了哈,有理解不对的地方欢迎指正哈。
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步