做变化的催化剂
Be a Catalyst for Change
你不能强迫人们改变。相反,要向他们展示未来可能会怎么样,并帮助他们参与未来的创造。
– 提示5 《程序员修炼之道 – 从小工到专家》
写在前面
前段时间发现Spring的Event超级好用,所以已经逐步在项目中加入了Spring Event的功能。
开发环境:
Java 1.8
Spring Boot 2.1.6.RELEASE
Spring 5.1.8.RELEASE
基本开发
Event是Spring中的概念,不是Spring Event所有的。只要添加了spring-context
依赖就可以引入了Spring的事件。
要使用Event只要准备三个部分:
- 事件类:定义事件,继承
ApplicationEvent
的类成为一个事件类。 - 发布者:发布事件,通过
ApplicationEventPublisher
发布事件。 - 监听者:监听并处理事件,实现
ApplicationListener
接口或者使用@EventListener
注解。
事件类
只要继承org.springframework.context.ApplicationEvent
,便是一个Spring Event类。一般我们会专门为一个类型的Event写一个抽象的事件类,作为该类型的所有事件的父类。
1 | /** |
然后定义具体的发布事件。这里推荐使用类实现的方式来发布具体的事件,而不是在事件中使用private String eventType
定义事件的类型。使用具体的类表示具体的事件,监听器只要监听具体的事件类即可,而无需再做判断,同时也不需要再另外维护事件类型列表。
1 | public class AccountCreatedEvent extends AccountEvent { |
还有一种实践是,利用泛型定义一个统一的父类。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21public abstract class BaseEvent<T> extends ApplicationEvent {
/**
* 该类型事件携带的信息
*/
private T eventData;
/**
*
* @param source 最初触发该事件的对象
* @param eventData 该类型事件携带的信息
*/
public BaseEvent(Object source, T eventData) {
super(source);
this.eventData = eventData;
}
public T getEventData() {
return eventData;
}
}
然后再指定事件的泛型类型即可。1
2
3
4
5
6
7
8
9
10
11public class AccountCreatedEvent extends BaseEvent<AccountEventData> {
public AccountCreatedEvent(Object source, AccountEventData eventData) {
super(source, eventData);
}
}
public class TodoCreatedEvent extends BaseEvent<TodoEventData> {
public TodoCreatedEvent(Object source, TodoEventData eventData) {
super(source, eventData);
}
}
以上均使用了一个AccountEventData
,这是为了方便拓展,如果后续需要给事件增加新的字段,可以直接在该类上增加即可,而无需修改所有的子事件类。
发布者
发布者负责发布消息,有三种实现方式。Spring容器中默认的ApplicationEventPublisher
是AbstractApplicationContext
,同时AbstractApplicationContext
也是ApplicationContext
的一个子类,也就是说,Spring默认使用AbstractApplicationContext
发布事件。
方式1:直接使用ApplicationEventPublisher
(推荐)1
2
3
4
5
6
7
8
9
10
11
12
13import org.springframework.context.ApplicationEventPublisher;
public class AccountsController {
"") (
public Account createAccount(@RequestBody Account account) {
...
publisher.publishEvent(new AccountCreatedEvent(this, new AccountEventData()));
return account;
}
}
方式2:实现ApplicationEventPublisherAware
接口(推荐)1
2
3
4public interface ApplicationEventPublisherAware extends Aware {
void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher);
}
这个方式其实就是注入一个ApplicationEventPublisher
,然后再用ApplicationEventPublisher#publisheEvent(ApplicationEvent)
方法发布事件。
1 | package org.springframework.data.rest.webmvc; |
如果你希望你的Service类能够发布事件,可以实现这个接口ApplicationEventPublisherAware
。
方式3:使用ApplicationContext#publishEvent(ApplicationEvent)
发布。1
2
3
4
5
6
7
8
9
10
11
12public class AccountEventPublisher {
private final ApplicationContext applicationContext;
public AccountEventPublisher(ApplicationContext context) {
this.applicationContext = context;
}
public void publish(TodoEvent ev) {
applicationContext.publishEvent(ev);
}
}
ApplicationContext
是ApplicationEventPublisher
的一个实现,在有前面的两种方案之后,其实就不需要这个重复封装实现方案了。当然,你也可以直接使用ApplicationContext
。
监听器
监听器负责接收和处理事件。
基本用法
有两种实现方法:实现ApplicationListener
接口或者使用@EventListener
注解。
实现ApplicationListener
接口:
1 | public class TodoFinishedListener implements ApplicationListener<TodoEvent.TodoFinishedEvent> { |
使用@EventListener
注解(推荐)1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 4j
public class SyncAccountListener {
/**
* 异步发送邮件
* @param event
*/
public void doOnNormalEvent(NormalAccountEvent event) {
try {
log.debug("befor");
Thread.sleep(1000);
log.debug("after");
} catch (InterruptedException e) {
log.error(e.getMessage(), e);
}
}
}
可以使用@EventListener
在同一个类中用不同的方法监听多个不同的事件。相对于实现ApplicationListener
接口,使用@EventListener
会更加灵活。
@EventListener
注解的说明
1 | "#event.account.age > 10") (value = {AccountCreatedEvent.class, AccountUpdatedEvent.class}, condition = |
value
: 监听的事件(组),用于支持同一父类的事件class
: 同valuecondition
: SpEL,使得Event Handler变得conditional#root.event
, Application的引用#root.args
, 表示方法参数,#root.args[0]表示第0个方法参数#<name>
, 如上面代码中的#event表示以参数名关联参数
ApplicationEventMulticaster 事件广播器
事件广播器负责将ApplicationEventPublisher
发布的事件广播给所有的监听器。如果没有提供事件广播器,Spring会自动使用SimpleApplicationEventMulticaster
作为默认的事件广播器。
构建事件基础
AbstractApplicationContext.java中的refresh()
方法中构建了完整的事件基础。AbstractApplicationContext#initApplicationEventMulticaster()
初始化了事件广播器,AbstractApplicationContext#registerListeners()
则负责添加Spring容器中的事件监听器。
1 | /** |
1 | /** |
事件发布
我们可以在AbstractApplicationContext.java
中找到事件的直接发布方法。AbstractApplicationContext#publish(Object, ResolvableType)
中,事件的发布是通过ApplicationEventMulticaster
做的广播发布。
1 | protected void publishEvent(Object event, @Nullable ResolvableType eventType) { |
在前面我们可以知道,SimpleApplicationEventMulticaster
是ApplicationEventMulticaster
在Spring容器中的默认实现。理所当然地可以从中找到事件发布的真实方式。multicastEvent
方法会找到监听当前事件的所有监听器,然后再执行执行监听方法。
SimpleApplicationEventMulticaster
中有两个属性,Executor taskExecutor
和ErrorHandler errorHandler
。前者可以定义所有监听器是否异步执行,默认为null,等价于同步执行的SyncTaskExecutor
,你也可以使用SimpleAsyncTaskExecutor
将所有监听器设置为异步执行。但这里有一点非常重要,如果你设置了Executor为异步的,那么所有的监听器都会异步执行,监听器和调用类会处于不同的上下文,不同的事务中,除非你有办法让TaskExecutor支持。其实我们完全不用通过修改广播器taskExecutor的方式来让监听器异步,可以通过@EnableAsync
启动异步,并@Async
将监听器设置为异步执行。通过@Async
的方式,可以自由地决定任意一个监听器是否为异步,而非暴力地让所有的监听器都异步化。
1 | public void multicastEvent(final ApplicationEvent event, @Nullable ResolvableType eventType) { |
而ErrorHandler errorHandler
则定义了在监听器发生异常之后的行为,在这里你可以看到,如果没有定义errorHandler
的话,会直接抛到上一层。
1 | protected void invokeListener(ApplicationListener<?> listener, ApplicationEvent event) { |
可以修改默认的ApplicationEventMulticaster
,或者直接继承/实现AbstractApplicationEventMulticaster/ApplicationEventMulticaster
。
1 | // 是谁,是谁让我所有的监听器都编程异步了。-》原来是有人修改了事件广播器的taskExecutor为异步的了。 |
@EnableAspectJAutoProxy(proxyTargetClass = true)
作用
同步
Listener默认都是同步的
1 | public Account createAccount(Account account) { |
以上的方法中,publishEvent
会执行完所有的同步监听器之后才返回。既然是同步,那么你可以通过@Order
注解来指定执行顺序。使用同步的监听器,可以让事件参与到publisher所在的事务中。从上面对ApplicationEventMulticaster
的讲解中可以知道,同步的执行其实就是简单的方法调用罢了。
异常与事务
上面已经讲到,同步的listener如果发生异常,而且没有被ErrorHandler拦截的话,是会往上抛出的,可以直接在publishEvent方法调用处捕获。
在同步的场景下,listener的执行,其实就是普通方法的调用。那么事务的控制也是和普通的方法调用是一样的。如果想要让监听器在事务中,那么就在监听器方法上添加事务注解@Transational
就可以了。具体的分析见Spring的事务传播行为。
异步
既然前面讲到,监听器的执行其实就是一个普通方法的执行,那么将监听器声明为异步的方法也会和将一个普通方法声明为异步的方法一样,使用@Async
。
需要明确的一点是,当监听器设置为异步之后,会导致该监听器方法和调用publishEvent的方法处于不同的事务中。其实就和普通异步没有太多区别啦。
使用@Async实现异步
启动异步
1 |
|
默认的AsyncConfigurer
是AsyncConfigurerSupport
,两个方法均返回了null。
设置一个监听器为异步
1 |
|
使用ApplicationEventMulticaster实现异步
为ApplicationEventMulticaster
指定一个异步的taskExecutor,将会让所有的监听器都变成异步执行。真心不推荐这种做法。
1 | public class AsyncConfig { |
事件事务管理 @TransactionalEventListener
定义
使用@TransactionalEventListener
是@EventListener
的拓展,可以指定监听器和发布事件的方法的事务隔离级别。隔离级别确保数据的有效性。@TransactionalEventListener
注解会将监听器声明为一个事务管理器(这部分有机会会在其他文章中说明)。
当一个监听器方法被@TransactionalEventListener
注解时,那么这个监听器只会在调用方为事务方法时执行,如果调用方是非事务方法,则无法该监听器不会被通知。值得注意的是,虽然@TransactionalEventListener
带有Transaction关键字,但这个方法并没有声明监听器为Transactional的。
1 |
|
配置 @TransactionalEventListener
除了含有与@EventListener
相同的属性(classes, condition),该注解还提供了另外的两个属性fallbackExecution
和phase
。
@TransactionalEventListener注解属性:fallbackExecution
定义:fallbackExecution
设置Listener是否要在没有事务的情况下处理event。
默认为false,表示当publishEvent所在方法没有事务控制时,该监听器不监听事件。通过设置fallbackExecution=true
,可以让Listener在任何情况都可以执行。
1 |
|
@TransactionalEventListener注解属性:phase
AFTER_COMMIT
- (default) 在事务完成之后触发事件。此时事务已经结束,监听器方法将找不到上一个事务AFTER_ROLLBACK
– 在事务回滚之后触发事件AFTER_COMPLETION
– 在事务完成之后触发事件 (含有AFTER_COMMIT
和AFTER_ROLLBACK
)BEFORE_COMMIT
- 在事务提交之前触发事件,此时调用方方法的事务还存在,监听器方法可以找到该事务
当你尝试在@TransactionEventListener
方法中执行JPA的save方法时,你会得到如下错误:
1 |
|
1 | Participating transaction failed - marking existing transaction as rollback-only |
原因是@TransactionalEventListener
默认是AFTER_COMMIT
,也就是当前事务已经结束了,所以无法找到所在事务,只能执行rollback,因而无法成功将数据保存到数据库中。但如果你希望执行findAll()方法,那么会拿到调用方提交到数据库中的数据,但也拿不到该Listener中save的数据。也许你会想,我在这个方法上@Transactional
不就可以了吗?目前的测试结果是也不行。具体原因会在写事务的文章中再另外讲解,这里暂不拓展。可以通过设置phase为TransactionPhase.BEFORE_COMMIT
进行解决,这样的话调用方的事务就还没有结束。
1 |
|
但这并不表示我们不应该使用 AFTER_COMMIT
,而是应该在合适的场景下使用。比如需要发送邮件。在下面的方法中,方法B会触发发送邮件,但由于方法A中还有操作,这些操作可能导致方法B回退,为了防止邮件提前发送(无法撤销操作),因此可以让发送邮件在事务提交之后再执行。
1 | methodA() { |