标准定制事件
在ApplicationContext中,事件的处理是通过ApplicationEvent和ApplicationListener 两个类来完成的。在容器中实现了ApplicationListener的接口的类,在每次ApplicationEvent事件发布的时候,都会被触发通知。这基本就是标准的观察者模式了。
在spring4.2,事件处理机制显著提升,提供了基于注解的方式发布事件,对象没必要再继承ApplicationEvent这个类。当这个注解对象被published的时候,框架会为你自动包装成一个事件。
spring提供如下标准事件:
Event | Explanation |
---|---|
ContextRefreshedEvent | Published when the ApplicationContext is initialized or refreshed, for example, using the refresh() method on the ConfigurableApplicationContext interface. "Initialized" here means that all beans are loaded, post-processor beans are detected and activated, singletons are pre-instantiated, and the ApplicationContext object is ready for use. As long as the context has not been closed, a refresh can be triggered multiple times, provided that the chosen ApplicationContext actually supports such "hot" refreshes. For example, XmlWebApplicationContext supports hot refreshes, but GenericApplicationContext does not. |
ContextStartedEvent | Published when the ApplicationContext is started, using the start() method on the ConfigurableApplicationContext interface. "Started" here means that all Lifecycle beans receive an explicit start signal. Typically this signal is used to restart beans after an explicit stop, but it may also be used to start components that have not been configured for autostart , for example, components that have not already started on initialization. |
ContextStoppedEvent | Published when the ApplicationContext is stopped, using the stop() method on the ConfigurableApplicationContext interface. "Stopped" here means that all Lifecycle beans receive an explicit stop signal. A stopped context may be restarted through a start() call. |
ContextClosedEvent | Published when the ApplicationContext is closed, using the close() method on the ConfigurableApplicationContext interface. "Closed" here means that all singleton beans are destroyed. A closed context reaches its end of life; it cannot be refreshed or restarted. |
RequestHandledEvent | A web-specific event telling all beans that an HTTP request has been serviced. This event is published after the request is complete. This event is only applicable to web applications using Spring’s DispatcherServlet. |
你也可以创建或者发布自己的事件。如下的例子演示了一个简单的类继承了ApplicationEvent。
public class BlackListEvent extends ApplicationEvent {
private final String address;
private final String test;
public BlackListEvent(Object source, String address, String test) {
super(source);
this.address = address;
this.test = test;
}
// accessor and other methods...
}
我们通常使用ApplicationEventPublisher类的publishEvent()方法来发布一个ApplicationEvent事件。
典型的做法是创建一个类,实现ApplicationEventPublisherAware接口,注册成spring的一个bean。如下:
public class EmailService implements ApplicationEventPublisherAware {
private List<String> blackList;
private ApplicationEventPublisher publisher;
public void setBlackList(List<String> blackList) {
this.blackList = blackList;
}
public void setApplicationEventPublisher(ApplicationEventPublisher publisher) {
this.publisher = publisher;
}
public void sendEmail(String address, String text) {
if (blackList.contains(address)) {
BlackListEvent event = new BlackListEvent(this, address, text);
publisher.publishEvent(event);
return;
}
// send email...
}
}
在配置启动阶段,容器会去检测实现了ApplicationEventPublisherAware 接口的EmailService类,同时会自动调用setApplicationEventPublisher()方法。
实际上,注入进去的参数就是容器自己。我们可以通过ApplicationEventPublisher接口,轻松的和容器的上下文交互。
创建一个类,实现ApplicationListener接口,并注册到容器中,就可以接收自定义的ApplicationEvent事件。如下例子:
public class BlackListNotifier implements ApplicationListener<BlackListEvent> {
private String notificationAddress;
public void setNotificationAddress(String notificationAddress) {
this.notificationAddress = notificationAddress;
}
public void onApplicationEvent(BlackListEvent event) {
// notify appropriate parties via notificationAddress...
}
}
注意到,ApplicationListener这个类,他的参数类型是自定义的事件类型参数:BlackListEvent。这意味着onApplicationEvent()这个方法是类型安全的,避免了类型转换。
我们可以注册很多事件监听,但是要注意,这些监听器在接收事件的时候,是同步处理的:publishEvent()这个方法会阻塞,直到所有的监听器完成事件的处理。这种同步单线程的方式,通常是单个监听器接收单个事件的方式,他和发布事件是在同一个事务中处理的。
另外一种发布策略可以参照下javadoc的ApplicationEventMulticaster这个接口。
如下是上面两个类的一些配置,当然,可以通过spring的注解来进行类的实例化,至于参数,可以结合spring-boot,进行配置文件注入。
<bean id="emailService" class="example.EmailService">
<property name="blackList">
<list>
<value>known.spammer@example.org</value>
<value>known.hacker@example.org</value>
<value>john.doe@example.org</value>
</list>
</property>
</bean>
<bean id="blackListNotifier" class="example.BlackListNotifier">
<property name="notificationAddress" value="blacklist@example.org"/>
</bean>
结合上述,当sendEmail()方法被调用的时候,如果有任何的邮件是属于黑名单的话,BlackListEvent事件就会被发布。blackListNotifier这个注册成了ApplicationListener,所以,能收到BlackListEvent事件。
spring 的事件机制是被设计用在同一个应用的上下文中不同的bean进行交互用的。当然,更复杂的企业级集成需求,Spring Integration 这个项目在著名的spring编程模型之上提供了比较完备的支持:轻便的,pattern-oriented,基于事件驱动的架构。
基于注解的事件监听
spring4.2,事件监听可以通过在一个公共的方法上用EventListener注解来实现。BlackListNotifier这个类可以如下实现:
public class BlackListNotifier {
private String notificationAddress;
public void setNotificationAddress(String notificationAddress) {
this.notificationAddress = notificationAddress;
}
@EventListener
public void processBlackListEvent(BlackListEvent event) {
// notify appropriate parties via notificationAddress...
}
}
通过上面可以看到,方法的签名可以被声明成自己监听的事件类型,但是这次没有实现任何特殊的接口。事件类型也可以通过泛型来缩小,只要实际的事件类型在其实现层次结构中解析你的泛型参数即可。
如果你的方法需要监听许多个事件,而且你的方法中不需要定义任何的参数,事件类型也可以如下方式来指定:
@EventListener({ContextStartedEvent.class, ContextRefreshedEvent.class})
public void handleContextStart() {
...
}
而且我们还可以通过condition属性,在运行时进行过滤,符合SpEL
expression表达式的事件类型,将会被invoke到特殊的事件去处理。
例如,上面的notifier可以被重写成“只有事件中的test属性等于foo的时候,才触发process方法”:
@EventListener(condition = "#blEvent.test == 'foo'")
public void processBlackListEvent(BlackListEvent blEvent) {
// notify appropriate parties via notificationAddress...
}
每个spel表达式针对一个专用的上下文。下面的表格列出来一些对上下文比较有用的项,可以选择使用:
name | location | description | example |
---|---|---|---|
Event | root object | The actual ApplicationEvent | #root.event |
Arguments array | root object | The arguments (as array) used for invoking the target | #root.args[0] |
Argument name | evaluation context | Name of any of the method arguments. If for some reason the names are not available (e.g. no debug information), the argument names are also available under the #a<#arg> where #arg stands for the argument index (starting from 0). | #blEvent or #a0 (one can also use #p0 or #p<#arg> notation as an alias). |
Table 8. Event SpEL available metadata
name | location | description | example |
---|---|---|---|
Event | root object | The actual ApplicationEvent | #root.event |
Arguments array | root object | The arguments (as array) used for invoking the target | #root.args[0] |
Argument name | evaluation context | Name of any of the method arguments. If for some reason the names are not available (e.g. no debug information), the argument names are also available under the #a<#arg> where #arg stands for the argument index (starting from 0). | #blEvent or #a0 (one can also use #p0 or #p<#arg> notation as an alias). |
root.event这个选项表明了根本的事件类型,尽管你的方法签名中指向的是真实的另外一个随意的发布事件。
如果你需要对另外一个事件的处理结果来发布一个事件,你只需要修改方法的返回签名,把他改成你需要发布的事件类型即可,如下:
@EventListener
public ListUpdateEvent handleBlackListEvent(BlackListEvent event) {
// notify appropriate parties via notificationAddress and
// then publish a ListUpdateEvent...
}
This feature is not supported for asynchronous listeners.
对于每个BlackListEvent事件的处理,这个方法将会发布一个新的事件:ListUpdateEvent。如果你需要发布多个事件,只要把返回结果改成事件的集合Collection即可。
异步监听
如果想创建一个特殊的监听器来异步的处理事件,需要regular @Async
support:
@EventListener
@Async
public void processBlackListEvent(BlackListEvent event) {
// BlackListEvent is processed in a separate thread
}
-
需要清楚的知道用异步事件的限制如下:
1、如果事件监听抛出一个Exception,这个异常不会传播到调用者,参见AsyncUncaughtExceptionHandler获取更多信息。
2、这种异步的监听是没办法返回结果的。如果你需要对处理结果再发一个事件,注入ApplicationEventPublisher来手工发布事件。
顺序监听
如果你想定义监听器的顺序,加上@Order注解即可:
@EventListener
@Order(42)
public void processBlackListEvent(BlackListEvent event) {
// notify appropriate parties via notificationAddress...
}
网友评论