Spring/SpringBoot面向事件驱动编程
- 事件监听机制可以理解为是一种观察者模式,有数据发布者(事件源)和数据接受者(监听器);
- 在Java中,事件对象都是继承java.util.EventObject对象,事件监听器都是java.util.EventListener实例;
- EventObject对象不提供默认构造器,需要外部传递source参数,即用于记录并跟踪事件的来源;
Spring/Spring Boot在面向事件驱动编程方面提供了以下内容:
- 事件(Events):Spring提供了一个基于观察者模式的事件模型。通过定义和发布事件,可以将不同组件之间解耦,使其能够以异步方式进行通信。
- 事件监听器(Event Listeners):通过实现ApplicationListener接口或使用@EventListener注解,可以创建事件监听器。这些监听器用于接收和处理发布的事件,并根据需要执行相应的操作。
- 事件发布器(Event Publisher):Spring框架提供了ApplicationEventPublisher接口,用于发布事件。可以通过调用publishEvent()方法来发布自定义的事件,从而通知所有注册的监听器。
- 领域事件(Domain Events):在面向领域驱动设计(Domain-Driven Design)的应用程序中,领域事件用于表示系统中重要的业务操作或状态变化。Spring框架提供了支持,允许开发人员将领域事件与应用程序的其他部分集成起来。
- 异步事件处理(Asynchronous Event Handling):Spring支持将事件处理设置为异步执行。可以使用@Async注解将事件处理方法标记为异步方法,从而使事件的处理在独立的线程中执行。
- 条件事件(Conditional Events):使用@Conditional注解,可以根据满足特定条件才触发事件。这样可以根据应用程序的状态或配置选择性地发布事件,从而更加灵活地控制事件的触发。
通过这些功能,Spring/Spring Boot的面向事件驱动编程能够实现松耦合、可扩展和易于维护的应用程序架构。开发人员可以使用事件和监听器来处理应用程序中的各种场景,例如状态变化、业务流程、异步消息处理等,从而提高系统的响应性和可测试性。
观察者模式
观察者模式的定义:在对象之间定义一个一对多的依赖,当一个对象状态改变的时候,所有依赖的对象都会自动收到通知。
观察者模式角色
- Subject:抽象主题(抽象被观察者),抽象主题角色把所有观察者对象保存在一个集合里,每个主题都可以有任意数量的观察者,抽象主题提供一个接口,可以增加和删除观察者对象。
- ConcreteSubject:具体主题(具体被观察者),该角色将有关状态存入具体观察者对象,在具体主题的内部状态发生改变时,给所有注册过的观察者发送通知。
- Observer:抽象观察者,是观察者者的抽象类,它定义了一个更新接口,使得在得到主题更改通知时更新自己。
- ConcrereObserver:具体观察者,实现抽象观察者定义的更新接口,以便在得到主题更改通知时更新自身的状态。
Spring事件
事件机制实现方式 实现Spring事件机制主要有4个类:
- ApplicationEvent:事件,每个实现类表示一类事件,可携带数据。
- ApplicationListener:事件监听器,用于接收事件处理时间。
- ApplicationEventMulticaster:事件管理者,用于事件监听器的注册和事件的广播。
- ApplicationEventPublisher:事件发布者,委托ApplicationEventMulticaster完成事件发布。
EventObject 应用事件,职责为定义业务
只需要实现ApplicationEvent 抽象类定义有参构造函数即可,source表示事件源,( 可按照自己的需求制定)
EventObject对象不提供默认构造器,需要外部传递source参数,即用于记录并跟踪事件的来源; Spring事件对象为ApplicationEvent,继承EventObject,源码如下:
java
public abstract class ApplicationEvent extends EventObject {
/**
* Create a new ApplicationEvent.
* @param source the object on which the event initially occurred (never {@code null})
*/
public ApplicationEvent(Object source) {
super(source);
this.timestamp = System.currentTimeMillis();
}
}
ApplicationListener
事件监听器,职责为处理事件广播器发布的事件
Spring事件监听器为ApplicationListener,继承EventListener, 源码如下:
java
public interface ApplicationListener<E extends ApplicationEvent> extends EventListener {
void onApplicationEvent(E var1);
}
并提供了两个实现:SmartApplicationListener和GenericApplicationListener接口。
实现Spring事件监听有两种方式:
- 面向接口编程,实现ApplicationListener接口;
- 基于注解驱动,@EventListener(Spring自定义的注解);
java
@Log4j2
@Component
public class AEventListener implements ApplicationListener<TestEvent> {
@Override
public void onApplicationEvent(TestEvent event) {
//逻辑处理
}
}
@Log4j2
@Component
public class AEventListener implements ApplicationListener<TestEvent> {
@Async
@EventListener
public void listener(TestEvent event) throws InterruptedException {
Thread.sleep(2000);
log.info("监听到数据:{}", event.getMessage());
}
}
SmartApplicationListener
提供了监听器对泛型事件的支持,spring3.0 添加的
java
public interface SmartApplicationListener extends ApplicationListener<ApplicationEvent>, Ordered {
boolean supportsEventType(Class<? extends ApplicationEvent> var1);
boolean supportsSourceType(Class<?> var1);
}
GenericApplicationListener
增强对泛型事件的支持(支持泛型方式不同与SmartApplicationListener),spring4.2 添加的。
源码:
java
public interface GenericApplicationListener extends ApplicationListener<ApplicationEvent>, Ordered {
boolean supportsEventType(ResolvableType var1);
boolean supportsSourceType(Class<?> var1);
}
ApplicationEventMulticaster
事件广播器,职责为将EventPubsher(事件发布者)发布的event 广播给事件EventListener(事件监听器)。
Spring提供了默认的实现SimpleApplicationEventMulticaster,如果用户没有配置自定义事件广播器, 则会默认使用SimpleApplicationEventMulticaster作为事件广播器。在容器刷新的过程中会实例化、初始化事件广播器。
ApplicationEventPublisher
事件发布者,职责为发布事件。 spring的ApplicationContext 本来就实现了ApplicationEventPublisher接口,因此应用上下文本来就是 一个事件发布者,在AbstractApplicationContext中实现了事件发布的业务。
发布方式1: 直接注入ApplicationContext:
java
@Autowired
private ApplicationContext applicationContext;
applicationContext.publishEvent();
发布方式2:直接注入ApplicationEventPublisher:
java
@Autowired
private ApplicationEventPublisher applicationEventPublisher;
applicationEventPublisher.publishEvent(myEvent)
实例:
- 面向接口编程,实现ApplicationListener接口:
自定义事件对象:
java
public class MyApplicationEvent extends ApplicationEvent {
public MyApplicationEvent(Object source) {
super(source);
}
}
自定义事件监听器:
java
@Component
public class MyApplicationListener implements ApplicationListener<MyApplicationEvent> {
@Override
public void onApplicationEvent(MyApplicationEvent event) {
System.out.println("收到事件:" + event);
}
}
启动服务并发布事件:
java
public class ApplicationEventBootstrap {
public static void main(String[] args) {
AnnotationConfigApplicationContext context =
new AnnotationConfigApplicationContext();
// 注册自定义事件监听器
context.addApplicationListener(new MyApplicationListener());
// 启动上下文
context.refresh();
// 发布事件,事件源为Context
context.publishEvent(new MyApplicationEvent(context));
// 结束
context.close();
}
}
运行结果:
收到事件:com.xx.MyApplicationEvent[source=org.springframework.context.annotation.AnnotationConfigApplicationContext@cb0ed20, started on Sat May 16 16:32:04 CST 2020]
- 通过@EventListener注解,该会根据方法参数类型来自动监听相应事件的发布
java
@Component
@Slf4j
public class OrderCreateEventListener3 {
@EventListener (classes = {OrderCreateEvent.class}) //classes属性指定处理事件的类型
@Async //异步监听
@Order(0)//使用order指定顺序,越小优先级越高
public void eventListener(OrderCreateEvent event) {
log.info("通过注解@EventListener和@Async,异步监听OrderCreateEvent事件,orderId:" + event.getOrder().getOrderNo());
}
}
如果要监听多个事件类型的发布,可以在@EventListener(classes = {FaceEvent.class,ArmEvent.class})指定,spring会多次调用此方法来处理多个事件。但是注意此时,方法参数不能有多个,否则会发生转换异常,可以将使用多个事件的父类作为唯一的方法参数来接收处理事件,但除非必要否则并不推荐监听多个事件的发布。
如果有多个监听器监听同一事件,我们可以在方法上使用spring的@order注解来定义多个监听器的顺序,order越小,优先级越高.
@EventListener还有一个属性,condition()里可以使用SPEL表达式来过滤监听到事件,即只有符合某种条件的才进行接收处理。比如: @EventListener(condition = "event.message == 'message'")
监听多个事件:
java
@EventListener({FaceEvent.class,ArmEvent.class})
public void onApplicationEvent3(Object event) {
if(event instanceof FaceEvent){
LOGGER.info("===> B 收到人脸事件: {}",((FaceEvent) event).getEventData());
}else if(event instanceof ArmEvent){
ArmEvent armEvent = (ArmEvent) event;
LOGGER.info("===> B 收到臂膀事件: {}",armEvent.getEventData());
}
}
注意事项
- 事件没要处理的监听器,就会被抛弃。
- 一个事件可以同时被多个监听处理类监听处理。
- 默认情况下事件是同步的,即事件被publish后会等待Listener的处理。如果发布事件处的业务存在事务,监听器处理也会在相同的事务中。
- 如果对于事件的处理不想受到影响,可以onApplicationEvent方法上加@Async支持异步或者在有@EventListener的注解方法上加上@Async。注:启动类上同时要加上@EnableAsync
在需要触发事件的地方注入ApplicationEventPublisher,并发布自定义事件:
java
@Service
public class MyService {
@Autowired
private ApplicationEventPublisher eventPublisher;
public void doSomething() {
// 执行业务逻辑
// ...
// 发布自定义事件
CustomEvent event = new CustomEvent(this, "Custom event message");
eventPublisher.publishEvent(event);
}
}
运行结果:
收到事件:com.xx.MyApplicationEvent[source=org.springframework.context.annotation.AnnotationConfigApplicationContext@cb0ed20, started on Sat May 16 16:32:04 CST 2020]
通过实例可以看出,上面两种方式都可正常发布和接收事件。
利用@TransactionalEventListener实现监听事件时的事务隔离
很多时候,只有事务提交之后我们才会发布相应的事件处理其他逻辑,比如用户注册之后,发送邮件或者短信。这时候就可以用注解@TransactionalEventListener。
@TransactionalEventListener和@EventListener都可以监听事件,但前者可以对发布事件和监听事件进行一些事务上的隔离。
@TransactionalEventListener是对@EventListener的一个扩展,允许将事件的监听器绑定到事务的某个阶段。可以绑定到以下事务阶段:
- AFTER_COMMIT (默认),事务提交后
- AFTER_ROLLBACK ,事务回滚后
- AFTER_COMPLETION ,事务完成,包括提交后和回滚后
- BEFORE_COMMIT ,事务提交前
@TransactionalEventListener指不和发布事件的方法在同一个事务内,发布事件的方法事务结束后才会执行本监听方法,监听逻辑内发生异常不会回滚发布事件方法的事务。
@TransactionalEventListener有一个属性为fallbackExecution,默认为false,指发布事件的方法没有事务控制时,监听器不进行监听事件,此为默认情况!fallbackExecution=true,则指发布事件的方法没有事务控制时,监听方法仍可以监听事件进行处理。
@TransactionalEventListener
和@EventListener
是Spring Framework中用于处理事件的注解,它们在功能和使用方式上有一些区别:
- 事务支持:最显著的区别是
@TransactionalEventListener
注解提供了对事务的支持。当使用@TransactionalEventListener
注解标记事件处理方法时,如果当前存在活动的事务,则事件处理方法将在事务内执行;如果没有事务,则会创建一个新的事务来执行事件处理方法。这确保了事件处理方法与其他数据库操作之间的一致性。而@EventListener
注解的事件处理方法默认不受事务控制。 - 事务传播行为:
@TransactionalEventListener
注解还允许指定事务的传播行为(propagation)和事务的隔离级别(isolation level)。可以通过设置transactional
属性来指定所需的事务传播行为和隔离级别。这使得可以在事件处理方法中定义更复杂的事务语义。 - 监听器顺序:
@TransactionalEventListener
注解还允许设置监听器的执行顺序(order)。可以使用phase
属性来指定监听器方法的执行顺序,按照数字从小到大的顺序执行。而@EventListener
注解的事件处理方法没有直接支持指定执行顺序的功能,但可以通过实现Ordered
接口或使用@Order
注解来达到相同的效果。
综上所述,@TransactionalEventListener
注解相比于@EventListener
注解提供了更强大的事务支持和控制。如果你需要在事件处理方法中保证事务的一致
Spring 中的事件机制 ApplicationEventPublisher
ApplicationContext 通过 ApplicationEvent 类和 ApplicationListener 接口进行事件处理。 如果将实现 ApplicationListener 接口的 bean 注入到上下文中,则每次使用 ApplicationContext 发布 ApplicationEvent 时,都会通知该 bean。本质上,这是标准的观察者设计模式。
- 启动类添加注解@EnableAsync,开启 Spring 异步的功能
- 创建用户注册事件类(UserRegisterEvent):可以自定义复杂对象
java
public class UserRegisterEvent extends ApplicationEvent {
/**
* 用户名
*/
private String username;
public UserRegisterEvent(Object source, String username) {
super(source);
this.username = username;
}
public String getUsername() {
return username;
}
}
- 创建用户 Service(UserService )
java
@Service
@Slf4j
public class UserService implements ApplicationEventPublisherAware {
private ApplicationEventPublisher applicationEventPublisher;
@Override
public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
this.applicationEventPublisher = applicationEventPublisher;
}
@Async
public void register(String username) {
log.info("新用户:" + username + ",进行注册...");
System.out.println("------------------------------------------------------------------------------------------------------------------------------");
//触发(发布)事件
applicationEventPublisher.publishEvent(new UserRegisterEvent(this, username));
}
}
需要注意的是,服务必须交给 Spring 容器托管。ApplicationEventPublisherAware 是由 Spring 提供的用于为 Service 注入 ApplicationEventPublisher 事件发布器的接口,使用这个接口,我们自己的 Service 就拥有了发布事件的能力。用户注册后,不再是显示调用其他的业务 Service,而是发布一个用户注册事件。
创建邮件服务,积分服务,其他服务(事件订阅者)等:
java
@Service
@Slf4j
public class EmailService implements ApplicationListener<UserRegisterEvent> {
@Override
@Async
public void onApplicationEvent(UserRegisterEvent userRegisterEvent) {
log.info("给用户 " + userRegisterEvent.getUsername() + " 发送邮箱...");
System.out.println("------------------------------------------------------------------------------------------------------------------------------");
}
}
@Service
@Slf4j
public class MsgService {
@EventListener
@Async
public void addCoupon(UserRegisterEvent userRegisterEvent) {
log.info("给用户 " + userRegisterEvent.getUsername() + " 发送短信...");
System.out.println("------------------------------------------------------------------------------------------------------------------------------");
}
}
事件订阅者的服务同样需要托管于 Spring 容器,ApplicationListener 接口是由 Spring 提供的事件订阅者必须实现的接口,我们一般把该 Service 关心的事件类型作为泛型传入。处理事件,通过 event.getSource() 即可拿到事件的具体内容
实现原理
通过上面实例可以看出,context 可以发布事件,那底层是怎么发布的,让我们继续看源码:
java
public abstract class AbstractApplicationContext extends DefaultResourceLoader
implements ConfigurableApplicationContext {
protected void publishEvent(Object event, @Nullable ResolvableType eventType) {
...
getApplicationEventMulticaster().multicastEvent(applicationEvent, eventType);
...
}
}
通过源码我们可以看出,事件应该是通过ApplicationEventMulticaster发布的,我们继续看:
java
public class SimpleApplicationEventMulticaster extends AbstractApplicationEventMulticaster
Spring 中事件发布都是通过SimpleApplicationEventMulticaster来实现的
java
public void multicastEvent(final ApplicationEvent event, @Nullable ResolvableType eventType) {
ResolvableType type = (eventType != null ? eventType : resolveDefaultEventType(event));
for (final ApplicationListener<?> listener : getApplicationListeners(event, type)) {
Executor executor = getTaskExecutor();
if (executor != null) {
// 异步
executor.execute(() -> invokeListener(listener, event));
}
else {
invokeListener(listener, event);
}
}
}
可以看出,如果设置了Executor则异步发送,否则同步;而且可以看出通过 resolveDefaultEventType(event) 对发布的事件类型进行了校验,这就是为什么我们可以直接使用泛型来指定我们想接收的事件对象, 比如上面的ApplicationListener<MyApplicationEvent>
。
java
private void doInvokeListener(ApplicationListener listener, ApplicationEvent event) {
try {
listener.onApplicationEvent(event);
最后就使用对应的ApplicationListener进行接收和处理就行了,那么ApplicationListener是什么时候注册的呢?
如何添加ApplicationListener?
- 直接添加,使用content.addApplicationListener(上面实例中有使用);
- 将自定义的ApplicationListener注册为一个Bean,Spring再初始化Bean之后会添加,具体代码在ApplicationListenerDetector#postProcessAfterInitialization,判断一个Bean如果是ApplicationListener,则也是使用context.addApplicationListener添加;
- 使用注解@EventListener,在初始化Bean之后,会在EventListenerMethodProcessor中进行处理和添加;
第三种实现的源码如下(EventListenerMethodProcessor中):
java
private void processBean(final String beanName, final Class<?> targetType) {
....
// 获取public 且有@EventListener的方法
AnnotatedElementUtils.findMergedAnnotation(method, EventListener.class));
...
ApplicationListener<?> applicationListener = factory.createApplicationListener(beanName, targetType, methodToUse);
// 添加监听器
context.addApplicationListener(applicationListener);
}
Spring内建事件
- ContextRefreshedEvent: Spring应用上下文就绪事件;
- ContextStartedEvent: Spring应用上下文启动事件;
- ContextStopedEvent: Spring应用上下文停止事件;
- ContextClosedEvent: Spring应用上下文关闭事件;
Spring Boot事件
Spring Boot事件是在Spring事件基础上进行的封装
java
public abstract class SpringApplicationEvent extends ApplicationEvent
事件对象改为SpringApplicationEvent,事件源为SpringApplication(Spring事件源为Context);
底层发布事件还是使用
SimpleApplicationEventMulticaster 对象,不过有点需要说明的是,Spring Boot 1.4开始,SpringApplication和ApplicationContext使用的都是
SimpleApplicationEventMulticaster实例,但是两者属于不同的对象(1.0 ~ 1.3版本是同一个对象);