本文内容
- 实现原理和标准事件
- 编程式实现自定义事件
- 基于注解的自定义事件
- 通用事件
实现原理和标准事件
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;
}
}
4. 测试程序和结果
```java
@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 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;
}
}
2. 定义 BlockedListEvent 的监听器返回值是单个 ApplicationEvent
```java
@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
* @关于我 请关注公众号 螃蟹的Java笔记 获取更多技术系列
*/
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的事件发布原理,自定义事件可通过编程式或是注解方式。也介绍了泛型事件结构以及如何发送任意的事件对象。案例比较多,建议实践一下。
本篇源码地址: https://github.com/kongxubihai/pdf-spring-series/tree/main/spring-series-ioc/src/main/java/com/crab/spring/ioc/demo19 知识分享,转载请注明出处。学无先后,达者为先!
|