Spring 中的事件监听机制
ApplicationContext 中的事件处理是通过ApplicationEvent 类和ApplicationListener 接口实现的。如果一个 Bean(监听器实例)实现了ApplicationListener 接口,并被部署到 Spring 容器中,那么每次在容器中发布一个ApplicationEvent 事件,该 Bean (监听器实例)都会被通知到。本质上,这种机制就是一个观察者
目录
(2)ApplicationEventPublisher 发布事件
3、Asynchronous Listeners 异步监听器
1、标准的 Spring 事件机制
ApplicationContext 中的事件处理是通过 ApplicationEvent 类和 ApplicationListener 接口实现的。如果一个 Bean(监听器实例)实现了 ApplicationListener 接口,并被部署到 Spring 容器中,那么每次在容器中发布一个 ApplicationEvent 事件,该 Bean (监听器实例)都会被通知到。本质上,这种机制就是一个观察者的设计模式。// 事件、监听器、观察者模式。
在 Spring 4.2 中,事件的基础结构得到了显著的改进,并且提供了基于注解的模型以及发布任意事件的能力(也就是说,一个对象不一定继承 ApplicationEvent ),当这样的对象被发布时,Spring 为你将这个对象自动包装在一个事件中。// 发布一个不继承 ApplicationEvent 的事件?怎样实现,如果不继承 ApplicationEvent 怎么会知道它是一个事件的实例?
Spring 中提供了一些标准事件(内置事件),了解 Spring 中的内置事件,请点击这里。// 这里简单概述一下,主要有:
- 容器刷新事件(ContextRefreshedEvent)
- 容器启动事件(ContextStartedEvent)
- 容器停止事件(ContextStoppedEvent)
- 容器关闭事件(ContextClosedEvent)
- 请求处理事件(RequestHandledEvent)
- Servlet 请求处理事件(ServletRequestHandledEvent)
(1)ApplicationEvent 自定义事件
在 Spring 中,你可以创建和发布自己的自定义事件。下面的例子展示了一个继承了 Spring ApplicationEvent 类的自定义事件类:
import org.springframework.context.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;
System.out.println("实例化BlockedListEvent...");
}
// accessor and other methods...
}
(2)ApplicationEventPublisher 发布事件
如果要发布一个自定义的 ApplicationEvent 事件,需要调用 ApplicationEventPublisher 中的 publishEvent() 方法。通常情况下,会去创建一个新的类,然后通过实现 ApplicationEventPublisherAware 接口来获取一个默认的 Publisher,最后把该类注册为容器的一个 Bean。下边的例子展示了这样一个类:// 不直接使用 ApplicationContext 调用 publishEvent() 的原因,看起来有种最少知道原则的意思。
public class EmailService implements ApplicationEventPublisherAware {
private List<String> blockedList;
// 使用事件发布者,与 Spring 容器进行交互
private ApplicationEventPublisher publisher;
public void setBlockedList(List<String> blockedList) {
this.blockedList = blockedList;
}
public void setApplicationEventPublisher(ApplicationEventPublisher publisher) {
this.publisher = publisher; // 自动装配,从 Spring 容器中装配一个发布者到 EmailService
}
public void sendEmail(String address, String content) {
if (blockedList.contains(address)) {
// 发布事件
System.out.println("publish BlockedListEvent...");
publisher.publishEvent(new BlockedListEvent(this, address, content));
return;
}
// send email...
}
}
在上边例子中,Spring 容器会为 setApplicationEventPublisher() 方法自动注入 ApplicationEventPublisher 实例( Publisher 是 Spring 提供的,我们暂时不用关注),获取 ApplicationEventPublisher 后,我们可以通过 ApplicationEventPublisher 与 Spring 容器进行交互。// 对于 Spring 中 Publisher 的具体实现,在源码分析中,聊到了很多多播器,Spring 默认使用 SimpleApplicationEventMulticaster 多播器。
(3)ApplicationListener 监听事件
为了接收自定义的 ApplicationEvent 事件,需要创建一个监听器。新建一个类实现 ApplicationListener 接口,并将该类注册为 Spring bean。下边的例子展示了这样一个类:
public class BlockedListNotifier implements ApplicationListener<BlockedListEvent> {// 泛型参数化
private String notificationAddress; // 通知地点,一个简单的属性
public void setNotificationAddress(String notificationAddress) {
this.notificationAddress = notificationAddress;
}
@Override // 默认情况下,监听器同步接受事件
public void onApplicationEvent(BlockedListEvent event) {
// notify appropriate parties via notificationAddress...
System.out.println("BlockedListNotifier监听到目标事件:" + notificationAddress);
}
}
你可以注册任意数量的监听器,但是这些监听器默认都是同步阻塞的,这意味着调用 publisher 中的 publishEvent() 方法会被阻塞,直到所有的监听器执行完操作逻辑。同步的好处是,如果 Spring 开启了事务,那么这些操作都会在一个事务中进行。其他发布策略,可以具体查看 ApplicationEventMulticaster 和 SimpleApplicationEventMulticaster 这两个接口的相关文档。
下面的示例显示了用于注册和配置上述每个类的 bean 定义:// Bean.xml 传统的 xml 方式
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">
<!--事件-->
<bean id="emailService" class="com.taier.pulsar.test.event.EmailService">
<property name="blockedList">
<list>
<value>known.spammer@example.org</value>
<value>known.hacker@example.org</value>
<value>john.doe@example.org</value>
</list>
</property>
</bean>
<!--监听器-->
<bean id="blockedListNotifier" class="com.taier.pulsar.test.event.BlockedListNotifier">
<property name="notificationAddress" value="blockedlist@example.org"/>
</bean>
</beans>
定义完 bean.xml 文件后,每当调用 emailService 中的 sendEmail() 方法时,如果有任何应该被阻止的电子邮件消息,都会发布一个 BlockedListEvent 类型的自定义事件。另外,blockedListNotifier 被注册为 ApplicationListener 实例并接收 BlockedListEvent 事件,此时 blockedListNotifier 可以通知它想通知的任何对象。// 当一个事件发生时,监听器可以操作一些自己的逻辑
public static void main(String[] args) {
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("Beans.xml");
EmailService service = (EmailService) context.getBean("emailService");
service.sendEmail("john.doe@example.org", "发送邮件的内容...");
}
Spring 事件机制,被设计用来实现同一个 Spring 容器中的 Beans 能够进行简单的互动。但是,对于更复杂的企业级集成需求,可以使用单独维护的 Spring integration 项目,该项目为构建轻量级的、面向模式的、事件驱动的体系结构提供了完整的支持,这些体系结构都构建在 Spring 编程模型之上。// 事件机制就是为了满足 beans 之间能够实现简单的互动
2、基于 @EventListener 注解的事件监听器
// 只是对监听器的实现,并没有提供事件实现的注解,所以定义一个事件,还是需要继承 ApplicationEvent
你可以使用 @EventListener 注解在一个类的任何方法上注册一个事件监听器。BlockedListNotifier 可以重写如下: // 这个类同样需要被实例化
public class BlockedListNotifier {
private String notificationAddress;
public void setNotificationAddress(String notificationAddress) {
this.notificationAddress = notificationAddress;
}
// 默认情况下,监听器同步接受事件
@EventListener
public void onApplicationEvent(BlockedListEvent event) {
System.out.println("BlockedListNotifier监听到目标事件:"+ notificationAddress);
}
}
被 @EventListener 注解的方法,它的参数声明了它侦听的事件类型,通过使用注解,可以使用灵活的监听器名称(方法名称随便定义),并且不用实现特定的侦听器接口。
如果你的的方法想监听多个事件,或者想在定义方法时不使用任何参数,那么也可以在注解上指定事件的类型。下面的例子展示了如何做到这一点:
@EventListener({ContextStartedEvent.class, ContextRefreshedEvent.class})
public void handleContextStart() {
// ...
}
还可以添加额外的条件来限制事件的监听,具体操作请看官方文档。
如果你需要发布一个事件作为处理另一个事件的结果,你可以改变方法签名来返回应该发布的事件(这个特征不支持异步监听),如下例所示:
@EventListener
public AnotherBlockedListEvent onApplicationEvent(BlockedListEvent event) {
// 返回一个事件,该事件的监听器也会被通知到,不需要额外的发布操作
return new AnotherBlockedListEvent(this,"john2.doe@example.org","my-event2");
}
onApplicationEvent() 方法为它处理的每个 BlockedListEvent 事件,都会发布一个新的 AnotherBlockedListEvent 事件。如果需要发布多个事件,则可以返回一个 Collection 或事件数组。// 这个特征很有意思,可以一环套一环的通知下去,做成一个通知链。
3、Asynchronous Listeners 异步监听器
如果你想使一个特定的侦听器异步处理事件,你可以使用常规的 @Async 支持。下面的例子展示了如何做到这一点:
@EventListener
@Async
public void processBlockedListEvent(BlockedListEvent event) {
// BlockedListEvent is processed in a separate thread
}
使用异步事件时要注意以下限制:
- 如果异步事件监听器抛出异常,则不会将其传播给调用者。更多细节请参见 AsyncUncaughtExceptionHandler。
- 异步事件监听器方法不能通过返回值来发布后续事件。如果你需要作为处理的结果发布另一个事件,请注入一个 ApplicationEventPublisher 来手动发布事件。
4、Ordering Listeners 监听器排序
如果你需要一个监听器在另一个监听器之前被调用,你可以在方法声明中添加 @Order 注释,如下面的例子所示:// @Order 注释,值越低优先级越高,用来对 Spring components(组件)排序
@EventListener
@Order(42)
public void processBlockedListEvent(BlockedListEvent event) {
// notify appropriate parties via notificationAddress...
}
5、Generic Events 泛型事件
根据一组对象中的特定类型来触发事件。需要有额外的配置。详情请参考官方文档。
总结:主要介绍了 Spring 中的事件机制,典型的观察者模式,该机制在 Spring Boot 中很重要,开发中用来设计架构也很常用,需要重点掌握。
更多推荐
所有评论(0)