Spring之事件机制详解

机制详解

Spring提供了事件机制,其本质是JDK提供的事件机制的应用,利用的是观察者设计模式,具体请看设计模式之观察者模式(Observer Pattern)。
这里我们来分析Spring事件机制的原理。
先上UML图,不熟悉UML规则的可以看UML类图的制作规则。

Spring之事件机制详解_事件机制

下面我们对上图中涉及到的几个类进行讲解。
ApplicationEvent:
抽象类,继承了JDK的EventObject接口,起到包装事件源的作用。
ApplicationListener:
实现了JDK的EventListener接口,起到监听器的作用。

ApplicationEventMulticaster类:
在观察者模式中,一定要有一个管理维护监听者列表的功能。在Spring的事件机制中,将维护监听者列表的功能单独定义了一个接口,即ApplicationEventMulticaster接口。这也体现了单一责任原则的设计思想。我们看其源码:

public interface ApplicationEventMulticaster {

/**
* Add a listener to be notified of all events.
* @param listener the listener to add
*/
void addApplicationListener(ApplicationListener<?> listener);

/**
* Add a listener bean to be notified of all events.
* @param listenerBeanName the name of the listener bean to add
*/
void addApplicationListenerBean(String listenerBeanName);

/**
* Remove a listener from the notification list.
* @param listener the listener to remove
*/
void removeApplicationListener(ApplicationListener<?> listener);

/**
* Remove a listener bean from the notification list.
* @param listenerBeanName the name of the listener bean to remove
*/
void removeApplicationListenerBean(String listenerBeanName);

/**
* Remove all listeners registered with this multicaster.
* <p>After a remove call, the multicaster will perform no action
* on event notification until new listeners are registered.
*/
void removeAllListeners();

/**
* Multicast the given application event to appropriate listeners.
* <p>Consider using {@link #multicastEvent(ApplicationEvent, ResolvableType)}
* if possible as it provides better support for generics-based events.
* @param event the event to multicast
*/
void multicastEvent(ApplicationEvent event);

/**
* Multicast the given application event to appropriate listeners.
* <p>If the {@code eventType} is {@code null}, a default type is built
* based on the {@code event} instance.
* @param event the event to multicast
* @param eventType the type of event (can be {@code null})
* @since 4.2
*/
void multicastEvent(ApplicationEvent event, @Nullable ResolvableType eventType);

}

可以看到,这个接口定义了增删查监听者的方法,所以,监听者列表的维护通过这个接口实现。需要注意的是这个接口还定义了multicastEvent方法。通过这个方法,将事件传给监听器。所以这个类,将事件和监听器,连接在一起。这里采用的是中介者模式,这个接口就是中介者角色。
ApplicationEventPublisher:
Spring设计的事件发布类,我们看其源码:


public interface ApplicationEventPublisher {

/**
* Notify all <strong>matching</strong> listeners registered with this
* application of an application event. Events may be framework events
* (such as ContextRefreshedEvent) or application-specific events.
* <p>Such an event publication step is effectively a hand-off to the
* multicaster and does not imply synchronous/asynchronous execution
* or even immediate execution at all. Event listeners are encouraged
* to be as efficient as possible, individually using asynchronous
* execution for longer-running and potentially blocking operations.
* @param event the event to publish
* @see #publishEvent(Object)
* @see org.springframework.context.event.ContextRefreshedEvent
* @see org.springframework.context.event.ContextClosedEvent
*/
default void publishEvent(ApplicationEvent event) {
publishEvent((Object) event);
}

/**
* Notify all <strong>matching</strong> listeners registered with this
* application of an event.
* <p>If the specified {@code event} is not an {@link ApplicationEvent},
* it is wrapped in a {@link PayloadApplicationEvent}.
* <p>Such an event publication step is effectively a hand-off to the
* multicaster and does not imply synchronous/asynchronous execution
* or even immediate execution at all. Event listeners are encouraged
* to be as efficient as possible, individually using asynchronous
* execution for longer-running and potentially blocking operations.
* @param event the event to publish
* @since 4.2
* @see #publishEvent(ApplicationEvent)
* @see PayloadApplicationEvent
*/
void publishEvent(Object event);

}

里面定义了publishEvent方法,进行事件的发布。但是事件不是直接发布到listener中,而是发布在ApplicationEventMulticaster类中,所以在ApplicationEventPublisher类中,一定会有ApplicationEventMulticaster对象,将事件发布到ApplicationEventMulticaster中。

事件流程总结:
通过上面几个类的描述,我们总结一下spring事件机制的流程:
流程的核心,就是PublishEvent。Event对象以参数的形式传入PublishEvent对象。然后将Event事件传入ApplicationEventMulticaster类中,由ApplicationEventMulticaster类将事件传给其维护的监听者,执行监听者方法。

领悟
由上面Spring设计事件模式思路我们可以感受到,Spring把单一的功能,都拎出来形成了一套接口规范,然后多个接口规范组合,去完成一件事情。所以我们在阅读源码时会感觉很乱。只要我们分析清楚每个对象的设计思路和作用是什么,再分析他们之间的组合完成了什么事情,就很容易理解其设计理念了。

应用
上面分析了Spring事件机制的运行原理,那么对我们实际开发中,有何帮助呢?
这就需要我们结合源码进行解读了。
笔者找到一篇写的很好的博文,大家可以参考:浅谈Spring事件监听。

我们可以自定义事件源,如下:

@Component
public class MyEventSource {
public void ccc(){
System.out.println("事件源方法");
}
}

然后定义Event对象,包装事件源:

@Component
public class MyEvent extends ApplicationEvent {
public MyEvent(MyEventSource source) {
super(source);
}

public void eventMethod(){
System.out.println("事件自定义方法");
}
}

这里我们需要注意,有参构造中,传入事件源,我们还可以在事件类中定义其他的方法,在监听者中调用。因为监听者监听的是事件类,所以可以直接调到事件类的自定义方法。

下面,我们定义监听者:

@Component
public class MyListener implements ApplicationListener {
@Override
public void onApplicationEvent(ApplicationEvent applicationEvent) {
if(applicationEvent instanceof MyEvent) {
((MyEvent)applicationEvent).eventMethod();
MyEventSource eventSource = (MyEventSource) applicationEvent.getSource();
eventSource.ccc();
System.out.println("监听者发生一些改变");
}
}
}

需要注意的是,如果我们让Spring自带的事件发布类去发布事件,上面我们提到,会发布到ApplicationEventMulticaster 中,而Spring默认的ApplicationEventMulticaster 类会把所有的事件都发布给监听者,它并不会去做过滤,什么事件发给什么监听者。所以我们只能在每个监听者方法中去做判断,事件类型属于某个类型时,才做处理。
如果我们想特定的事件发布给特定的监听者,那我们只能自己实现Spring的发布类和ApplicationEventMulticaster类,自己定义事件的发布机制。

更正:
ApplicationListener类是支持泛型的,在类后定义泛型,可以过滤掉其他的事件对象,只接收泛型类事件。

最后,也是最关键的步骤,万事俱备只欠东风了,事件的发布,是需要我们在业务代码中自行进行发布的,这里我们用ApplicationContext作为发布者,进行事件的发布,代码如下:

public class MyTest {

@Test
public void test1() {
ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
Object o=applicationContext.getBean("myEvent");
applicationContext.publishEvent(applicationContext.getBean("myEvent"));


}
}

总结
Spring事件机制其实就是发布-订阅思想的一个体现,只不过,Spring中的订阅发布只限于在一个Spring容器中完成。而我们平时使用的订阅发布,都是分布式环境下的,一般采用MQ的方式完成。在实际的应用开发中,一个容器中进行订阅发布的需求并不是很多,当有些特殊需求可以转化成容器内的订阅发布时,希望大家可以想到应用Spring提供的事件机制。
在单机环境中,我们要实现代码的解耦,可以采用事件机制。例如:在前面讲观察者模式时,我们把监听者的放在事件类中维护的方式,就是高耦合的。而Spring将其进行了解耦。

补充:
Spring为我们提供了一些事件类。我们可以利用这些现成的事件,自定义监听者,来做一些业务。下面我们介绍常用的事件。
ContextRefreshedEvent(上下文更新完成事件):在spring容器初始化流程完成后,触发事件。事件的触发(即调用publishEvent方法)是Spring自己调用的,我们只需定义Listener监听者处理业务即可。
如之前一个项目,在项目启动的时候有一个while(true){…}死循环,造成了项目无法正常启动。当时的解决方案是将死循环另起了一个线程解决的。现在可以采用ContextRefreshedEvent事件解决。当容器加载完成后,再执行死循环业务。代码如下:

@Component
public class MyListener implements ApplicationListener<ContextRefreshedEvent> {

@Override
public void onApplicationEvent(ContextRefreshedEvent event) {
while (true){
System.out.println("执行业务");
}
}
}

RequestHandledEvent:在web应用中,一个http请求结束触发该事件。在一些特殊场景里,可能会应用到该事件。