package com.felix.event;
import org.springframework.context.ApplicationEvent;
public class BaseEvent extends ApplicationEvent {
public BaseEvent(Object source) {
super(source);
}
}
package com.felix.event;
/**
* 自定义的Event
*/
public class SelfEvent extends BaseEvent{
public SelfEvent(Object source) {
//可以做点事情
super(source);
//可以做点事情
}
}
package com.felix.event.publish;
import com.felix.event.SelfEvent;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.stereotype.Service;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
@Service
public class SelfHandler {
@Autowired
private ApplicationContext applicationContext;
public void handler(){
for(int i = 0;i < 1000; i ++){
Map test = new HashMap();
test.put("index",i);
test.put("name","袁成舟");
test.put("sex","male");
test.put("age","26");
/**
* 这边使用多线程发布的时候
* 消费者那边实现 ApplicationListener 这个接口的消费者消费的时候不是有序消费的
*/
CompletableFuture.runAsync(() -> {
applicationContext.publishEvent(new SelfEvent(test));
});
}
}
}
这里要注意实现ApplicationListener
接口的监听者监听的时候不是有序的,上面我采用多线程实验了一波实锤了。
注意
如果发布和监听都没有采用异步,即发布处是一个for循环,监听者没有开启异步,那么两者就是同步的。发布者发完一个要等监听者处理完之后才能发布第二个事件,发布者在监听者没有返回ack的时候是阻塞的。
举个栗子:
发布者发布了100个事件,在发布这100个事件发布过程中,会等上一个事件处理完成之后再发布下一个事件,等同于整个是个同步的过程。
如果就是要监听者异步去监听处理,可以在监听者类上加上@EnableAsync
,在onApplicationEvent
方法上加上@Async
异步注解
package com.felix.event.consumer;
import com.felix.event.SelfEvent;
import org.springframework.context.ApplicationListener;
import org.springframework.stereotype.Component;
@Component
public class SelfEventListener implements ApplicationListener<SelfEvent> {
@Override
public void onApplicationEvent(SelfEvent selfEvent) {
System.out.println("收到自定义的SelfEvent事件了-------" + selfEvent.getSource());
}
}
发布者
发布者这里新建了一个工具类,实现了ApplicationEventPublisherAware
接口,可以注入ApplicationEventPublisher
,注入原理类似BeanNameAware
接口.
package com.felix.event.tool;
import org.springframework.context.ApplicationEvent;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.ApplicationEventPublisherAware;
import org.springframework.stereotype.Component;
@Component
public class SpringApplicationEventPublishUtil implements ApplicationEventPublisherAware {
private ApplicationEventPublisher publisher;
@Override
public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
this.publisher = applicationEventPublisher;
}
public void publishEvent(ApplicationEvent event){
publisher.publishEvent(event);
}
}
监听的方法一次可以监听多个事件
而且当多个方法监听一个事件的时候,可以按照自定义的顺序去执行这些个方法,即这些监听同一个事件的方法都会执行的,只是执行的时候有序了而已
@Order(1)
注解就是定义顺序的,数字越小,执行顺序越靠前
package com.felix.event.consumer;
import com.felix.event.tool.FaceEvent;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.ApplicationEvent;
import org.springframework.context.event.EventListener;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
@Component
@Slf4j
public class AnnotationConsumer {
/**
* 如果要监听多个事件类型的发布,可以在@EventListener(classes = {FaceEvent.class,ArmEvent.class})指定,
* spring会多次调用此方法来处理多个事件。但是注意此时,方法参数不能有多个,否则会发生转换异常,
* 可以将使用多个事件的父类作为唯一的方法参数来接收处理事件,但除非必要否则并不推荐监听多个事件的发布。
* 可以2个方法同时监听同一个事件,并且优先级按照数字值小的先监听.
* 不过发布者发布了一个事件,但是在有2个方法同时监听的时候这两个方法都会执行,只是执行顺序按照@Order中的值来决定
* @param event
*/
@EventListener(classes = {FaceEvent.class})
@Order(1)
public void onAnnotationEvent_1(ApplicationEvent event){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
log.info("收到自定义的 FaceEvent 事件了,优先级=1-------{}",event.getSource());
}
@EventListener(classes = {FaceEvent.class})
@Order(2)
public void onAnnotationEvent_2(ApplicationEvent event){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
log.info("收到自定义的 FaceEvent 事件了,优先级=2-------{}",event.getSource());
}
}
@TransactionalEventListener
和@EventListener
都可以监听事件,但前者可以对发布事件和监听事件进行一些事务上的隔离。@TransactionalEventListenerr
指不和发布事件的方法在同一个事务内,发布事件的方法事务结束后才会执行本监听方法,监听逻辑内发生异常不会回滚发布事件方法的事务。
@TransactionalEventListener
有一个属性为fallbackExecution
,默认为false
,指发布事件的方法没有事务控制时,监听器不进行监听事件,此为默认情况! fallbackExecution=true
,则指发布事件的方法没有事务控制时,监听方法仍可以监听事件进行处理。
刚才我们说到使用@TransactionalEventListener
会在发布事件的方法事务结束后执行监听方法,但其实我们还可以进行细化的控制。它有一个属性为TransactionPhase
,默认为TransactionPhase.AFTER_COMMIT
,即事务提交后。还可以根据需要选择AFTER_COMPLETION、BEFORE_COMMIT、AFTER_ROLLBACK
。
但仍需注意,如果fallbackExecution=false
,且发布事件的方法没有事务控制时,监听器根本不会监听到事件,此处的TransactionPhase
也就没有意义了。
TransactionalEventListener坑点
如果遇到这样的业务,操作B需要在操作A事务提交后去执行,那么TransactionalEventListener是一个很好地选择。这里需要特别注意的一个点就是:当B操作有数据改动并持久化时,并希望在A操作的AFTER_COMMIT阶段执行,那么你需要将B事务声明为PROPAGATION_REQUIRES_NEW。这是因为A操作的事务提交后,事务资源可能仍然处于激活状态,如果B操作使用默认的PROPAGATION_REQUIRED的话,会直接加入到操作A的事务中,但是这时候事务A是不会再提交,结果就是程序写了修改和保存逻辑,但是数据库数据却没有发生变化,解决方案就是要明确的将操作B的事务设为PROPAGATION_REQUIRES_NEW。