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 的原理,以及事件处理中的一些多线程问题。这个放在日后与各位一起探讨。