Spring Boot Event可以实现组件之间的解耦,这是一种典型的观察者设计模式。
基本使用
使用Spring Boot Event 使用步骤如下:
- 自定义一个事件源并继承
ApplicationEvent
- 创建事件监听器监听事件源
- 使用
ApplicationEventPublisher
推送事件
其中监听器两种实现方式:
- 实现
ApplicationListener<E extends ApplicationEvent>
接口 - 使用
@EventListener
及其派生注解
使用方式如下:
@SpringBootApplication(exclude = DataSourceAutoConfiguration.class)
public class EventApplication {
public static void main(String[] args) {
ConfigurableApplicationContext context = new SpringApplicationBuilder(EventApplication.class)
.web(WebApplicationType.NONE)
.run(args);
context.addApplicationListener(new SmsApplicationLister());
MyService myService = context.getBean(MyService.class);
myService.doBusiness();
}
@Getter
static class MyEvent extends ApplicationEvent {
private final Integer point;
public MyEvent(Object source, Integer point) {
super(source);
this.point = point;
} }
@Slf4j
static class SmsApplicationLister implements ApplicationListener<MyEvent> {
@Override
public void onApplicationEvent(MyEvent event) {
log.info("发送短信...");
} }
@Slf4j
@Component static class EmailApplicationLister implements ApplicationListener<MyEvent> {
@Override
public void onApplicationEvent(MyEvent event) {
log.info("发送邮件...");
} }
@Component
@Slf4j static class PointApplicationLister {
@EventListener
public void point(MyEvent event) {
log.info("积分+{}", event.getPoint());
} }
@Component
@Slf4j static class MyService {
@Resource
private ApplicationEventPublisher publisher;
public void doBusiness() {
log.info("主线业务");
MyEvent myEvent = new MyEvent(this, 10);
publisher.publishEvent(myEvent);
}
}
}
注意上述代码中,SmsApplicationLister
是通过addApplicationListener
方法添加进去的,这与EmailApplicationLister
使用@Compont
注解方式添加到容器中有所不同。下面会有所解释
执行了之后的结果如图:
可以看出它们是以自然顺序排序的:可以在相关Listener上添加@Order
注解或者实现Ordered
接口来改变它们在同一个线程的时候的执行顺序,如:
@Component
@Slf4j
static class PointApplicationLister {
@EventListener
@Order(Ordered.HIGHEST_PRECEDENCE)
public void point(MyEvent event) {
log.info("积分+{}", event.getPoint());
}}
针对@EventListener
标注的方法,我有以下的思考:
- 标注的方法一定要
public
这样的公有方法才可以使用吗? - 标注的方法的入参一定要继承了
ApplicationEvent
的参数吗?可以有多个参数吗 - 标注的方法可以有返回值吗,有返回值的话,那可以用了做什么?
那么这里的答案是:
- 标注的方法可以不是
public
修饰的公有方法,甚至可以是private
方法 - 标注的方法的入参可以
String
等其他参数,但是只能有一个入参,多个参数会解析失败 - 标注的方法可以有返回值,该返回值如果不是
ApplicationEvent
的子类,将会被包装成其子类PayloadApplicationEvent
进行事件的传递,也就是说可以不用在PointApplicationLister
类中注入ApplicationEventPublisher
进行事件的发布推送。
源码解析
查阅源码可使用的快捷键
ctrl+N
:查找类ctrl+F12
: 在指定类中查找方法、属性等ctrl+shift+F7
: 查找文件是否存在指定字符串alt+F7
: 查找类在其他类出现的文件
前言
环境:
Spring Boot 3.3.2
JDK17
首先,我们可以了解以下类的相关关系:
类 | 描述 |
---|---|
ApplicationEventPublisher | 封装装事件发布功能的接口 |
ApplicationEventMulticaster | 可以管理多个ApplicationListener对象并向其发布事件的对象实现的接口,一个典型的例子就是,ApplciationContext 实现了ApplicationEventPublisher 接口,并在其内部实现中,使用了ApplicationEventMulticaster 作为实际发布事件的发布者。 |
SimpleApplicationEventMulticaster | 实现了ApplicationEventMulticaster 接口,它是Spring Event 体系的实际发布者 |
ApplicationListener | : Application Event listeners监听器接口,在Spring 6.1 之后添加了supportsAsyncExecution 方法,来控制该监听器是否支持异步执行,默认是true ,支持异步执行。 |
PayloadApplicationEvent | 是ApplicationEvent 的子类,它对非ApplicationEvent 的类进行了封装,使得可以在各个监听器中进行事件传播。 |
SmartInitializingSingleton | 在单例实例化阶段结束之后触发的接口,此接口可以由单例bean实现,以便在常规单例实例化算法之后执行一些初始化。 |
EventListenerMethodProcessor | BeanFactoryPostProcessor 后置处理器,将 EventListener 方法注册为单独的ApplicationListener |
EventListenerFactory | 将EventListener 方法创建成ApplicationListener 实例的策略工厂接口 |
ApplicationListenerMethodAdapter | GenericApplicationListener 适配器,将事件的处理委托给@EventListener 注释的方法。同时可以处理其返回值的,将非ApplicationEvent 子类的返回值包装成PayloadApplicationEvent ,允许方法入参为非ApplicationEvent 子类 |
@TransactionalEventListener | 根据TransactionPhase调用的EventListener,添加事务控制,如果发布事件的方法没有添加添加事务,默认不会执行Listener中代码 |
Event的执行顺序如下:
在AbstractApplicationContext#refresh
方法中有以下代码:
- 首先先初始化
ApplicationEventMulticaster
- 接着将实现了
ApplicationListener
接口的Bean注册到ApplicationEventMulticaster
中,并发布早期的事件。 - 然后在
finishBeanFactoryInitialization
方法中的beanFactory.preInstantiateSingletons()
代码执行afterSingletonsInstantiated
方法,而EventListenerMethodProcessor
实现了EventListenerMethodProcessor
接口,所以在这里进行@TransactionalEventListener
和@EventListener
注解的解析
ApplicationEventMulticaster的初始化
在org.springframework.context.support.AbstractApplicationContext#initApplicationEventMulticaster
中方法的代码如图:
在该方法中,可以看出源码首先会从beanFactory
容器中查找是否存在一个名称APPLICATION_EVENT_MULTICASTER_BEAN_NAME
即applicationEventMulticaster
的Bean
- 如果有,那么将其赋值到
this.applicationEventMulticaster
中 - 否则,则创建一个
SimpleApplicationEventMulticaster
将赋值,同时将它注册到容器中,Bean的名称为applicationEventMulticaster
从这里可以看出,想要覆盖默认创建的SimpleApplicationEventMulticaster
一种方式是需要创建一个名称为applicationEventMulticaster
并且实现了ApplicationEventMulticaster
的Bean
类似以下代码:
@Bean
public ApplicationEventMulticaster applicationEventMulticaster(@Qualifier("taskExecutor") Executor executor) {
SimpleApplicationEventMulticaster multicaster = new SimpleApplicationEventMulticaster();
multicaster.setTaskExecutor(executor);
return multicaster;
}
SimpleApplicationEventMulticaster实现类发布事件
SimpleApplicationEventMulticaster
是Spring事件发布默认的一个实现类。发送事件部分源码如下:
这里getApplcationListeners
方法会找到对应的listener列表进行循环,每一个listener都会判断是否存在线程池,同时调用其supportsAsyncExecution
判断该listener是否支持异步执行。
在Spring 6.1之前是没有supportsAsyncExecution
,所以只要有executor,那么所有的listener都会异步执行。而这个executor来源是SimpleApplicationEventMulticaster
一个taskExecutor
属性,在上述的ApplicationEventMulticaster
的初始化中可以看出,Spring默认没有给SimpleApplicationEventMulticaster
添加Executor
,如果在这里给予线程池,那么会导致异步Listener关于事务功能失效
关于异步的相关功能在 异步事件 与 @Async 中介绍
EventListenerMethodProcessor解析@EventListener注解
EventListenerMethodProcessor
的afterSingletonsInstantiated
方法中执行了processBean
方法,部分源码如下:
这里找到了有@EventListener
标注的方法进行轮询,找到对应支持该方法的工厂类,执行factory.createApplicationListener
代码创建对应的listener并添加到当前的SimpleApplicationEventMulticaster
对象中。然后退出当前循环。
这里的有一点EventListenerFactory
的顺序问题如何确定,可以看EventListenerMethodProcessor#postProcessBeanFactory
中的AnnotationAwareOrderComparator.sort(factories)
进行了排序
相关工厂的实现类都实现了Ordered
接口,其中DefaultEventListenerFactory
的顺序最小,排到最后,该工厂类,支持所有方法。而TransactionalEventListenerFactory
则支持标注了@TransactionalEventListener
的方法。
也就是说:如果factoriesz只有DefaultEventListenerFactory
工厂时,@TransactionalEventListener
标注的方法也会跟@EventListener
的方法走相同的逻辑,就是普通的监听器方法。
接下来看DefaultEventListenerFactory
的createApplicationListener方法,该方法创建了一个实现了ApplicationListener
接口的类:ApplicationListenerMethodAdapter
,通过名字可以判断这是将标注了@EventListener
方法转成ApplicationListener
接口的适配器类。
该类创建如下:
将method存储了下来,用于反射调用。
重写了onApplicationEvent
方法,但是没有重写supportsAsyncExecution
方法,这也意味着一旦SimpleApplicationEventMulticaster
配置了线程池,那么@EventListener
标注的方法都会是异步,这会导致有事务的listener事务失效
非public方法、入参和返回值的源码解析
由上可知,监听器的功能在processEvent
方法中
依次通过上述三张图可以看出,适配器是会处理带有返回值的方法的,而handleResult
方法则会执行到AbstractApplicationContext#publishEvent
方法,将不是ApplicationEvent
子类的结果参数包装成PayloadApplicationEvent
类进行事件发布。
图二中的ReflectionUtils.makeAccessible(this.method)
代码设置方法是可访问的,然后反射调用,那么无论是不是AppplicationEvent
的子类都可以正常调用。
所以,我们使用以下代码进行测试
@Component
@Slf4j
public class NonApplicationEventListener {
@EventListener
private String test(MyEvent myEvent) {
log.info("测试private,并返回字符串: {}",myEvent);
return "发送字符串";
}
@EventListener
public void handleString(String string) {
log.info(string);
}
}
异步事件 与 @Async
在Spring Boot中,有个TaskExecutionAutoConfiguration
类,当不存在ThreadPoolTaskExecutor
类时候自动配置默认ThreadPoolTaskExecutor
Bean,名称为applicationTaskExecutor
别名为taskExecutor
。
可以通过以下代码看到效果
@SpringBootApplication(exclude = DataSourceAutoConfiguration.class)
public class NewEventApplication {
public static void main(String[] args) {
ConfigurableApplicationContext context = new SpringApplicationBuilder(NewEventApplication.class)
.web(WebApplicationType.NONE)
.run(args);
System.out.println(Arrays.toString(context.getAliases("applicationTaskExecutor")));
System.out.println(context.getBean("applicationTaskExecutor").getClass());
}
}
由于Spring默认是不带线程池的,可以通过自定义Bean注入,根据线程的自动配置,我们可以这样配置
@Bean
public ApplicationEventMulticaster applicationEventMulticaster(ThreadPoolTaskExecutor executor) {
SimpleApplicationEventMulticaster multicaster = new SimpleApplicationEventMulticaster();
multicaster.setTaskExecutor(executor);
return multicaster;
}
这样,事件会变成异步,那么就会导致异步的事件失效,当然,在Spring 6.1,对于通过实现了ApplicationListener
接口的类,可以通过实现supportsAsyncExecution
方法来阻止该监听器的异步, 如下:
static class SmsApplicationLister implements ApplicationListener<MyEvent> {
@Override
public void onApplicationEvent(MyEvent event) {
log.info("发送短信...");
}
@Override
public boolean supportsAsyncExecution() {
return false;
}}
然而,对于@EventListener
标注的方法,由于ApplicationListenerMethodAdapter
类没有重写supportsAsyncExecution
,所以一旦配置线程池,该类事件事务都会失效,可以猜测之后@EventListener
可能也会添加supportsAsyncExecution
属性,用于配置是否异步。
@Async
在使用@EnableAsync
之前,我们可以通过断点,再看一下之前源码,我们的Listener长什么样子
可以看出现在的Listener不是一个代理类,接下来有以下代码:
@SpringBootApplication(exclude = DataSourceAutoConfiguration.class)
@EnableAsync
public class NewEventApplication {
public static void main(String[] args) {
ConfigurableApplicationContext context = new SpringApplicationBuilder(NewEventApplication.class)
.web(WebApplicationType.NONE)
.run(args);
context.addApplicationListener(new SmsApplicationLister());
MyService myService = context.getBean(MyService.class);
myService.doBusiness();
}
@Getter
static class MyEvent extends ApplicationEvent {
private final Integer point;
public MyEvent(Object source, Integer point) {
super(source);
this.point = point;
} }
@Slf4j
@Order(1)
static class SmsApplicationLister implements ApplicationListener<MyEvent> {
@Override
@Async
public void onApplicationEvent(MyEvent event) {
log.info("发送短信...");
}
@Override
public boolean supportsAsyncExecution() {
return false;
}
}
@Slf4j
@Component static class EmailApplicationLister implements ApplicationListener<MyEvent> {
@Override
@Async
public void onApplicationEvent(MyEvent event) {
log.info("发送邮件...");
}
@Override
public boolean supportsAsyncExecution() {
return false;
}
}
@Component
@Slf4j
static class MyService {
@Resource
private ApplicationEventPublisher publisher;
public void doBusiness() {
log.info("主线业务");
MyEvent myEvent = new MyEvent(this, 10);
publisher.publishEvent(myEvent);
}
}
}
注意:SmsApplicationLister
是通过addApplicationListener
方式添加进去的,而EmailApplicationLister
类标注了@Component
,通过断点可以发现EmailApplicationLister
变成了由JdkDynamicAopProxy
增强的代理,而SmsListener
还是个原始类
可以看出只有一个实现了异步,那么为什么会这样子呢,我们继续进入代码getApplicationListeners
方法,F7
和F8
继续断点继续阅读源码。
在retrieveApplicationListeners
,也就是listener有两套机制,其中listenerBeans
会查找容器中的Bean对象,EmailApplicationLister
的代理对象也就是从这里获得的,所以在方法调用的时候,代理对象会解析@Async
注解的内容。
也就是说,如果SimpleApplicationEventMulticaster
配置了线程池,同时ApplicationListener
实例的onApplicationEvent
方法标注了@Async
注解,那么就会是 异步的异步。
同时,在这个方法也找到了对Listener进行排序的代码AnnotationAwareOrderComparator.sort(allListeners);
上面的是ApplcationListener
实例,是JDK动态代理,而@EventListener
标注的方法则GBlib代理,依旧是从org.springframework.context.event.ApplicationListenerMethodAdapter#doInvoke
方法断点得出
@Async中配置线程池
通过断点可以发现,在AnnotationAsyncExecutionInterceptor#getExecutorQualifier
方法中会解析@Async
中的方法以获取对应的线程池对象。
在AsyncExecutionInterceptor#getDefaultExecutor
会获取默认的线程对象,如果没有,则创建一个SimpleAsyncTaskExecutor
对象
进入父类AsyncExecutionAspectSupport#getDefaultExecutor
方法可以了解到,Executor
的获取流程
通过上面的源码可以得出,如果配置了多个TaskExecutor
的Bean,并且没有一个Bean
的名称叫taskExeutor
那么该方法就会返回null
,由于返回了null,所以最终默认的
是SimpleAsyncTaskExecutor
对象。
制造出这种情况,可以使用在代码中配置两个TaskExecutor
的Bean,这会覆盖掉TaskExecutionAutoConfiguration
中自动配置,从而没有taskExecutor
该名称的TaskExecutor
Bean
@Bean
public ThreadPoolTaskExecutor silver() {
return new ThreadPoolTaskExecutorBuilder()
.corePoolSize(3)
.maxPoolSize(10)
.queueCapacity(100)
.threadNamePrefix("silver")
.build();
}
@Bean
public TaskExecutor gravel() {
return new ThreadPoolTaskExecutorBuilder()
.corePoolSize(3)
.maxPoolSize(10)
.queueCapacity(100)
.threadNamePrefix("gravel")
.build();
}
这三个类的关系如下:AsyncExecutionAspectSupport
-> AsyncExecutionInterceptor
-> ``AnnotationAsyncExecutionInterceptor`
在AsyncExecutionAspectSupport#determineAsyncExecutor
方法中可以看到相关逻辑,@Async
注解上线程池的名称,如果不存在,会走其他方式获取一个线程池。
事务、异步、@TranscationEventListener
我们知道,Spring 的事务在某些条件下,是会失效的,其中一种情况是异步,如
@Slf4j
public class OrderService {
@Resource
private OrderRepository orderRepository;
@Resource
private ApplicationEventPublisher applicationEventPublisher;
@Transactional(rollbackFor = Exception.class)
public void save(OrderPO orderPO) {
orderRepository.save(orderPO);
UserPointEvent event = new UserPointEvent(this, orderPO.getUserId(), orderPO.getId());
applicationEventPublisher.publishEvent(event);
log.info("order end");
}}
@Component
@Slf4j
public class UserPointListener {
@EventListener
@Async
public void handleUserPoint(UserPointEvent event) {
log.info("event execute");
// 可能是别的有关数据库操作的代码
throw new RuntimeException("报错");
}}
这里orderRepository#save
保存是不会回滚的。如果我们注释@Async
,那么方法就会回滚,此时我们注意一下日志,代码不会输出order end
这条日志。
在学习@EventListener
注解中,会发现它有一个派生注解@TranscationEventListener
,它比@EventListener
多出了两个值:ransactionPhase phase() default TransactionPhase.AFTER_COMMIT
和boolean fallbackExecution() default false
首先解释一下fallbackExecution
,它表示如果发布事件的方法没有事务是否依旧执行,默认是false
不执行。即:OrderService#save
方法上没有事务,那么该listener不执行功能
@Component
@Slf4j
public class UserPointListener {
@TransactionalEventListener
// @Async
public void handleUserPoint(UserPointEvent event) {
log.info("event execute");
throw new RuntimeException("报错");
}}
将OrderService#save
方法标记事务,看一下日志:
在有事务的情况下,会先OrderService#save
中的内容,再回调handleUserPoint
方法中内容。这与@EventListener
有着这样的一个执行顺序区别。如果没有事务,而是fallbackExecution
值为true
,则与@EventListener
执行顺序无区别。
但是@TranscationEventListener
还有着一个phase
属性。TransactionPhase
枚举有四个值:BEFORE_COMMIT
、AFTER_COMMIT
、AFTER_ROLLBACK
以及AFTER_COMPLETION
。phase
的默认值是AFTER_COMMIT
。
这里有什么意义呢,我们将UserPointListener
代码改造成这样:
@Component
@Slf4j
public class UserPointListener {
@Resource
private UserPointService userPointService;
private final ThreadLocalRandom random = ThreadLocalRandom.current();
@TransactionalEventListener
// @Async
public void handleUserPoint(UserPointEvent event) {
log.info("event execute");
UserPointPO userPointPO = new UserPointPO();
userPointPO.setPoint(random.nextInt(800,16767));
userPointPO.setId(1);
userPointPO.setUserId(1);
userPointService.save(userPointPO);
userPointService.query(event.getUserId());
}}
我们在这个事件中加上了对数据库的查询,使用的JPA,会发生这样的问题: no transaction is in progress
这是因为phase
默认值为AFTER_COMMIT
,所以listener在是事务提交之后才执行的,就算同一个线程,后续的操作,这个线程也是没有了事务,所以会报错。同理,AFTER_ROLLBACK
,AFTER_COMPLETION
也是如此。
解决这个问题方法是在这个事务,在执行数据库操作的方法添加@Transactional(propagation = Propagation.REQUIRES_NEW,rollbackFor = Exception.class)
,在没有事务的时候开启一个新的事务。
@TransactionalEventListener
@Transactional(propagation = Propagation.NOT_SUPPORTED,rollbackFor = Exception.class)
@Async
public void handleUserPoint(UserPointEvent event) {
log.info("event execute");
UserPointPO userPointPO = new UserPointPO();
userPointPO.setPoint(random.nextInt(800,16767));
userPointPO.setId(1);
userPointPO.setUserId(1);
userPointService.save(userPointPO);
userPointService.query(event.getUserId());
}
也可以是@TransactionalEventListener(phase = TransactionPhase.BEFORE_COMMIT)
@TransactionalEventListener(phase = TransactionPhase.BEFORE_COMMIT)
// @Async
public void handleUserPoint(UserPointEvent event) {
log.info("event execute");
UserPointPO userPointPO = new UserPointPO();
userPointPO.setPoint(random.nextInt(800,16767));
userPointPO.setId(1);
userPointPO.setUserId(1);
userPointService.save(userPointPO);
userPointService.query(event.getUserId());
}
注意:第二段代码是和发布事件的方法处于同一个事务中,后续报错,orderRepository.save
方法会回滚,但是如果加上@Async
,那么事务就会失效,也就不会回滚。
第一段代码后续的事件是开启一个新事务了,与前面的事务没有关系了,所以使用不使用@Async
都一样,后续代码报错,orderRepository.save
方法也不会回滚。
通过伪代码的描述大致如下:
public void test(){
try{
// orderRepository.save
save();
// 事务提交
commit();
}
catch(Exception e){
rollback();
}
// 开启一个新事务
try{
handleUserPoint();
// 事务提交
commit();
}catch(Exception e){
rollback();
}
}
public void test(){
try{
// orderRepository.save
save();
// @TransactionalEventListener(phase = TransactionPhase.BEFORE_COMMIT)
handleUserPoint();
// 事务提交
commit();
}
catch(Exception e){
rollback();
}
}
代码
关于@TransactionalEventListener
的部分源码
java-skill-learn/event-spring-boot-study at main · DawnSilverGravel/java-skill-learn (github.com)
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.3.2</version>
</parent>
<properties>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>org.liquibase</groupId>
<artifactId>liquibase-core</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
</dependencies>
参考链接
167-第卌八讲-事件监听器1_哔哩哔哩_bilibili 通过这里的视频,再看我这里的总结,可能更容易理解一些,在该老师的讲解下,进行了拓展和延伸。