机制详解
Spring提供了事件机制,其本质是JDK提供的事件机制的应用,利用的是观察者设计模式,具体请看设计模式之观察者模式(Observer Pattern)。
这里我们来分析Spring事件机制的原理。
先上UML图,不熟悉UML规则的可以看UML类图的制作规则。
下面我们对上图中涉及到的几个类进行讲解。
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请求结束触发该事件。在一些特殊场景里,可能会应用到该事件。