目前的版本是spring 4.3.10 release版,这里的@Transcational注解是spring的注解,而不是javax.persitence的注解
事件生命周期
一般而言,事件的生命周期为事件创建, 事件发布,事件订阅,事件结束.
事件创建
spring的事件都继承ApplicationEvent,然后添加自己需要的东西
如下:
public class LoginEvent extends ApplicationEvent implements PointCommon {
public LoginEvent(Object source) {
super(source);
}
private String activeId;
private String activeCode="login";
private Boolean pointFlag;
public String getActiveId() {
return activeId;
}
public void setActiveId(String activeId) {
this.activeId = activeId;
}
@Override
public String getActiveCode() {
return activeCode;
}
public Boolean getPointFlag() {
return pointFlag;
}
public void setPointFlag(Boolean pointFlag) {
this.pointFlag = pointFlag;
}
public LoginEvent(Object source, String activeId, String activeCode, Boolean pointFlag) {
super(source);
this.activeId = activeId;
this.pointFlag = pointFlag;
}
}
这是一个自定的登录事件,定义了基本的事件信息.
事件发布
一般实现ApplicationEventPublisherAware,使用 ApplicationEventPublisher publisher来发布事件. 自定义事件
public class LoginService implements ApplicationEventPublisherAware {
private EventService eventService;
private ApplicationEventPublisher eventPublisher;
@Override
public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
this.eventPublisher = applicationEventPublisher;
}
public void login(HttpServletRequest request...){
LoginEvent event = new LoginEvent("", "234sd", "login", true);
publisher.publishEvent(event);
}
}
订阅事件
实现ApplicationListener<T>接口,T可指定事件的类型,但是要继承ApplicationEvent才行;
@Component
public class PointHandler implements ApplicationListener<LoginEvent >{
@Override
public void onApplicationEvent(LoginEvent event) {
...
}
}
这样就可以处理事件了.
与下面的方式相比,继承EventListener比较笨重.
@EventListener
public void handle(PointCommon event) {
out.println("----斯蒂芬-----");
out.println(event.getDescription());
}
没错,直接加上@EventListener就能实现了监听器的功能.
事件中断
事件链中同步事件方法出现异常,整个事件链就会出现中断.不继续执行.异步方法无影响.
事件结束
当所有的时间监听器处理完事件之后,事件才算结束,jvm才会执行其他操作.但有异步事件监听器时,只要所有的同步事件监听器处理完事件之后,jvm就会执行其他操作,且异步事件不会对事件源方法产生有效影响.这里可以认为事件已经结束了.
EventListener详解
我们可以通过注解的condition属性来添加额外的运行时拦截条件,该属性定义了一个SpEL表达式,你需要匹配这个特定事件,才能调用相应的方法.
例如,我们的通知器可以重写为只有当事件的test属性等于foo时才会调用.
@EventListener(condition = "#event.test == 'foo'")
public void processBlackListEvent(BlackListEvent event) {
// notify appropriate parties via notificationAddress...
}
每一次SpEL表达式评估都有专门的上下文.下面的表格展示了你在该上下文中能获得的选项,所有你可以使用他们进行有条件的事件处理:
Event Spel available metadata
名称:event 位置:root object 描述:实际的ApplicationEvent(应用事件),例子:#root.event
名称:args 位置:root Object 描述:调用目标使用的参数(数组) 例子:#root.args[0]
名称:argument name 位置:evaluation context 描述:方法中每个参数的名字.如果因某些原因这些名字不可获得,这个参数的名称仍可以通过#a<#arg>来获取,这个#arg代表的是参数的下标(从0开始). 例子:#iban或#a0(也可以使用#p0或使用#p<#arg>标志作为别名)
记住#root.event允许你访问对应的事件,即使你的方法签名实际上对应的是任意要发布的对象.
如果你因为处理完某个事件后需要发布一个新事件,只需要把方法签名的返回修改为你需要发布的事件的类型,像这样:
@EventListener
public ListUpdateEvent handleBlackListEvent(BlackListEvent event) {
// notify appropriate parties via notificationAddress and
// then publish a ListUpdateEvent...
}
该功能不支持异步的监听器;
这个新方法会为每个该方法处理过的blackListEvent发布一个新的ListUpdateEvent事件.如果你需要发布几个事件,只需要返回这些事件的集合.
异步事件处理方法不支持该特性.
Asynchronous Listeners 异步监听器
如果你想要一个特别的监听器来异步处理事件,简单的使用常规的@Async支持:
@EventListener
@Async
public void processBlackListEvent(BlackListEvent event) {
// BlackListEvent is processed in a separate thread
}
记住使用异步事件时注意以下几个局限:
- 1.如果监听器抛出一个异常,它不会传播到调用者那里.查看AsyncUncaughtExceptionHandler以应对更多细节.
- 2.这些事件监听器无法发送回复.如果你需要发送其他事件作为处理结果,注入ApplicationEventPublisher进行手动发送.
Ordering Listeners 监听器排序
如果你想要一个监听器先于其他调用.只需要在方法参数上添加@Order注解;
@EventListener
@Order(42)
public void processBlackListEvent(BlackListEvent event) {
// notify appropriate parties via notificationAddress...
}
Generic Events 泛型事件
你可以使用泛型更深入的定义你的事件的结构.思考一个EntityCreatedEvent<T>,其中T是被创建实体的实际类型.你可以创建一个只用来接受Person类的EntityCreatedEvent事件监听器;
@EventListener
public void onPersonCreated(EntityCreatedEvent<Person> event) {
...
}
由于类型擦除,只有当事件完好符合事件监听器拦截的泛型参数,它才工作.(这类似如class PersonCreatedEvent extends EntityCreatedEvent<Person>{...});
在特定的环境下,如果有所有的事件都遵循相同的结构(它可能是上面提到的那种事件)会变得相当乏味.这样的话,你可用实现ResolvableTypeProvider来指导运行时环境提供的架构.
public class EntityCreatedEvent<T>
extends ApplicationEvent implements ResolvableTypeProvider {
public EntityCreatedEvent(T entity) {
super(entity);
}
@Override
public ResolvableType getResolvableType() {
return ResolvableType.forClassWithGenerics(getClass(),
ResolvableType.forInstance(getSource()));
}
}
它不仅只对ApplicationEvent事件其作用,还适用于你作为事件发布的包含着的任意对象.
同步处理事件
spring的默认事件是同步事件.
@EventListener
@Transactional
public void handleEvent(EventEvent event) {
throw new NullPointerException("测试事务");
// System.out.println("同步步处理问题!!");
}
-
如果方法上加事务注解,则对其方法的事务其作用,如异常回滚.
-
如果方法上不加事务注解,则对其调用方法的事务无影响
-
如果方法加上事务注解,根据事务一致性和Transcational的默认事务传播机制,则将该事务归入到上级方法的事务中,从而影响上级方法的事务.
异步处理事件
开启spring异步支持,才能使用异步事件.开启方法:https://my.oschina.net/u/1590027/blog/1511296
经典代码例子:
@EventListener
@Async
@Transactional
public void handleEvent2(EventEvent event) throws Exception {
Event event1 = event.getEvent();
event1.setName("异步事件事务一致性");
eventService.update(event1);
System.out.println("异步输出问题!!");
throw new NullPointerException("测试空指针");
}
- 本身事务有效.可能是spring版本问题,有人说事务无效,可是在方法体内该事务还是有效的.
- 对于事件源所在的方法及其上级来说,该方法抛出的异常对他们无任何影响.
- 如果该事件处理方法的业务逻辑容错率低,则不建议使用异步事件.