17 Spring事件发布详解
本文内容
- 实现原理和标准事件
- 编程式实现自定义事件
- 基于注解的自定义事件
- 通用事件
实现原理和标准事件
Spring中的事件发布本质上是标准的观察者设计模式。ApplicationContext 中的事件处理是通过 ApplicationEvent 类和 ApplicationListener 接口提供的。如果将实现 ApplicationListener 接口的bean部署到上下文中,则每次将 ApplicationEvent 发布到 ApplicationContext 时,该bean都会得到通知。
public abstract class ApplicationEvent extends EventObject {
// 事件源
public ApplicationEvent(Object source) {
super(source);
this.timestamp = System.currentTimeMillis();
}
public final long getTimestamp() {
return this.timestamp;
}
}
@FunctionalInterface
public interface ApplicationListener<E extends ApplicationEvent> extends EventListener {
// 处理事件
void onApplicationEvent(E event);
}
下表描述了 Spring 提供的标准事件:
事件 | 解释 |
---|---|
ContextRefreshedEvent | 在初始化或刷新时发布ApplicationContext (例如,通过使用接口refresh() 上的方法ConfigurableApplicationContext )。这里,“初始化”意味着所有 bean 都已加载,后处理器 bean 被检测并激活,单例被预先实例化,并且ApplicationContext 对象已准备好使用。只要上下文没有关闭,就可以多次触发刷新,前提是所选择的ApplicationContext 实际支持这种“热”刷新。例如,XmlWebApplicationContext 支持热刷新,但 GenericApplicationContext 不支持。 |
ContextStartedEvent | 使用接口上的方法 ApplicationContext 启动时发布。在这里,“已启动”意味着所有 bean 都接收到一个明确的启动信号。通常,此信号用于在显式停止后重新启动 bean,但它也可用于启动尚未配置为自动启动的组件(例如,尚未在初始化时启动的组件)。start()``ConfigurableApplicationContext``Lifecycle |
ContextStoppedEvent | 使用接口上的方法 ApplicationContext 停止时发布。在这里,“停止”意味着所有 的 bean 都会收到一个明确的停止信号。可以通过 调用重新启动已停止的上下文。stop()``ConfigurableApplicationContext``Lifecycle``start() |
ContextClosedEvent | 在ApplicationContext 使用接口close() 上的方法ConfigurableApplicationContext 或通过 JVM 关闭挂钩关闭时发布。在这里,“关闭”意味着所有的单例 bean 都将被销毁。一旦上下文关闭,它就到了生命的尽头,无法刷新或重新启动。 |
RequestHandledEvent | 一个特定于 Web 的事件,告诉所有 bean 一个 HTTP 请求已得到服务。此事件在请求完成后发布。此事件仅适用于使用 Spring 的 Web 应用程序DispatcherServlet 。 |
ServletRequestHandledEvent | 它的子类RequestHandledEvent 添加了 Servlet 特定的上下文信息。 |
编程式实现自定义事件
定义事件继承 ApplicationEvent
public class BlockedListEvent extends ApplicationEvent { private final String address; private final String content; public BlockedListEvent(Object source, String address, String content) { super(source); this.address = address; this.content = content; } public String getAddress() { return address; } public String getContent() { return content; } }
定义事件监听器实现 ApplicationListener
@Component public class BlockedListListener implements ApplicationListener<BlockedListEvent> { // @Async @Override public void onApplicationEvent(BlockedListEvent event) { System.out.println("收到邮件禁用发送通知,地址:" + event.getAddress() + " 内容:" + event.getContent()); } }
业务类注入 ApplicationEventPublisher 用于发布事件
@Component public class EmailSendService { @Autowired private ApplicationEventPublisher publisher; // 邮件黑名单 private List<String> blockedList; public void sendEmail(String address, String content) { if (blockedList.contains(address)) { System.out.println("不允许发邮件,同步发布通知事件"); publisher.publishEvent(new BlockedListEvent(this, address, content)); System.out.println("同步发布通知事件结束"); return; } // 允许 System.out.println("允许发邮件"); } public void setBlockedList(List<String> blockedList) { this.blockedList = blockedList; } }
测试程序和结果
@Test
public void test_synchronous() {
AnnotationConfigApplicationContext context =
new AnnotationConfigApplicationContext(AppConfig.class);
EmailSendService sendService = context.getBean(EmailSendService.class);
sendService.setBlockedList(Arrays.asList("123"));
sendService.sendEmail("123","ooo");
sendService.sendEmail("456","ooo");
}
不允许发邮件,同步发布通知事件
收到邮件禁用发送通知,地址:123 内容:ooo
同步发布通知事件结束
允许发邮件
从结果分析,publishEvent()
发布事件的方式是阻塞同步的,期间会等待所有的事件监听器完成处理。
基于注解的自定义事件
从Spring 4.2开始,事件基础结构得到了显著的改进,并提供了一个基于注释的模型,以及发布任何任意事件(也就是说,一个不一定是从ApplicationEvent扩展而来的对象)的能力。当这样一个对象被发布时会被包装在一个事件中。
可以使用 @EventListener 注释在托管 bean 的任何方法上注册事件监听器。
@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface EventListener {
/**
* Alias for {@link #classes}.
*/
@AliasFor("classes")
Class<?>[] value() default {};
// 监听哪些事件
// 如果使用单个值指定该属性,则带注释的方法可以选择接受单个参数。
// 但是,如果使用多个值指定此属性,则带注释的方法不能声明任何参数
@AliasFor("value")
Class<?>[] classes() default {};
// SpEL表达式条件
String condition() default "";
}
监听单个事件
@Component
public class AnnotatedBlockedListListener{
@EventListener(value = BlockedListEvent.class)
public void onApplicationEvent(BlockedListEvent event) {
System.out.println("AnnotatedBlockedListListener收到邮件禁用发送通知,地址:" + event.getAddress() + " 内容:" + event.getContent());
}
}
监听多个事件
如果使用多个值指定监听多个事件,则带注释的方法不能声明任何参数。
@Component
public class MultiEventListener {
@EventListener({ContextStartedEvent.class, ContextRefreshedEvent.class})
public void handleContextStart() {
System.out.println("收到 ContextStartedEvent 或 ContextRefreshedEvent");
}
}
监听方法结果作为新事件发布
监听方法处理后返回值如果是 ApplicationEvent 的实现类,Spring会将结果当做新事件来发布。支持单个或是集合形式,集合形式的返回值会拆分成单个依次发送。直接看案例。
定义一个结果事件 ResultEvent 继承 ApplicationEvent
1. public class ResultEvent extends ApplicationEvent { private final String result; public ResultEvent(Object source, String result) { super(source); this.result = result; } public String getResult() { return result; } }
定义 BlockedListEvent 的监听器返回值是单个 ApplicationEvent
@Component
public class AnnotatedBlockedListListener2 {
@EventListener(value = BlockedListEvent.class)
public ResultEvent onApplicationEvent(BlockedListEvent event) {
System.out.println("邮件处理完毕,返回单个通知ResultEvent事件");
return new ResultEvent(event.getSource(), "success");
}
}
定义 BlockedListEvent 的监听器返回值集合形式的 ApplicationEvent
@Component public class AnnotatedBlockedListListener3 { @EventListener(value = BlockedListEvent.class) public List<ResultEvent> onApplicationEvent(BlockedListEvent event) { System.out.println("邮件处理完毕,返回多个通知ResultEvent事件"); List<ResultEvent > resultEvents = new ArrayList<>(5); for (int i = 0; i < 5; i++) { resultEvents.add(new ResultEvent(event.getSource(), "success:" + i)); } return resultEvents; } }
定义 ResultEvent 的监听器
@Component public class ResultEventListener { @EventListener(ResultEvent.class) public void onApplicationEvent(ResultEvent event) { System.out.println("收到ResultEvent,结果:" + event.getResult()); } }
运行结果
AnnotatedBlockedListListener收到邮件禁用发送通知,地址:123 内容:ooo 邮件处理完毕,返回单个通知ResultEvent事件 收到ResultEvent,结果:success 邮件处理完毕,返回多个通知ResultEvent事件 收到ResultEvent,结果:success:0 收到ResultEvent,结果:success:1 收到ResultEvent,结果:success:2 收到ResultEvent,结果:success:3 收到ResultEvent,结果:success:4 收到邮件禁用发送通知,地址:123 内容:ooo 同步发布通知事件结束 允许发邮件
从结果看,AnnotatedBlockedListListener2 和 AnnotatedBlockedListListener3 的监听方法结果被当做新的事件发布了,并被 ResultEventListener 监听到后处理。
使用SpEL表达式
@EventListener 中的 condition
可以指定SpEL表达式条件。如当事件内容是“ooo”时才监听器方法才处理该事件。实现如下
@Component
public class ConditionalBlockedListListener{
// 当事件内容是ooo 才处理
@EventListener(value = BlockedListEvent.class, condition = "#event.content == 'ooo'")
public void onApplicationEvent(BlockedListEvent event) {
System.out.println("当事件内容是ooo 才处理");
}
}
下表列出了可用于上下文的项目,可以用于condition
中:
名称 | 位置 | 描述 | 例子 |
---|---|---|---|
事件 | root object | ApplicationEvent 对象 | #root.event or event |
参数数组 | root object | 方法的参数数组 | #root.args 或者args ; args[0] 按照索引取 |
参数名称 | context | 任何方法参数的名称。如果,由于某些原因,名称不可用(例如,因为编译后的字节码中没有调试信息),则使用#a<#arg>语法也可以使用单个参数,其中<#arg>表示参数索引(从0开始)。 | #blEvent 或是#a0 |
异步事件
默认情况下,上面的案例的事件发布和事件处理都是同步阻塞完成的。可以使用 @Async 来完成异步处理事件。
@Component
@EnableAsync // 启用Spring异步处理
public class AnnotatedBlockedListListener4 {
@Async // 标记方法为异步
@EventListener(value = BlockedListEvent.class)
public void onApplicationEvent(BlockedListEvent event) {
System.out.println("异步处理收到邮件禁用发送通知,地址:" + event.getAddress() + " 内容:" + event.getContent());
}
}
使用异步事件时请注意以下限制:
- 如果异步事件侦听器抛出异常,它不会传播给调用者。需要使用 AsyncUncaughtExceptionHandler 来处理异常,后面专门详解。
- 异步事件侦听器方法不能通过返回值来发布后续事件。如果需要发布另一个事件作为处理的结果,请注入一个 ApplicationEventPublisher 以手动发布该事件。
使用@Order定义同一事件监听器
同一个事件多个监听器时候,@Order 值较小的先收到。
@Order(3)
@EventListener(value = BlockedListEvent.class)
public void onApplicationEvent(BlockedListEvent event) {
System.out.println("AnnotatedBlockedListListener收到邮件禁用发送通知,地址:" + event.getAddress() + " 内容:" + event.getContent());
}
@Order(2)
@EventListener(value = BlockedListEvent.class)
public ResultEvent onApplicationEvent(BlockedListEvent event) {
System.out.println("邮件处理完毕,返回单个通知ResultEvent事件");
return new ResultEvent(event.getSource(), "success");
}
@Order(1)
@EventListener(value = BlockedListEvent.class)
public List<ResultEvent> onApplicationEvent(BlockedListEvent event) {
System.out.println("邮件处理完毕,返回多个通知ResultEvent事件");
List<ResultEvent > resultEvents = new ArrayList<>(5);
for (int i = 0; i < 5; i++) {
resultEvents.add(new ResultEvent(event.getSource(), "success:" + i));
}
return resultEvents;
}
观察下输出
邮件处理完毕,返回多个通知ResultEvent事件
邮件处理完毕,返回单个通知ResultEvent事件
AnnotatedBlockedListListener收到邮件禁用发送通知,地址:123 内容:ooo
泛型化事件结构
使用泛型事件
可以使用泛型进一步定义事件的结构。直接看案例。
实体创建事件使用 EntityCreatedEvent<T>
,其中 T 是创建的实际实体的类型
public class EntityCreatedEvent<T> extends ApplicationEvent {
public EntityCreatedEvent(T source) {
super(source);
}
}
假设有2个是实体 Order 和 Person, 则它们的创建事件监听方法如下:
@EventListener(PersonEntityCreatedEvent.class)
public void onPersonCreated(EntityCreatedEvent<Person> event) {
System.out.println("onPersonCreated");
}
@EventListener(OrderEntityCreatedEvent.class)
public void onOrderCreated(EntityCreatedEvent<Order> event) {
System.out.println("onOrderCreated");
}
由于Java中的类型擦除,这仅在触发的事件解析事件侦听器过滤的通用参数时才有效。对应的定义一下2个类:
public class OrderEntityCreatedEvent extends EntityCreatedEvent<Order>{
public OrderEntityCreatedEvent(Order source) {
super(source);
}
}
public class PersonEntityCreatedEvent extends EntityCreatedEvent<Person>{
public PersonEntityCreatedEvent(Person source) {
super(source);
}
}
测试程序和测试结果
public class EntityCreatedEventTest {
@Test
public void test() {
AnnotationConfigApplicationContext context =
new AnnotationConfigApplicationContext(AppConfig.class);
context.publishEvent(new OrderEntityCreatedEvent(new Order()));
context.publishEvent(new PersonEntityCreatedEvent(new Person()));
context.close();
}
}
onOrderCreated
onPersonCreated
使用ResolvableTypeProvider优化
前面的案例,每个实体类A都需要定义一个对应的具体事件类EntityCreatedEvent<A>
,这样模板化的机械化的工作某种程度上非常乏味。在这种情况下,可以实现 ResolvableTypeProvider 来引导框架超出运行时环境提供的范围,就是运行时指定类型。
泛型事件实现 ResolvableTypeProvider
/**
* 泛型的实体创建事件
* @author zfd
* @version v1.0
* @date 2022/1/26 15:09
*/
public class ResolvableEntityCreatedEvent<T> extends ApplicationEvent implements ResolvableTypeProvider {
public ResolvableEntityCreatedEvent(T source) {
super(source);
}
@Override
public ResolvableType getResolvableType() {
return ResolvableType.forClassWithGenerics(getClass(), ResolvableType.forInstance(getSource()));
}
}
省去了实体对应具体事件的定义,直接上监听方法
@Component
public class ResolvableEntityCreatedEventListener {
@EventListener
public void onPersonCreated(ResolvableEntityCreatedEvent<Person> event) {
System.out.println("onPersonCreated Resolvable");
}
@EventListener
public void onOrderCreated(ResolvableEntityCreatedEvent<Order> event) {
System.out.println("onOrderCreated Resolvable");
}
}
观察下结果
@Test
public void test_resolvable() {
AnnotationConfigApplicationContext context =
new AnnotationConfigApplicationContext(AppConfig.class);
context.publishEvent(new ResolvableEntityCreatedEvent<>(new Order()));
context.publishEvent(new ResolvableEntityCreatedEvent(new Person()));
context.close();
}
// 结果符合预期
onOrderCreated Resolvable
onPersonCreated Resolvable
事件发布任意对象
原理分析
从Spring 4.2开始,事件基础结构得到了显著的改进,并提供了一个基于注释的模型,以及发布任何任意事件(也就是说,一个不一定是从ApplicationEvent扩展而来的对象)的能力。当这样一个对象被发布时会被包装在一个事件中。
调用 ApplicationEventPublisher#publishEvent(event) 发布事件时,如果event本身继承了 ApplicationEvent正常发送,如果 event 没有继承 ApplicationEvent 则将被包装成 PayloadApplicationEvent 来发送。
看下源码实现 AbstractApplicationContext#publishEvent(Object event, @Nullable ResolvableType eventType)
protected void publishEvent(Object event, @Nullable ResolvableType eventType) {
Assert.notNull(event, "Event must not be null");
// Decorate event as an ApplicationEvent if necessary
ApplicationEvent applicationEvent;
// 1 本身实现了ApplicationEvent
if (event instanceof ApplicationEvent) {
applicationEvent = (ApplicationEvent) event;
}
else {
// 本身没有实现 ApplicationEvent 包装成 PayloadApplicationEvent
applicationEvent = new PayloadApplicationEvent<>(this, event);
if (eventType == null) {
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 {
// 3 广播发送
getApplicationEventMulticaster().multicastEvent(applicationEvent, eventType);
}
那么实现任意对象发布事件的关键是 PayloadApplicationEvent ,来看下其定义:
- 实现了 ApplicationEvent 可作为事件发布
- 泛型类,可包装任意事件内容
- ResolvableTypeProvider 上面提到的类型解析接口,实现任意发布的关键!
public class PayloadApplicationEvent<T> extends ApplicationEvent implements ResolvableTypeProvider {}
使用案例
定义任意的发布对象,不继承 ApplicationEvent
public class ArbitraryObject { private String name; public ArbitraryObject(String name) { this.name = name; } @Override public String toString() { return "ArbitraryObject{" + "name='" + name + '\'' + '}'; } }
定义监听器监听 PayloadApplicationEvent 事件,指定泛型
@Component public class PayloadApplicationEventListener { @EventListener public void onPayloadApplicationEvent(PayloadApplicationEvent<ArbitraryObject> event) { System.out.println("收到PayloadApplicationEvent:" + event.getPayload()); } }
发送事件观察结果
@Test public void test_arbitrary() { AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class); context.publishEvent(new ArbitraryObject("随意对象非ApplicationEvent事件")); context.close(); }
监听到的事件内容输出如下
收到PayloadApplicationEvent:ArbitraryObject{name='随意对象非ApplicationEvent事件'}
总结
本文详解分析了Spring的事件发布原理,自定义事件可通过编程式或是注解方式。也介绍了泛型事件结构以及如何发送任意的事件对象。