Spring的事件机制

从事件机制谈起

观察者模式的事件思想

在 23 种设计模式中,观察者模式作为对象间一对多依赖关系的实现,被广为在软件设计中使用。在观察者模式中,被观察者相当于事件中的时间发布者;而观察者相当于事件中的监听者。因此可以说,观察者模式便是事件驱动机制的一种提现。

事件驱动

事件驱动的一个常见形式便是发布-订阅模式。在跨进程的通信间,我们通常采用引入 MQ (消息队列) 来实现消息的发布和订阅。目前主流应用的架构中,均采用消息的发布-订阅模式来进行大型分布式系统的解耦。使得数据生产方和使用方分离,同时 MQ 还可起到削峰等作用。

聊完跨进程,同一进程内很多时候也需要这种事件驱动机制来进行逻辑解耦。
试想如下场景:
现在系统中需要针对用户操作的行为进行记录,记录按照业务需求需存入缓存、MQ两处。如果此处不进行解耦直接在原有程序添加,代码如下:

1
2
3
4
5
public void service() {
//原有业务代码
doRecordCache();
doRecordMQ();
}

以上代码示例中,我们模拟了直接在原有代码上增加记录代码后的示例。这段代码虽然能够满足需求,但是在扩展性上很差。可能会出现的问题大概有以下两个方面:

  1. 如果我们未来需在同项目不同的业务代码中增加新的记录时,仍需要添加同样的代码,且 doRecordCache()doRecordMQ() 若想进行复用,必然要将其封装到一个新的类中,但是在这种场景下封装一个新的类是没有语义的。
  2. 如果未来该记录需要存储到第三个地方,则需要改动每个代码,实现新方式的增加,这显然是不够理想的。
    在这种情况下,使用进程内的事件发布-订阅机制便可以大大增加该场景下代码的复用性和可读性。

使用事件机制

事件机制主要由三个部分组成:事件源,事件对象,监听器,具体描述如下:

  • 事件源:事件发生的起源
  • 事件对象:事件实体,事件对象会持有一个事件源
  • 监听器:监听事件对象,对事件对象进行处理

Java 提供的事件机制

Java 提供了事件相关的接口定义,具体包含以下两个:

  • EventObject: 事件对象,自定义事件对象需要继承该类
  • EventListener: 事件监听器接口
    由于事件源 Source 不需要实现任何接口,所以 Java 中没给出相应的定义。
    一个简单的利用 Java 原生实现事件模型的例子:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
public class EventTest {
public static void main(String[] args) {
EventSource eventSource = new EventSource();
TestEventListener listener = new TestEventListener();
eventSource.addListener(listener);
eventSource.publishEvent(new TestEvent(eventSource, "一个事件"));
}
}
/**
* 事件对象
*/
class TestEvent extends EventObject {
private String msg;
/**
* Constructs a prototypical Event.
*
* @param source The object on which the Event initially occurred.
* @param msg 附加消息
* @throws IllegalArgumentException if source is null.
*/
public TestEvent(Object source, String msg) {
super(source);
this.msg = msg;
}
public String getMsg() {
return msg;
}
}
/**
* 事件监听者,按照 Java 规范应实现 EventListener 接口
*/
class TestEventListener implements EventListener {
public void handleEvent (TestEvent event) {
System.out.println("TestEvent, msg is:" + event.getMsg());
}
}
/**
* 事件源
*/
class EventSource {
private Set<EventListener> listenerSet = new HashSet<>();
public void addListener(EventListener listener) {
listenerSet.add(listener);
}
public void publishEvent(TestEvent event) {
for (EventListener eventListener : listenerSet) {
((TestEventListener) eventListener).handleEvent(event);
}
}
}

以上代码示例便是在 Java 中实现一个事件的发布与监听的过程。

Spring 事件使用

Spring 提供了事件相关的类和接口,在 Spring 项目中可以通过实现提供的接口,来实现事件的发布-订阅。Spring 的事件机制以 Java 的事件机制作为基础,按需进行了扩展。
Spring 中与事件相关的定义如下:

  • ApplicationEvent:继承 EventObject 类,事件源应继承该类;
  • ApplicationEventListener:事件监听者,该类接收一个泛型,供 ApplicationEventPublisher 在发布事件时选择 EventListener;
  • ApplicationEventPublisher:封装发布事件的方法,通知所有在 Spring 中注册的该事件的监听者进行处理;
  • ApplicationEventPublisherAware:Spring 提供的 Aware 接口之一,实现该接口的 Bean 可以获取 ApplicationEventPublisher 并进行发布事件。

一个简单利用 Spring 事件机制进行事件发布-订阅的例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
/**
* 事件源
*/
@RestController
@RequestMapping("/event")
public class EventTestController implements ApplicationEventPublisherAware {
private static final Logger logger = LoggerFactory.getLogger(EventTestController.class);
private ApplicationEventPublisher applicationEventPublisher;
@RequestMapping(value = "/publish", method = RequestMethod.GET)
public void publishEvent(String msg) throws Exception {
logger.info("msg is: {}", msg);
//Spring进行事件的通知, 无需自行处理
applicationEventPublisher.publishEvent(new TestEvent(this, msg));
}
@Override
public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
this.applicationEventPublisher = applicationEventPublisher;
}
}
/**
* 事件对象
*/
public class TestEvent extends ApplicationEvent {
private String msg;
/**
* Create a new ApplicationEvent.
*
* @param source the object on which the event initially occurred (never {@code null})
*/
public TestEvent(Object source, String msg) {
super(source);
this.msg = msg;
}
public String getMsg() {
return msg;
}
}
/**
* 事件监听者
* 事件监听者实现ApplicationListener<E extends ApplicationEvent>, 交由 Spring 进行管理,无需自己进行监听器的注册与通知过程
*/
@Component
public class TestEventListener implements ApplicationListener<TestEvent> {
private static final Logger logger = LoggerFactory.getLogger(TestEventListener.class);
@Override
public void onApplicationEvent(TestEvent event) {
logger.info("publish event, msg is: {}", event.getMsg());
}
}

以上代码中,可以看到。在 Spring 框架使用事件与在 Java 中使用时间机制其实并没有什么不同,均由 事件源、事件对象以及事件监听者组成。与 Java 原生提供的事件机制不同的是,Spring 中提供了 ApplicationEvent 类作为基类,开发者可以以此为基础定义自己的自定义事件。
在 Spring 中,继承自 ApplicationEvent 的事件对象的监听者,可以由 Spring 容器进行管理,并在发布时通过 ApplicationEventPublisher 进行发布。这就避免了我们自己实现监听者的注册和通知过程,免去了很多繁杂的过程,使得更专心于业务本身。

总结

本文主要介绍了进程内如何通过发布-订阅模式进行解耦,针对于 Spring 中的事件框架,还应有很多更深层次的挖掘,包括 ApplicationEventPublisher 的原理,以及事件处理中的一些多线程问题。这些问题将在下篇博文中进行深入探寻,了解 Spring 的设计奥妙。