一、介绍

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

二、例子

2.1 引入依赖

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

<!-- 测试订阅与监听 -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter</artifactId>
</dependency>

2.2 自定义事件

Spring中使用ApplicationEvent接口来表示一个事件,所以我们自定义事件MyEvent需要实现该接口,构造器source参数表示当前事件的事件源,一般传入Spring的context上下文对象即可。

package com.qiang.spring.boot.test.issue.monitor.config;

import org.springframework.context.ApplicationEvent;

/**
 * @author: 小强崽
 * @create: 2020-12-29 00:38
 * @description: 自定义事件
 */
public class MyEvent extends ApplicationEvent {
    public MyEvent(Object source) {
        super(source);
    }
}

2.3 事件发布器

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

package com.qiang.spring.boot.test.issue.monitor.config;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.ApplicationEventPublisherAware;
import org.springframework.stereotype.Component;

/**
 * @author: 小强崽
 * @create: 2020-12-29 00:39
 * @description: 事件发布器
 */
@Component
public class MyEventPublisher implements ApplicationEventPublisherAware, ApplicationContextAware {

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

    private ApplicationContext applicationContext;
    private ApplicationEventPublisher applicationEventPublisher;

    @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 = new MyEvent(applicationContext);
        applicationEventPublisher.publishEvent(myEvent);
        logger.info("发布自定义事件结束!");
        // 程序异常后还是能监听到,事件发布和事件监听是同一个线程完成的,过程为同步操作,只有当所有对应事件监听器的逻辑执行完毕后,事件发布方法才能出栈。
        // throw new RuntimeException("程序异常");
    }
}

2.4 事件监听器

2.4.1 注解实现

我们可以方便地通过@EventListener注解实现事件监听,编写MyAnnotationEventListener。被@EventListener注解标注的方法入参为MyEvent类型,所以只要MyEvent事件被发布了,该监听器就会起作用,即该方法会被回调。

@Component
public class MyAnnotationEventListener {

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

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

2.4.2 接口实现

除了使用@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");
    }
}

2.5 测试结果

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

package com.qiang.spring.boot.test.issue.monitor;

import com.qiang.spring.boot.test.issue.monitor.config.MyEventPublisher;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;

/**
 * @author: 小强崽
 * @create: 2020-12-29 00:16
 * @description: 启动类
 */
@SpringBootApplication
public class IssueMonitorApplication {
    public static void main(String[] args) {
        ConfigurableApplicationContext configurableApplicationContext = SpringApplication.run(IssueMonitorApplication.class, args);
        MyEventPublisher myEventPublisher = configurableApplicationContext.getBean(MyEventPublisher.class);
        myEventPublisher.publishEvent();
    }
}

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

image-20201229141714437

2.6 事件监听异步化

2.6.1 单个异步

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

package com.qiang.spring.boot.test.issue.monitor;

import com.qiang.spring.boot.test.issue.monitor.config.MyEventPublisher;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;

/**
 * @author: 小强崽
 * @create: 2020-12-29 00:16
 * @description: 开启异步通知:@EnableAsync,当开启全局异步化后不需要再开启了
 */
@EnableAsync
@SpringBootApplication
public class IssueMonitorApplication {
    public static void main(String[] args) {
        ConfigurableApplicationContext configurableApplicationContext = SpringApplication.run(IssueMonitorApplication.class, args);
        MyEventPublisher myEventPublisher = configurableApplicationContext.getBean(MyEventPublisher.class);
        myEventPublisher.publishEvent();
    }
}

package com.qiang.spring.boot.test.issue.monitor.config;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.event.EventListener;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;

/**
 * @author: 小强崽
 * @create: 2020-12-29 00:44
 * @description: 事件监听,@EventListener注解
 */
@Component
public class MyAnnotationEventListener {

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

    /**
     * 单事件监听
     * 异步通知:@Async
     * @param myEvent
     */
    @Async
    @EventListener
    public void onMyEventPublished(MyEvent myEvent) {
        logger.info("收到自定义事件MyEvent -- MyAnnotationEventListener");
    }

}

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

image-20201229143402411

2.6.2 整体异步化

新建一个配置类。

package com.qiang.spring.boot.test.issue.monitor.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.event.ApplicationEventMulticaster;
import org.springframework.context.event.SimpleApplicationEventMulticaster;
import org.springframework.context.support.AbstractApplicationContext;
import org.springframework.core.task.SimpleAsyncTaskExecutor;

/**
 * @author: 小强崽
 * @create: 2020/12/29 10:11
 * @description: 整体异步配置类
 **/
@Configuration
public class AsyncEventConfigure {

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

在配置类中,我们注册了一个名称为AbstractApplicationContext.APPLICATION_EVENT_MULTICASTER_BEAN_NAME(即applicationEventMulticaster)的Bean,用于覆盖默认的事件多播器,然后指定了TaskExecutor,SimpleAsyncTaskExecutor为Spring提供的异步任务executor。在启动项目前,先把之前在springboot入口类添加的@EnableAsync注解去掉和MyAnnotationEventListener的注解@Async去掉,然后启动项目,输出如下。可以看到,监听器事件都异步化了。

image-20201229143849213

2.7 多事件监听器

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

package com.qiang.spring.boot.test.issue.monitor.config;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.ApplicationEvent;
import org.springframework.context.event.ContextClosedEvent;
import org.springframework.context.event.ContextRefreshedEvent;
import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Component;

/**
 * @author: 小强崽
 * @create: 2020-12-29 00:44
 * @description: 事件监听,@EventListener注解
 */
@Component
public class MyAnnotationEventListener {

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

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

}

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

image-20201229144214865

2.8 监听器排序

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

修改MyEventListener。

package com.qiang.spring.boot.test.issue.monitor.config;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.ApplicationListener;
import org.springframework.core.Ordered;
import org.springframework.stereotype.Component;

/**
 * @author: 小强崽
 * @create: 2020-12-29 00:47
 * @description: 监听器排序
 */
@Component
public class MyEventListener implements ApplicationListener<MyEvent>, Ordered {

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

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

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

修改MyAnnotationEventListener。

package com.qiang.spring.boot.test.issue.monitor.config;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.ApplicationEvent;
import org.springframework.context.event.EventListener;
import org.springframework.core.Ordered;
import org.springframework.stereotype.Component;

/**
 * @author: 小强崽
 * @create: 2020-12-29 00:44
 * @description: 事件监听,@EventListener注解
 */
@Component
public class MyAnnotationEventListener implements Ordered {

    private final Logger logger = LoggerFactory.getLogger(this.getClass());
    
    @EventListener(classes = {MyEvent.class})
    public void onMyEventPublished(ApplicationEvent applicationEvent) {
        if (applicationEvent instanceof MyEvent) {
            logger.info("监听到 MyEvent 事件,我的优先级较低");
        }
    }

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

启动程序。

image-20201229145111436

2.9 配合SpEL表达式

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

package com.qiang.spring.boot.test.issue.monitor.config;

import org.springframework.context.ApplicationEvent;

/**
 * @author: 小强崽
 * @create: 2020-12-29 00:38
 * @description: 自定义事件
 */
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。

package com.qiang.spring.boot.test.issue.monitor.config;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.ApplicationEventPublisherAware;
import org.springframework.stereotype.Component;

/**
 * @author: 小强崽
 * @create: 2020-12-29 00:39
 * @description: 事件发布器
 */
@Component
public class MyEventPublisher implements ApplicationEventPublisherAware, ApplicationContextAware {

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

    private ApplicationContext applicationContext;
    private ApplicationEventPublisher applicationEventPublisher;

    @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 = new MyEvent(applicationContext);
        myEvent.setFlag(false);
        applicationEventPublisher.publishEvent(myEvent);
        logger.info("发布自定义事件结束!");
    }
}

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

condition = "#applicationEvent.flag"的含义为,当前applicationEvent事件(这里为MyEvent)的flag属性为true的时候执行。
package com.qiang.spring.boot.test.issue.monitor.config;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.ApplicationEvent;
import org.springframework.context.event.EventListener;
import org.springframework.core.Ordered;
import org.springframework.stereotype.Component;

/**
 * @author: 小强崽
 * @create: 2020-12-29 00:44
 * @description: 事件监听,@EventListener注解
 */
@SuppressWarnings("all")
@Component
public class MyAnnotationEventListener implements Ordered {

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

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

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

启动程序,输出如下,因为我们发布的MyEvent的flag属性值为false,所以上面这个监听器没有被触发。

image-20201229165600941

2.10 事务事件监听器

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

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

引入依赖

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-tx</artifactId>
</dependency>

编辑代码

@TransactionalEventListener(phase = TransactionPhase.BEFORE_COMMIT)
public void onTransactionBeforeCommit(ApplicationEvent applicationEvent) {
    logger.info("监听到事务提交事件之前 BeforeCommit !");
}

@TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)
public void onTransactionAfterCommit(ApplicationEvent applicationEvent) {
    logger.info("监听到事务提交事件完成 AfterCommit !");
}

@TransactionalEventListener(phase = TransactionPhase.AFTER_ROLLBACK)
public void onTransactionAfterRollback(ApplicationEvent applicationEvent) {
    logger.info("监听到事务提交事件回滚 AfterRollback !");
}

@TransactionalEventListener(phase = TransactionPhase.AFTER_COMPLETION)
public void onTransactionAfterCompletion(ApplicationEvent applicationEvent) {
    logger.info("监听到事务提交事件完成之后 AfterCompletion !");
}