0%

Spring 的事件机制

观察者模式

在说事件机制之前,我们先聊一下观察者模式,因为 Spring 的事件机制本质上是观察者模式的一种实现。

我们都知道,有一种设计模式叫观察者模式,它用于建立对象之间一对多的依赖关系,当一个对象的状态发生变化时, 所有依赖它的对象都会得到通知并自动更新。这种模式常用于需要实现对象之间松耦合的场景, 其中一个对象(被观察者)的状态变化会影响到其他多个对象(观察者)。

观察者模式的主要角色

  1. 被观察者(Subject):也称为主题或者可观察对象,它维护了一个观察者列表,可以添加、删除和通知观察者。当其状态发生变化时,会通知所有注册的观察者。
  2. 观察者(Observer):观察者是依赖于被观察者的对象,它定义了一个方法,用于在被观察者状态发生变化时进行更新操作。

观察者模式的工作流程

  1. 被观察者对象注册观察者:观察者通过某种方式向被观察者注册,通常是将自己添加到被观察者的观察者列表中。(建立起观察者与被观察者的关联)
  2. 被观察者状态变化
  3. 通知观察者:被观察者遍历观察者列表,调用每个观察者的更新方法(onEventhandle...)
  4. 观察者更新:每个观察者根据被观察者的通知进行相应的更新操作,执行与状态变化相关的任务

观察者模式的优点

解耦性:被观察者和观察者之间的关系是松耦合的,提高了代码的可维护性和扩展性。 可以轻松添加或删除观察者,可以在不修改被观察者的情况下增加新的观察者。

Spring 中的事件

Spring 的事件机制是 Spring 框架中的一个重要特性,基于观察者模式实现,它可以实现应用程序中的解耦,提高代码的可维护性和可扩展性。

Spring 的事件机制包括事件、事件发布、事件监听器等几个基本概念:

  1. 事件:事件是一个抽象的概念,它代表着应用程序中的某个动作或状态的发生。
  2. 事件发布:是事件发生的地方,它负责发布事件,从而通知事件监听器。
  3. 事件监听器:事件的接收者,它负责处理事件并执行相应的操作。

在 Spring 的事件机制中,事件源和事件监听器之间通过事件进行通信,从而实现了代码的解耦。

1

如上图所示,在观察者模式的实现中,往往还会有一个 Dispatcher 的角色, 由它来通知观察者,在 Spring 中,ApplicationContext 就扮演了这个角色。

如何定义事件

在 Spring 中,我们可以通过继承 ApplicationEvent 来自定义一个事件:

1
2
3
4
5
6
7
import org.springframework.context.ApplicationEvent;

public class MyEvent extends ApplicationEvent {
public MyEvent(Object source) {
super(source);
}
}

我们会发现,ApplicationEvent 有一个必选的参数 source,这个参数在实践中往往传递 this,也就是事件发生处的对象,这个参数不能为 null

如何监听事件?

在 Spring 中,监听事件的方式有两种:

  1. 实现 ApplicationListener,需要指定它要监听的事件类型
1
2
3
4
5
6
7
8
9
10
import org.springframework.context.ApplicationListener;
import org.springframework.stereotype.Component;

@Component
public class MyEventListener implements ApplicationListener<MyEvent> {
@Override
public void onApplicationEvent(MyEvent event) {
System.out.println("MyEventListener::onApplicationEvent");
}
}

注意:我们需要添加 @Component 注解以便 Spring 可以注册这个观察者。

  1. 使用 @EventListener 注解
1
2
3
4
5
6
7
8
9
10
11
import com.example.springeventdemo.event.MyEvent;
import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Component;

@Component
public class SpringEventListener {
@EventListener
public void myEvent(MyEvent myEvent) {
System.out.println("my event.");
}
}

我们可以使用 @EventListener 注解在托管 bean 的任何方法上注册事件监听器。 需要监听的事件通过方法的参数来指定。

如何发布事件

我们可以使用 ApplicationEventPublisher 来发布一个事件,也就是通知所有的观察者:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import com.example.springeventdemo.event.MyEvent;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.stereotype.Component;

@Component
public class MyService {
@Autowired
ApplicationEventPublisher applicationEventPublisher;

public void publish() {
applicationEventPublisher.publishEvent(new MyEvent(this));
}
}

也可以使用 ConfigurableApplicationContext,不过这个接口其实也是继承了 ApplicationEventPublisher 接口。

事件异步处理

有时候,我们的一些事件是可以异步处理的,比如注册成功之后给用户发送验证邮件, 注册成功我们就可以返回了,而发送验证邮件的这一步操作可以异步进行处理, 从而加快接口的响应速度。

在 Spring 中,我们可以使用 @Async 注解来将一个 EventListener 标记为异步处理的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import org.springframework.context.event.EventListener;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;

@Component
public class AsyncMyEventListener {
@EventListener
@Async
public void listen(MyEvent myEvent) {
try {
// 模拟耗时操作
Thread.sleep(1000);
// 请求结束之后才会输出下面这一行
System.out.println("AsyncMyEventListener::listen");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}

但是,要使用 @Async 我们必须在我们的主程序类中加上 @EnableAsync 注解:

1
2
3
4
5
6
7
8
9
10
11
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.scheduling.annotation.EnableAsync;

@SpringBootApplication
@EnableAsync // 加上这个注解
public class SpringEventDemoApplication {
public static void main(String[] args) {
SpringApplication.run(SpringEventDemoApplication.class, args);
}
}

使用 @Async 注解的监听器,会放到跟请求不同的线程中处理。

指定 EventListener 的处理条件

我们可以通过 EventListenercondition 属性来决定监听器是否需要执行:

使用 SpEL 表达式,当然我们也可以把判断写到方法体内。

1
2
3
4
5
// 在 myEvent 的 foo 属性等于 'bar' 的时候才会触发
@EventListener(condition = "#myEvent.foo == 'bar'")
public void myEvent1(MyEvent myEvent) {
System.out.println("my event: foo=bar");
}

使用 condition 而不是写到方法体中的原因是:

  1. 解耦和可配置性:通过将条件与事件监听器声明分离,你可以在不修改监听器代码的情况下更改条件。这使得在不同的环境或不同的配置下轻松切换监听器的行为成为可能。
  2. 动态切换行为:允许你根据应用程序或配置来动态决定是否触发事件监听器。这对于需要根据运行时条件来启动或禁用监听器的情况非常有用。
  3. 可测试性:可以为不同的条件编写单元测试,以确保条件的正确性。
  4. 统一管理:当有多个监听器时,将条件集中管理在 condition 属性中可以提高代码的可读性,因为你可以轻松查看每个监听器的条件而无需查看每个监听器的具体实现。

监听多个事件

我们可以通过 EventListenerclasses 属性来指定要监听的多个事件:

1
2
3
4
@EventListener(classes = {MyEvent.class, AnotherEvent.class})
public void myEvent2(Object event) {
System.out.println("myEvent2: " + event.getClass());
}

这个时候,我们的参数类型就需要修改一下了。

指定事件监听器的执行顺序

我们可以通过 @Order 注解来指定一个事件的不同监听器的执行顺序:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import org.springframework.context.event.EventListener;
import org.springframework.core.annotation.Order;

@Component
public class SpringEventListener {
@Order(2)
@EventListener
public void myEvent2(MyEvent myEvent) {
// 后执行
System.out.println("my event. order = 2");
}

@Order(1)
@EventListener
public void myEvent1(MyEvent myEvent) {
// 先执行
System.out.println("my event. order = 1");
}
}

控制事件在事务提交前执行

有时候,我们会在代码中通过 @Transactional 来使用事务:

1
2
3
4
@Transactional(rollbackFor = RuntimeException.class)
public void saveFoo(Foo foo) {
fooRepository.save(foo);
}

假设我们在这个方法中有很多代码,然后其中穿插地发布了一些事件,但是我们希望这些事件在整个事务后才去触发监听器的处理逻辑, 这个时候我们就需要使用 @TransactionalEventListener 来注解我们的事件监听器,而不是使用 @EventListener

发布事件:

1
2
3
4
5
6
7
8
9
10
11
@Transactional(rollbackFor = RuntimeException.class)
public void saveFoo(Foo foo) {
// 发布了事件,但是事件处理器并不会马上处理,要等事务开始提交、结束提交的时候才会执行
// 所以我们会看到 "before save" 和 "after save" 输出在 "before commit" 之前
FooEvent event = new FooEvent(this);
eventPublisher.publishEvent(event);

System.out.println("before save");
fooRepository.save(foo);
System.out.println("after save");
}

事件监听器:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import org.springframework.stereotype.Component;
import org.springframework.transaction.event.TransactionPhase;
import org.springframework.transaction.event.TransactionalEventListener;

@Component
public class TransactionEventListener {
// 在事务提交前处理这个 FooEvent
@TransactionalEventListener(phase = TransactionPhase.BEFORE_COMMIT)
public void beforeCommit(FooEvent event) {
System.out.println("before commit: foo event.");
}

// 在事务提交后处理这个 FooEvent
// 如果事务回滚则不会处理这个 FooEvent。
@TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)
public void afterCommit(FooEvent event) {
System.out.println("after commit: foo event.");
}
}

上面的代码会输出:

1
2
3
4
before save
after save
before commit: foo event.
after commit: foo event.

@TransactionalEventListener 为我们提供了一个 phase 参数,让我们可以控制事件监听器的执行时机,它有以下可选值:

  • TransactionPhase.BEFORE_COMMIT:事务提交前
  • TransactionPhase.AFTER_COMMIT:事务提交后
  • TransactionPhase.AFTER_ROLLBACK:事务回滚后
  • TransactionPhase.AFTER_COMPLETION:事务完成后

tips:@TransactionalEventListener 并不是给我们监听事务的,只是控制事件在事务提交过程中的某一时刻触发。