首页 > 其他分享 >【SpringBoot】【二】 SpringApplicationRunListeners 监听器执行过程详解

【SpringBoot】【二】 SpringApplicationRunListeners 监听器执行过程详解

时间:2023-05-05 15:24:58浏览次数:39  
标签:SpringApplicationRunListeners SpringBoot void args public listener 监听器 event

1  前言

我们看到 SpringBoot 启动的时候,会在每个时机执行监听器,这节我们就来看看,加载监听器的过程我们就不说了哈,上节说过了哈,本节我们主要看:

(1)SpringApplicationRunListeners 的创建过程

(2)监听器的执行时机有哪些

(3)监听器的执行过程

三个方面来看哈。

2  使用

在看之前,我们先来看看使用体验一下,使用方式上大概可分三种:

(1)spring.factories 配置的

(2)@Conponen 方式的

(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 的执行时机就看的差不多了哈,有理解不对的地方欢迎指正哈。

标签:SpringApplicationRunListeners,SpringBoot,void,args,public,listener,监听器,event
From: https://www.cnblogs.com/kukuxjx/p/17373029.html

相关文章

  • SpringBoot 超大文件上传和断点续传的实现
    ​ 以ASP.NETCoreWebAPI 作后端 API ,用 Vue 构建前端页面,用 Axios 从前端访问后端 API,包括文件的上传和下载。 准备文件上传的API #region 文件上传  可以带参数        [HttpPost("upload")]        publicJsonResultuploadProject(I......
  • springboot,maven多模块打jar包踩坑
    父工程msgdemo中msgdemoimpl依赖gson_enum模块。在msgdemoimpl中有controller接口代码一开始在该pom中使用了import指定了该依赖的范围,所以打包失败。使用默认的范围即可。......
  • SpringBoot中策略模式+工厂模式业务实例(接口传参-枚举类查询策略映射关系-执行不同策
    场景设计模式-策略模式在Java中的使用示例:https://blog.csdn.net/BADAO_LIUMANG_QIZHI/article/details/127622238上面讲了策略模式在Java中的使用示例。下面看一个在SpringBoot中的实际使用示例。业务场景:有多个煤矿,信号灯有多个厂家/规则,每个煤矿对应一种信号灯。需要编......
  • java基于springboot+vue的宿舍管理系统、学生宿舍管理系统、高校宿舍管理系统,附源码+
    1、项目介绍java基于springboot+vue的宿舍管理系统、学生宿舍管理系统、高校宿舍管理系统,实现管理员:首页、个人中心、公告信息管理、院系管理、班级管理、学生管理、宿舍信息管理、宿舍安排管理、卫生检查管理、报修信息管理、报修处理管理、缴费信息管理,学生;首页、个人中心、公......
  • springboot 项目国际化+登录拦截器
    项目页面国际化1.语言配置文件需要下载插件ResourceBundleEditor 新建国际目录i18n 在properties配置文件中自定义  2.前端index页面要设置语言参数传递给后端,切换中英文 3.自定义地区解析器MyLocaleResolver后端接收并处理 4.自定义了一个地区解析器要生效......
  • springboot mvc配置原理+扩展springmvc(重点)
    1.新建config目录2.在config目录下创建自定义配置类3.根据官方文档得到要有注解@Configuration并且继承类WebMvcConfigurer 扩展springmvc:我们慢慢脱离了原始的繁琐的xml配置,现在转向javaconfig配置 最后扩展->springmvc配置原理源码:注意点:springmvc的配置在springboo......
  • springboot 多环境配置及配置文件的位置
    了解即可  ......
  • springboot 分析源码欢迎页和图标-> thymeleaf模板引擎常用语法->扩展
    欢迎页: icon: 注意点: thymeleaf模板引擎1.使用thymeleaf模板引擎前要导入对应依赖包2.阅读源码:根据源码说明我们可以将html文件放置在templates目录下,然后通过controller进行跳转即可 controller类://在templates下的东西需要通过controller类来跳转,//需要导入......
  • SpringBoot定义优雅全局统一Restful API 响应框架三
    我们目前已经设计出了,包含全局响应,异常错误响应进行了统一返回。但是错误内容我们设计的比较模糊统一,还可以进行细化这样更有利于定位错误当我们需要调用Http接口时,无论是在Web端还是移动端,都有可能遇到各种错误,例如参数缺失、类型错误、系统错误等。为了规范错误信息的返回,我们......
  • springboot与mongodb之事务管理(二)
    一、事务说明1、在4.0版本中,MongoDB支持副本集上的多文档事务,分片集群是不支持事务的,会报以下异常TransactionsarenotsupportedbytheMongoDBclustertowhichthisclientisconnected2、在版本4.2中,MongoDB引入了分布式事务,在副本集或分片集群上都是支持事务的。3......