之前的文章链接:https://blog.csdn.net/qq_41907991/article/details/88544777

我们要知道的是,Spring的publish-event使用的是监听者模式

监听者模式包含了一个监听者Listener与之对应的事件Event,还有一个事件发布者EventPublish,过程就是EventPublish发布一个事件,被监听者捕获到,然后执行事件相应的方法

监听者模式跟观察者模式的区别:

观察者模式是一对多的模式,一个被观察者Observable和多个观察者Observer,被观察者中存储了所有的观察者对象,当被观察者接收到一个外界的消息,就会遍历广播推算消息给所有的观察者

  1. 首先我们看下两个发布事件的方法,可以发现,最后都会调到void publishEvent(Object event)
public interface ApplicationEventPublisher {

   default void publishEvent(ApplicationEvent event) {
publishEvent((Object) event);
} void publishEvent(Object event); }
  1. 继续跟踪代码:

    protected void publishEvent(Object event, @Nullable ResolvableType eventType) {
    Assert.notNull(event, "Event must not be null"); // 如果是一个ApplicationEvent,就强转
    // 如果不是的话,包装成一个PayloadApplicationEvent
    ApplicationEvent applicationEvent;
    if (event instanceof ApplicationEvent) {
    applicationEvent = (ApplicationEvent) event;
    }
    else {
    applicationEvent = new PayloadApplicationEvent<>(this, event);
    if (eventType == null) {
    // 包装成一个PayloadApplicationEvent,并获取实际的带泛型的事件类型
    // 比如PayloadApplicationEvent<MyEvent>
    eventType = ((PayloadApplicationEvent<?>) applicationEvent).getResolvableType();
    }
    } // Multicast right now if possible - or lazily once the multicaster is initialized
    if (this.earlyApplicationEvents != null) {
    this.earlyApplicationEvents.add(applicationEvent);
    }
    else {
    // 1.获取一个applicationEventMulticaster
    // 2.广播事件
    getApplicationEventMulticaster().multicastEvent(applicationEvent, eventType);
    } // 如果当前容易存在父容器,父容器也要发布事件
    if (this.parent != null) {
    if (this.parent instanceof AbstractApplicationContext) {
    ((AbstractApplicationContext) this.parent).publishEvent(event, eventType);
    }
    else {
    this.parent.publishEvent(event);
    }
    }
    }
    1. 我们来看下ApplicationEventMulticaster的初始化
    protected void initApplicationEventMulticaster() {
    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 {
    // 如果我们没定义ApplicationEventMulticaster,默认使用SimpleApplicationEventMulticaster
    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() + "]");
    }
    }
    }

    ​ 通过这段代码,我们可以知道,我们如果申明了一个ApplicationEventMulticaster类,会覆盖默认的SimpleApplicationEventMulticaster。

    1. 注册监听者
    protected void registerListeners() {
    // 把提前存储好的监听器添加到监听器容器中
    for (ApplicationListener<?> listener : getApplicationListeners()) {
    getApplicationEventMulticaster().addApplicationListener(listener);
    } // Do not initialize FactoryBeans here: We need to leave all regular beans
    // uninitialized to let post-processors apply to them!
    String[] listenerBeanNames = getBeanNamesForType(ApplicationListener.class, true, false);
    for (String listenerBeanName : listenerBeanNames) {
    getApplicationEventMulticaster().addApplicationListenerBean(listenerBeanName);
    } // Publish early application events now that we finally have a multicaster...
    Set<ApplicationEvent> earlyEventsToProcess = this.earlyApplicationEvents;
    this.earlyApplicationEvents = null;
    if (earlyEventsToProcess != null) {
    for (ApplicationEvent earlyEvent : earlyEventsToProcess) {
    getApplicationEventMulticaster().multicastEvent(earlyEvent);
    }
    }
    }
    1. 看完上面的代码,我相信大家多spring的publish-event机制已经有了一定的了解,现在我们再来探讨一个问题-------怎么实现异步机制

    我继续看getApplicationEventMulticaster().multicastEvent(applicationEvent, eventType)这段代码:

    @Override
    public void multicastEvent(final ApplicationEvent event, @Nullable ResolvableType eventType) {
    ResolvableType type = (eventType != null ? eventType : resolveDefaultEventType(event));
    for (final ApplicationListener<?> listener : getApplicationListeners(event, type)) {
    Executor executor = getTaskExecutor();
    if (executor != null) {
    executor.execute(() -> invokeListener(listener, event));
    }
    else {
    invokeListener(listener, event);
    }
    }
    }

    可以发现,在通知监听者的时候,会去检查ApplicationEventMulticaster对象中是否配置了Executor执行器,如果配置了的话,会将invokeListener(listener, event)封装成一个线程可执行任务,然后给执行器执行。

    这样的话,我们可以自定义一个ApplicationEventMulticaster:

    //需要注意的是,我们的名字一定要叫applicationEventMulticaster,否则不生效,回头看下源码就明白了
    @Component(&quot;applicationEventMulticaster&quot;)
    public class MyMulticaster extends SimpleApplicationEventMulticaster { @Override
    protected Executor getTaskExecutor() {
    // 这里可以按实际需求进行线程池的配置
    return new SimpleAsyncTaskExecutor();
    } /**
    * 这个方法主要配置错误处理,默认只会打一个警告级别的日志
    *
    * @return
    */
    @Override
    protected ErrorHandler getErrorHandler() {
    return (t -&gt; {
    System.out.println(&quot;出错了&quot;);
    });
    }
    }

    通过上面的配置后,我们所有的publish-event都会通过异步的机制去执行,但是我们想一个问题,有时候我们不想异步呢?一部分我们想同步,一部分想异步,这个时候怎么做呢?我们可以通过下面这种方式:

    • 配置类上加注解@EnableAsync

    • // 执行的方法上加@Async注解
      @EventListener
      @Async
      public void on(MySecondEvent event) throws Exception { }

    这样通过aop的方式,会将我们的方法采用异步的方式进行执行。我们没有配置线程池的时候可能会报错:

    org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'org.springframework.core.task.TaskExecutor' available

    不过我们方法还是会异步执行,这主要是下面这段代码:

    protected Executor getDefaultExecutor(@Nullable BeanFactory beanFactory) {
    if (beanFactory != null) {
    try {
    // 在这里会抛出一个NoSuchBeanDefinitionException
    return beanFactory.getBean(TaskExecutor.class);
    }
    catch (NoUniqueBeanDefinitionException ex) {
    logger.debug("Could not find unique TaskExecutor bean", ex);
    try {
    return beanFactory.getBean(DEFAULT_TASK_EXECUTOR_BEAN_NAME, Executor.class);
    }
    catch (NoSuchBeanDefinitionException ex2) {
    if (logger.isInfoEnabled()) {
    logger.info("More than one TaskExecutor bean found within the context, and none is named " +
    "'taskExecutor'. Mark one of them as primary or name it 'taskExecutor' (possibly " +
    "as an alias) in order to use it for async processing: " + ex.getBeanNamesFound());
    }
    }
    }
    // 这里被捕获
    catch (NoSuchBeanDefinitionException ex) {
    logger.debug("Could not find default TaskExecutor bean", ex);
    try {
    // 我们也没有定义一个名字是DEFAULT_TASK_EXECUTOR_BEAN_NAME = "taskExecutor"
    // 的线程池
    return beanFactory.getBean(DEFAULT_TASK_EXECUTOR_BEAN_NAME, Executor.class);
    }
    catch (NoSuchBeanDefinitionException ex2) {
    logger.info("No task executor bean found for async processing: " +
    "no bean of type TaskExecutor and no bean named 'taskExecutor' either");
    }
    // Giving up -> either using local default executor or none at all...
    }
    }
    // 最后会返回一个null
    return null;
    }
    @Override
    @Nullable
    // 最后会返回一个SimpleAsyncTaskExecutor
    protected Executor getDefaultExecutor(@Nullable BeanFactory beanFactory) {
    Executor defaultExecutor = super.getDefaultExecutor(beanFactory);
    return (defaultExecutor != null ? defaultExecutor : new SimpleAsyncTaskExecutor());
    }

Spring 学习 之 再探publish-event机制的更多相关文章

  1. Spring学习之旅(四)Spring工作原理再探

    上篇博文对Spring的工作原理做了个大概的介绍,想看的同学请出门左转.今天详细说几点. (一)Spring IoC容器及其实例化与使用 Spring IoC容器负责Bean的实例化.配置和组装工作有 ...

  2. 再探快速傅里叶变换(FFT)学习笔记(其三)(循环卷积的Bluestein算法+分治FFT+FFT的优化+任意模数NTT)

    再探快速傅里叶变换(FFT)学习笔记(其三)(循环卷积的Bluestein算法+分治FFT+FFT的优化+任意模数NTT) 目录 再探快速傅里叶变换(FFT)学习笔记(其三)(循环卷积的Blueste ...

  3. spring学习12 -Spring 框架模块以及面试常见问题注解等

    以下为spring常见面试问题: 1.Spring 框架中都用到了哪些设计模式? Spring框架中使用到了大量的设计模式,下面列举了比较有代表性的: 代理模式—在AOP和remoting中被用的比较 ...

  4. Spring 学习记录8 初识XmlWebApplicationContext(2)

    主题 接上文Spring 学习记录7 初识XmlWebApplicationContext refresh方法 refresh方法是定义在父类AbstractApplicationContext中的. ...

  5. 【再探backbone 02】集合-Collection

    前言 昨天我们一起学习了backbone的model,我个人对backbone的熟悉程度提高了,但是也发现一个严重的问题!!! 我平时压根没有用到model这块的东西,事实上我只用到了view,所以昨 ...

  6. 不错的Spring学习笔记(转)

    Spring学习笔记(1)----简单的实例 ---------------------------------   首先需要准备Spring包,可从官方网站上下载.   下载解压后,必须的两个包是s ...

  7. spring学习 8-面试(事务,解决线程安全)

    1.介绍一下Spring的事物管理 参考:Spring 学习7 -事务 2.Spring如何处理线程并发问题    Spring使用ThreadLocal解决线程安全问题 参考:Spring学习11- ...

  8. Spring学习(一)初识Spring

    什么是Spring 定义:Spring 是一个轻量级的 DI / IoC 和 AOP 容器的开源框架,目的为了简化java开发. DI:注入 IOC:控制反转 AOP:面向切面编程 原理:利用了jav ...

  9. Spring学习笔记之二----基于XML的Spring AOP配置

    在Spring配置文件中,通常使用<aop:config>元素来设置AOP,其中应包括: <aop:aspect>指定aspect,aspect是一个POJO类,包含了很多的a ...

随机推荐

  1. Arthas-Java的线上问题定位工具

    Arthas(阿尔萨斯) 能为你做什么? Arthas 是Alibaba开源的Java诊断工具,深受开发者喜爱. 当你遇到以下类似问题而束手无策时,Arthas可以帮助你解决: 这个类从哪个 jar ...

  2. mybatis配置的逻辑删除不好使了

    在使用mybatisplus中,可使用逻辑删除.案例中,使用mybatisplus逆向生成model,使用delete_status为识别逻辑删除字段. springboot 中配置启动逻辑删除 my ...

  3. Mybatis源码详解系列(三)--从Mapper接口开始看Mybatis的执行逻辑

    简介 Mybatis 是一个持久层框架,它对 JDBC 进行了高级封装,使我们的代码中不会出现任何的 JDBC 代码,另外,它还通过 xml 或注解的方式将 sql 从 DAO/Repository ...

  4. SpringMVC转发及重定向

    基础环境搭建请参考SringMVC入门程序 1:springmvc-servlet.xml <?xml version="1.0" encoding="UTF-8& ...

  5. 【原创】Linux RCU原理剖析(二)-渐入佳境

    背景 Read the fucking source code! --By 鲁迅 A picture is worth a thousand words. --By 高尔基 说明: Kernel版本: ...

  6. Django文档阅读-Day3

    Django文档阅读-Day3 Writing your first Django app, part 3 Overview A view is a "type" of Web p ...

  7. 你知道什么是 GitHub Action 么?

    本文是 GitHub Action 的入门教程,如您已有相关使用经验可以直接关掉. GitHub Action 是 GitHub 于 2018 年 10 月推出的一个 CI\CD 服务. 之前一直都是 ...

  8. java中String StringBuilder StringBuffer比较和效率(性能)测试

    string stringbuilder stringbuffer三者的区别 从JDK源码看,String.StringBuilder.StringBuffer都是存放在char[] 数组字符串. 简 ...

  9. Java锁之自旋锁

    Java锁之自旋锁 自旋锁:spinlock,是指尝试获取锁的线程不会立即阻塞,而是采用循环的方式去尝试获取锁,这样的好处是减少线程上下文切换的消耗,缺点是循环会消耗CPU 原来提到的比较并交换,底层 ...

  10. shell脚本:备份数据库、代码上线

    备份MySQL数据库场景:一台MySQL服务器,跑着5个数据库,在没有做主从的情况下,需要对这5个库进行备份 需求:1)每天备份一次,需要备份所有的库2)把备份数据存放到/data/backup/下3 ...