1.背景
事件驱动的一个常见形式便是发布-订阅模式。在跨进程的通信间,我们通常采用引入 MQ (消息队列) 来实现消息的发布和订阅。目前主流应用的架构中,均采用消息的发布-订阅模式来进行大型分布式系统的解耦。使得数据生产方和使用方分离,同时 MQ 还可起到削峰等作用。
同一进程内很多时候也需要这种事件驱动机制来进行逻辑解耦。 试想如下场景: 现在系统中需要针对用户操作的行为进行记录,记录按照业务需求需存入缓存、MQ两处。如果此处不进行解耦直接在原有程序添加,代码如下:
public void xxService() {
//原有业务代码
recordCache();
recordMQ();
}
解析:
在上面代码示例中,我们模拟了直接在原有代码上增加记录代码后的示例。这段代码虽然能够满足需求,但是在扩展性上很差。可能会出现的问题大概有以下两个方面: 1. 如果我们未来需在同项目不同的业务代码中增加新的记录时,仍需要添加同样的代码,且 recordCache() 和 recordMQ() 若想进行复用,必然要将其封装到一个新的类中,但是在这种场景下封装一个新的类是没有语义的。 2. 如果未来该记录需要存储到第三个地方,则需要改动每个代码,实现新方式的增加,这显然是不够理想的。 在这种情况下,使用进程内的事件发布-订阅机制便可以大大增加该场景下代码的复用性和可读性。
2.使用
事件机制主要由三个部分组成:事件源,事件对象,监听器,具体描述如下: - 事件源:事件发生的起源 - 事件对象:事件实体,事件对象会持有一个事件源 - 监听器:监听事件对象,对事件对象进行处理。
(1)Java 提供的事件机制
Java 提供了事件相关的接口定义,具体包含以下两个: - EventObject: 事件对象,自定义事件对象需要继承该类 - EventListener: 事件监听器接口 由于事件源 Source 不需要实现任何接口,所以 Java 中没给出相应的定义。
展示一个简单的利用 Java 原生实现事件模型的例子:
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);
}
}
}
(2)Spring 事件使用
Spring 提供了事件相关的类和接口,在 Spring 项目中可以通过实现提供的接口,来实现事件的发布-订阅。Spring 的事件机制以 Java 的事件机制作为基础,按需进行了扩展。 Spring 中与事件相关的定义如下: - ApplicationEvent:继承 EventObject 类,事件源应继承该类; - ApplicationEventListener:事件监听者,该类接收一个泛型,供 ApplicationEventPublisher 在发布事件时选择 EventListener; - ApplicationEventPublisher:封装发布事件的方法,通知所有在 Spring 中注册的该事件的监听者进行处理; - ApplicationEventPublisherAware:Spring 提供的 Aware 接口之一,实现该接口的 Bean 可以获取 ApplicationEventPublisher 并进行发布事件。
展示一个简单利用 Spring 事件机制进行事件发布-订阅的例子:
/**
* 事件源
*/
@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 的原理,以及事件处理中的一些多线程问题。这个放在日后与各位一起探讨。