ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

深入理解 Spring 事件发布与监听

2021-06-21 14:01:28  阅读:177  来源: 互联网

标签:Spring public 深入 事件 监听器 logger 监听 MyEvent


在使用 Spring 构建的应用程序中,适当使用事件发布与监听的机制可以使我们的代码灵活度更高,降低耦合度。

在使用 Spring 构建的应用程序中,适当使用事件发布与监听的机制可以使我们的代码灵活度更高,降低耦合度。Spring 提供了完整的事件发布与监听模型,在该模型中,事件发布方只需将事件发布出去,无需关心有多少个对应的事件监听器;监听器无需关心是谁发布了事件,并且可以同时监听来自多个事件发布方发布的事件,通过这种机制,事件发布与监听是解耦的。

本节将举例事件发布与监听的使用,并介绍内部实现原理。

事件发布监听例子

新建 springboot 应用,boot 版本 2.4.0,引入如下依赖:

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter</artifactId>
    </dependency>
</dependencies>

自定义事件

Spring 中使用 ApplicationEvent 接口来表示一个事件,所以我们自定义事件 MyEvent 需要实现该接口:

public class MyEvent extends ApplicationEvent {
   
    public MyEvent(Object source) {
        super(source);
    }
}

构造器 source 参数表示当前事件的事件源,一般传入 Spring 的 context 上下文对象即可。

事件发布器

事件发布通过事件发布器 ApplicationEventPublisher 完成,我们自定义一个事件发布器 MyEventPublisher:

@Component
public class MyEventPublisher implements ApplicationEventPublisherAware, ApplicationContextAware {

    private ApplicationContext applicationContext;
    private ApplicationEventPublisher applicationEventPublisher;

    private final Logger logger = LoggerFactory.getLogger(this.getClass());

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }

    @Override
    public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
        this.applicationEventPublisher = applicationEventPublisher;
    }

    public void publishEvent() {
        logger.info("开始发布自定义事件MyEvent");
        MyEvent myEvent = new MyEvent(applicationContext);
        applicationEventPublisher.publishEvent(myEvent);
        logger.info("发布自定义事件MyEvent结束");
    }
}

在自定义事件发布器 MyEventPublisher 中,我们需要通过 ApplicationEventPublisher 来发布事件,所以我们实现了 ApplicationEventPublisherAware 接口,通过回调方法 setApplicationEventPublisher 为 MyEventPublisher 的 ApplicationEventPublisher 属性赋值;同样的,我们自定义的事件 MyEvent 构造函数需要传入 Spring 上下文,所以 MyEventPublisher 还实现了 ApplicationContextAware 接口,注入了上下文对象 ApplicationContext。

publishEvent 方法发布了一个自定义事件 MyEvent。事件发布出去后,我们接着编写相应的事件监听器。

注解监听

我们可以方便地通过 @EventListener 注解实现事件监听,编写 MyEventPublisher:

@Component
public class MyAnnotationEventListener {

    private final Logger logger = LoggerFactory.getLogger(this.getClass());

    @EventListener
    public void onMyEventPublished(MyEvent myEvent) {
        logger.info("收到自定义事件MyEvent -- MyAnnotationEventListener");
    }
}

被 @EventListener 注解标注的方法入参为 MyEvent 类型,所以只要 MyEvent 事件被发布了,该监听器就会起作用,即该方法会被回调。

编程实现监听

除了使用 @EventListener 注解实现事件的监听外,我们也可以手动实现 ApplicationListener 接口来实现事件的监听(泛型为监听的事件类型):

@Component
public class MyEventListener implements ApplicationListener<MyEvent> {

    private final Logger logger = LoggerFactory.getLogger(this.getClass());

    @Override
    public void onApplicationEvent(MyEvent event) {
        logger.info("收到自定义事件MyEvent");
    }
}

测试

在 springboot 的入口类中测试事件的发布:

@SpringBootApplication
public class MyApplication {

    public static void main(String[] args) {
        ConfigurableApplicationContext context = SpringApplication.run(MyApplication.class, args);
        MyEventPublisher publisher = context.getBean(MyEventPublisher.class);
        publisher.publishEvent();
    }
}

运行程序,输出如下:

2020-06-22 16:31:46.667  INFO 83600 --- [           main] c.m.demo.publisher.MyEventPublisher      : 开始发布自定义事件MyEvent
2020-06-22 16:31:46.668  INFO 83600 --- [           main] cc.mrbird.demo.listener.MyEventListener  : 收到自定义事件MyEvent
2020-06-22 16:31:46.668  INFO 83600 --- [           main] c.m.d.l.MyAnnotationEventListener        : 收到自定义事件MyEvent -- MyAnnotationEventListener
2020-06-22 16:31:46.668  INFO 83600 --- [           main] c.m.demo.publisher.MyEventPublisher      : 发布自定义事件MyEvent结束

可以看到,两个监听器都监听到了事件的发布。此外细心的读者会发现,事件发布和事件监听是同一个线程完成的,过程为同步操作,只有当所有对应事件监听器的逻辑执行完毕后,事件发布方法才能出栈。后面进阶使用会介绍如何使用异步的方式进行事件监听。

事件发布监听原理

事件发布监听过程

在事件发布方法上打个断点:

在这里插入图片描述

以 debug 的方式启动程序,程序执行到该断点后点击 Step Into 按钮,程序跳转到 AbstractApplicationContext 的 publishEvent(ApplicationEvent event) 方法:

img

继续点击 Step Into,程序跳转到 AbstractApplicationContext 的 publishEvent(Object event, @Nullable ResolvableType eventType) 方法:

img

  1. getApplicationEventMulticaster 方法用于获取广播事件用的多播器,源码如下所示:

    img

    那么 AbstractApplicationContext 的 applicationEventMulticaster 属性是何时赋值的呢,下面将会介绍到。

  2. 获取到事件多播器后,调用其 multicastEvent 方法广播事件,点击 Step Into 进入该方法内部查看具体逻辑:

    img

    查看 invokeListener 方法源码:

    img

    继续查看 doInvokeListener 方法源码:

    img

上述过程就是整个事件发布与监听的过程。

多播器创建过程

为了弄清楚 AbstractApplicationContext 的 applicationEventMulticaster 属性是何时赋值的(即事件多播器是何时创建的),我们在 AbstractApplicationContext 的 applicationEventMulticaster 属性上打个断点:

img

以 debug 的方式启动程序,程序跳转到了 AbstractApplicationContext 的 initApplicationEventMulticaster 方法中:

img

通过跟踪方法调用栈,我们可以总结出程序执行到上述截图的过程:

  1. SpringBoot 入口类的 main 方法执行 SpringApplication.run(MyApplication.class, args) 启动应用:

    img

  2. run 方法内部包含 refreshContext 方法(刷新上下文):

    img

  3. refresh 方法内部包含 initApplicationEventMulticaster 方法:

    img

  4. initApplicationEventMulticaster 方法创建多播器。

监听器获取过程

在追踪事件发布与监听的过程中,我们知道事件对应的监听器是通过 getApplicationListeners 方法获取的:

img

方法返回三个 MyEvent 事件对应的监听器,索引为 0 的监听器为 DelegatingApplicationListener,它没有实质性的处理某事件,忽略;索引为 1 的监听器为通过实现 ApplicationEventListener 接口的监听器;索引为 2 的监听器为通过 @EventListener 实现的监听器。

编程实现监听器注册过程

查看 getApplicationListeners 源码:

img

其中 retrieverCache 的定义为final Map<ListenerCacheKey, CachedListenerRetriever> retrieverCache = new ConcurrentHashMap<>(64)

接着查看 retrieveApplicationListeners 方法(方法见名知意,程序第一次获取事件对应的监听器时,缓存中是空的,所以继续检索获取事件对应的监听器):

img

从上面这段代码我们知道,用于遍历的监听器集合对象 listeners 和 listenerBeans 的值是从 this.defaultRetriever 的 applicationListeners 和 applicationListenerBeans 属性获取的,所以我们需要关注这些属性是何时被赋值的。defaultRetriever 的类型为 DefaultListenerRetriever:

img

我们在 applicationListeners 属性上右键选择 Find Usages 查看赋值相关操作:

img

可以看到,赋值操作发生在 AbstractApplicationEventMulticaster 的 addApplicationListener 方法中,

继续在 addApplicationListener 方法上右键选择 Find Usages 查看调用源:

img

我们在 registerListeners 方法上打个断点,重新启动程序,查看方法调用栈:

img

从方法调用栈我们可以总结出 this.defaultRetriever 的 applicationListeners 和 applicationListenerBeans 属性值赋值的过程:

  1. SpringApplication.run(MyApplication.class, args)启动 Boot 程序;

  2. run方法内部调用refreshContext刷新容器方法:

    img

  3. refresh方法内部调用了registerListener方法注册监听器:

    img

  4. registerListeners方法内部从 IOC 容器获取所有 ApplicationListener 类型 Bean,然后赋值给 this.defaultRetriever 的 applicationListeners 和 applicationListenerBeans 属性。

注解监听器注册过程

查看@EventListener注解源码:

img

查看 EventListenerMethodProcessor 源码:

img

其实现了 SmartInitializingSingleton 接口,该接口包含 afterSingletonsInstantiated 方法:

img

通过注释可以看到这个方法的调用时机为:单实例 Bean 实例化后被调用,此时 Bean 已经被创建出来。

我们查看 EventListenerMethodProcessor 是如何实现该方法的:

img

继续查看 processBean 方法源码:

img

至此,两种方式注册监听器的原理都搞清楚了。

使用进阶与拓展

事件监听异步化

通过前面的分析,我们知道事件广播和监听是一个线程完成的同步操作,有时候为了让广播更有效率,我们可以考虑将事件监听过程异步化。

单个异步

先来看看如何实现单个监听器异步。

首先需要在 springboot 入口类上通过@EnableAsync注解开启异步,然后在需要异步执行的监听器方法上使用@Async注解标注,以 MyAnnotationEventListener 为例:

@Component
public class MyAnnotationEventListener {

    private final Logger logger = LoggerFactory.getLogger(this.getClass());

    @Async // 异步
    @EventListener
    public void onMyEventPublished(MyEvent myEvent) {
        logger.info("收到自定义事件MyEvent -- MyAnnotationEventListener");
    }
}

启动程序,输出如下:

img

通过日志可以看出来,该监听器方法已经异步化,执行线程为 task-1。

整体异步

通过前面源码分析,我们知道多播器在广播事件时,会先判断是否有指定 executor,有的话通过 executor 执行监听器逻辑。所以我们可以通过指定 executor 的方式来让所有的监听方法都异步执行:

img

新建一个配置类:

@Configuration
public class AsyncEventConfigure {

    @Bean(name = AbstractApplicationContext.APPLICATION_EVENT_MULTICASTER_BEAN_NAME)
    public ApplicationEventMulticaster simpleApplicationEventMulticaster() {
        SimpleApplicationEventMulticaster eventMulticaster = new SimpleApplicationEventMulticaster();
        eventMulticaster.setTaskExecutor(new SimpleAsyncTaskExecutor());
        return eventMulticaster;
    }
}

在配置类中,我们注册了一个名称为 AbstractApplicationContext.APPLICATION_EVENT_MULTICASTER_BEAN_NAME(即 applicationEventMulticaster)的 Bean,用于覆盖默认的事件多播器,然后指定了 TaskExecutor,SimpleAsyncTaskExecutor 为 Spring 提供的异步任务 executor。

在启动项目前,先把之前在 springboot 入口类添加的@EnableAsync注解去掉,然后启动项目,输出如下:

img

可以看到,监听器事件都异步化了。

多事件监听器

事件监听器除了可以监听单个事件外,也可以监听多个事件(仅 @EventListener 支持),修改 MyAnnotationEventListener:

@Component
public class MyAnnotationEventListener {

    private final Logger logger = LoggerFactory.getLogger(this.getClass());

    @EventListener(classes = {MyEvent.class, ContextRefreshedEvent.class, ContextClosedEvent.class})
    public void onMyEventPublished(ApplicationEvent event) {
        if (event instanceof MyEvent) {
            logger.info("监听到MyEvent事件");
        }
        if (event instanceof ContextRefreshedEvent) {
            logger.info("监听到ContextRefreshedEvent事件");
        }
        if (event instanceof ContextClosedEvent) {
            logger.info("监听到ContextClosedEvent事件");
        }
    }
}

该监听器将同时监听 MyEvent、ContextRefreshedEvent 和 ContextClosedEvent 三种类型事件:

img

监听器排序

单个类型事件也可以有多个监听器同时监听,这时候可以通过实现 Ordered 接口实现排序(或者 @Order 注解标注)。

修改 MyEventListener:

@Component
public class MyEventListener implements ApplicationListener<MyEvent>, Ordered {

    private final Logger logger = LoggerFactory.getLogger(this.getClass());

    @Override
    public void onApplicationEvent(MyEvent event) {
        logger.info("收到自定义事件MyEvent,我的优先级较高");
    }

    @Override
    public int getOrder() {
        return 0;
    }
}

修改 MyAnnotationEventListener:

@Component
public class MyAnnotationEventListener implements Ordered {

    private final Logger logger = LoggerFactory.getLogger(this.getClass());

    @EventListener(classes = {MyEvent.class})
    public void onMyEventPublished(ApplicationEvent event) {
        if (event instanceof MyEvent) {
            logger.info("监听到MyEvent事件,我的优先级较低");
        }
    }

    @Override
    public int getOrder() {
        return 1;
    }
}

启动程序输出如下:

img

配合 SpEL 表达式

@EventListener 注解还包含一个 condition 属性,可以配合 SpEL 表达式来条件化触发监听方法。修改 MyEvent,添加一个 boolean 类型属性:

public class MyEvent extends ApplicationEvent {
    
    private boolean flag;

    public boolean isFlag() {
        return flag;
    }

    public void setFlag(boolean flag) {
        this.flag = flag;
    }

    public MyEvent(Object source) {
        super(source);
    }
}

在发布事件的时候,将该属性设置为 false:

@Component
public class MyEventPublisher implements ApplicationEventPublisherAware, ApplicationContextAware {

    private ApplicationContext applicationContext;
    private ApplicationEventPublisher applicationEventPublisher;

    private final Logger logger = LoggerFactory.getLogger(this.getClass());

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }

    @Override
    public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
        this.applicationEventPublisher = applicationEventPublisher;
    }

    public void publishEvent() {
        logger.info("开始发布自定义事件MyEvent");
        MyEvent myEvent = new MyEvent(applicationContext);
        myEvent.setFlag(false); // 设置为false
        applicationEventPublisher.publishEvent(myEvent);
        logger.info("发布自定义事件MyEvent结束");
    }
}

在 MyAnnotationEventListener 的 @EventListener 注解上演示如何使用 SpEL:

@Component
public class MyAnnotationEventListener implements Ordered {

    private final Logger logger = LoggerFactory.getLogger(this.getClass());

    @EventListener(classes = {MyEvent.class}, condition = "#event.flag")
    public void onMyEventPublished(ApplicationEvent event) {
        if (event instanceof MyEvent) {
            logger.info("监听到MyEvent事件,我的优先级较低");
        }
    }

    @Override
    public int getOrder() {
        return 1;
    }
}

condition = "#event.flag"的含义为,当前 event 事件(这里为 MyEvent)的 flag 属性为 true 的时候执行。

启动程序,输出如下:

img

因为我们发布的 MyEvent 的 flag 属性值为 false,所以上面这个监听器没有被触发。

事务事件监听器

Spring 4.2 开始提供了一个 @TransactionalEventListener 注解用于监听数据库事务的各个阶段:

  1. AFTER_COMMIT - 事务成功提交;
  2. AFTER_ROLLBACK – 事务回滚后;
  3. AFTER_COMPLETION – 事务完成后(无论是提交还是回滚);
  4. BEFORE_COMMIT - 事务提交前;

例子:

@TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)
public void onTransactionChange(ApplicationEvent event){
    logger.info("监听到事务提交事件");
}

标签:Spring,public,深入,事件,监听器,logger,监听,MyEvent
来源: https://blog.csdn.net/mbh12333/article/details/118085149

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有