之前的文章链接: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. CentOS7安装MYCAT中间件

    MYCAT是一个被广泛使用的功能强大的开源的数据库中间件,当然他的理想不仅仅是做一个中间件.这篇文章主要记录MYCAT服务的搭建过程,下篇会继续更新MYCAT的使用配置. 本篇记录将使用CentOS7 ...

  2. python干货-类属性和方法,类的方法重写

    类属性与方法 类的私有属性 __private_attrs: 两个下划线开头,表明为私有,外部不可用,内部使用时self.__private_attrs. 类的方法 在类的内部,使用 def 关键字来 ...

  3. 常用vscode 插件第一季

    VSCODE 常用插件第一季 1.chinese 此插件中文(简体)语言包为 VS Code 提供本地化界面. https://marketplace.visualstudio.com/items?i ...

  4. E - Dividing Chocolate ATcoder

    题目大意:切割图形,给你一个非0即1的矩阵,将它切割成多个长方形,使每个小长方形中1的个数不得多于k个,切割的规则,要么切一整行,要么是一整列. 题解: 二进制枚举. 注意行数最大才是10.用二进制枚 ...

  5. ATcoder--D - Summer Vacation

    这个题目的题意有点难搞 题目连接: https://atcoder.jp/contests/abc137/tasks/abc137_d 题目大意:输入n和m 指的是一共有n个输入在m天前一共能赚到的钱 ...

  6. WPF中在Gmap.net中将Marker动起来

    前一段时间说过一篇绘制极坐标的,这段时间对它进行了改造已经今非昔比了,功能实现了很多,我目的是让Marker动起来,然后还会绘制Route,上篇也就是简单的绘制了Route,没有关于Marker的相关 ...

  7. api接口安全

    API接口安全 在做app开发中,如何保证api的接口安全,不被其他app去调用? 接口安全的措施很多,今天记录一个常用的措施 签名: 前台想要调用接口,需要使用几个参数生成签名: 时间戳:当前时间 ...

  8. SQLServer系统表使用简介(sysobjects、syscolumns、syscomments等)转载

    sysobjects:记录了数据库中每一个表.视图.约束.存储过程等详细内容的表. 表中常用的字段如下 : 列名 数据类型 描述 name sysname 对象名 id int 对象标识号 xtype ...

  9. Mac剪切板中的PNG保存到文件swift

    SwiftGG 教程大全 中文翻译 命令行工具开发教程 Line Programs on macOS Tutorial swift4,较详细 Swift基础中需要注意的点 NSPasteboard M ...

  10. IIS WebDAV安全配置

    本文为转载,原文地址:http://www.2cto.com/article/201307/228165.html IIS WebDAV安全配置 2013-07-16 12:13:00 作者:瞌睡龙收 ...