观察者模式在spring中的应用

作者:王子源

1 观察者模式简介

1.1 定义

指多个对象间存在一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。这种模式有时又称作发布-订阅模式、模型-视图模式,它是对象行为型模式。

1.2 角色介绍

在观察者模式中,有以下几个角色。

主题也叫被观察者(Subject):

  1. 定义被观察者必须实现的职责,
  2. 它能动态的增加取消观察者,它一般是抽象类或者是实现类,仅仅完成作为被观察者必须实现的职责:
  3. 管理观察者并通知观察者。

观察者(Observer):
观察者接受到消息后,即进行更新操作,对接收到的信息进行处理。

具体的被观察者(ConcreteSubject):
定义被观察者自己的业务逻辑,同时定义对哪些事件进行通知。

具体的观察者(ConcreteObserver):
具体的观察者,每个观察者接收到消息后的处理反应是不同的,每个观察者都有自己的处理逻辑。

1.3 观察者模式的适用场景

  1. 对象间存在一对多关系,一个对象的状态发生改变会影响其他对象。
  2. 当一个抽象模型有两个方面,其中一个方面依赖于另一方面时,可将这二者封装在独立的对象中以使它们可以各自独立地改变和复用。
  3. 实现类似广播机制的功能,不需要知道具体收听者,只需分发广播,系统中感兴趣的对象会自动接收该广播。
  4. 多层级嵌套使用,形成一种链式触发机制,使得事件具备跨域(跨越两种观察者类型)通知。

2 观察者模式在Spring中的应用

2.1 spring的事件监听机制

Spring事件机制是观察者模式的实现。ApplicationContext中事件处理是由ApplicationEvent类和ApplicationListener接口来提供的。如果一个Bean实现了ApplicationListener接口,并且已经发布到容器中去,每次ApplicationContext发布一个ApplicationEvent事件,这个Bean就会接到通知。ApplicationEvent 事件的发布需要显示触发,要么 Spring 触发,要么我们编码触发。spring内置事件由spring触发。我们先来看一下,如何自定义spring事件,并使其被监听和发布。

2.1.1 事件

事件,ApplicationEvent,该抽象类继承了EventObject,EventObject是JDK中的类,并建议所有的事件都应该继承自EventObject。

1public abstract class ApplicationEvent extends EventObject { 2private static final long serialVersionUID = 7099057708183571937L; 3private final long timestamp = System.currentTimeMillis(); 4 5public ApplicationEvent(Object source) { 6super(source); 7} 8 9public final long getTimestamp() { 10return this.timestamp; 11} 12} 13 14

2.1.2 监听器

ApplicationListener,是一个接口,该接口继承了EventListener接口。EventListener接口是JDK中的,建议所有的事件监听器都应该继承EventListener。

1@FunctionalInterface 2public interface ApplicationListener<E extends ApplicationEvent> extends EventListener { 3void onApplicationEvent(E var1); 4} 5 6

2.1.3 事件发布器

ApplicationEventPublisher,ApplicationContext继承了该接口,在ApplicationContext的抽象实现类AbstractApplicationContext中做了实现下面我们来看一下

org.springframework.context.support.AbstractApplicationContext#publishEvent(java.lang.Object, org.springframework.core.ResolvableType)

1protected void publishEvent(Object event, @Nullable ResolvableType eventType) { 2Assert.notNull(event, "Event must not be null"); 3 4ApplicationEvent applicationEvent; 5if (event instanceof ApplicationEvent) { 6applicationEvent = (ApplicationEvent) event; 7} 8else { 9applicationEvent = new PayloadApplicationEvent<>(this, event); 10if (eventType == null) { 11eventType = ((PayloadApplicationEvent<?>) applicationEvent).getResolvableType(); 12} 13} 14if (this.earlyApplicationEvents != null) { 15this.earlyApplicationEvents.add(applicationEvent); 16} 17else { 18//获取当前注入的发布器,执行发布方法 19getApplicationEventMulticaster().multicastEvent(applicationEvent, eventType); 20} 21if (this.parent != null) { 22if (this.parent instanceof AbstractApplicationContext) { 23((AbstractApplicationContext) this.parent).publishEvent(event, eventType); 24} 25else { 26this.parent.publishEvent(event); 27} 28} 29} 30 31

我们可以看到,AbstractApplicationContext中publishEvent方法最终执行发布事件的是ApplicationEventMulticaster#multicastEvent方法,下面我们再来一起看一下multicastEvent方法

1public void multicastEvent(final ApplicationEvent event, @Nullable ResolvableType eventType) { 2ResolvableType type = (eventType != null ? eventType : resolveDefaultEventType(event)); 3Executor executor = getTaskExecutor(); 4//拿到所有的监听器,如果异步执行器不为空,异步执行 5for (ApplicationListener<?> listener : getApplicationListeners(event, type)) { 6if (executor != null) { 7executor.execute(() -> invokeListener(listener, event)); 8} 9else { 10//执行监听方法 11invokeListener(listener, event); 12} 13} 14} 15protected void invokeListener(ApplicationListener<?> listener, ApplicationEvent event) { 16ErrorHandler errorHandler = getErrorHandler(); 17if (errorHandler != null) { 18try { 19doInvokeListener(listener, event); 20} 21catch (Throwable err) { 22errorHandler.handleError(err); 23} 24} 25else { 26doInvokeListener(listener, event); 27} 28} 29 30 31private void doInvokeListener(ApplicationListener listener, ApplicationEvent event) { 32try { 33listener.onApplicationEvent(event); 34} 35catch (ClassCastException ex) { 36String msg = ex.getMessage(); 37if (msg == null || matchesClassCastMessage(msg, event.getClass())) { 38Log logger = LogFactory.getLog(getClass()); 39if (logger.isTraceEnabled()) { 40logger.trace("Non-matching event type for listener: " + listener, ex); 41} 42} 43else { 44throw ex; 45} 46} 47} 48 49

上面介绍了非spring内置的事件发布和监听执行流程。总结一下

  1. 定义一个事件,该事件继承ApplicationEvent
  2. 定义一个监听器,实现ApplicationListener接口
  3. 定义一个事件发布器,实现ApplicationEventPublisherAware接口
  4. 调用ApplicationEventPublisher#publishEvent方法.

2.2 spring事件实现原理

上面我们讲到了spring事件的发布,那么spring事件发布之后,spring是如何根据事件找到事件对应的监听器呢?我们一起来探究一下。

spring的容器初始化过程想必大家都已十分了解,这里就不过多赘述,我们直接看refresh方法在refresh方法中,有这样两个方法,initApplicationEventMulticaster()和registerListeners()

我们先来看initApplicationEventMulticaster()方法

2.2.1 initApplicationEventMulticaster()

org.springframework.context.support.AbstractApplicationContext#initApplicationEventMulticaster

1public static final String APPLICATION_EVENT_MULTICASTER_BEAN_NAME = "applicationEventMulticaster"; 2protected void initApplicationEventMulticaster() { 3//获得beanFactory 4ConfigurableListableBeanFactory beanFactory = getBeanFactory(); 5//BeanFactory中是否有ApplicationEventMulticaster 6if (beanFactory.containsLocalBean(APPLICATION_EVENT_MULTICASTER_BEAN_NAME)) { 7this.applicationEventMulticaster = 8beanFactory.getBean(APPLICATION_EVENT_MULTICASTER_BEAN_NAME, ApplicationEventMulticaster.class); 9if (logger.isTraceEnabled()) { 10logger.trace("Using ApplicationEventMulticaster [" + this.applicationEventMulticaster + "]"); 11} 12} 13else { 14//如果BeanFactory中不存在,就创建一个SimpleApplicationEventMulticaster 15this.applicationEventMulticaster = new SimpleApplicationEventMulticaster(beanFactory); 16beanFactory.registerSingleton(APPLICATION_EVENT_MULTICASTER_BEAN_NAME, this.applicationEventMulticaster); 17if (logger.isTraceEnabled()) { 18logger.trace("No '" + APPLICATION_EVENT_MULTICASTER_BEAN_NAME + "' bean, using " + 19"[" + this.applicationEventMulticaster.getClass().getSimpleName() + "]"); 20} 21} 22} 23 24

上述代码我们可以看出,spring先从BeanFactory中获取applicationEventMulticaster如果为空,则直接创建SimpleApplicationEventMulticaster

2.2.2 registerListeners()

org.springframework.context.support.AbstractApplicationContext#registerListeners

registerListeners 是将各种实现了 ApplicationListener 的监听器注册到 ApplicationEventMulticaster 事件广播器中

1 2protected void registerListeners() { 3for (ApplicationListener<?> listener : getApplicationListeners()) { 4getApplicationEventMulticaster().addApplicationListener(listener); 5} 6 7String[] listenerBeanNames = getBeanNamesForType(ApplicationListener.class, true, false); 8for (String listenerBeanName : listenerBeanNames) { 9//把监听器注册到事件发布器上 10getApplicationEventMulticaster().addApplicationListenerBean(listenerBeanName); 11} 12 13Set<ApplicationEvent> earlyEventsToProcess = this.earlyApplicationEvents; 14this.earlyApplicationEvents = null; 15if (!CollectionUtils.isEmpty(earlyEventsToProcess)) { 16//如果内置监听事件集合不为空 17for (ApplicationEvent earlyEvent : earlyEventsToProcess) { 18//执行spring内置的监听方法 19getApplicationEventMulticaster().multicastEvent(earlyEvent); 20} 21} 22} 23 24

这里解释一下earlyApplicationListeners

earlyApplicationListeners的本质还是ApplicationListener。Spring单例Ban的实例化是在Refresh阶段实例化的,那么用户自定义的一些ApplicationListener组件自然也是在这个阶段才初始化,但是Spring容器启动过程中,在Refresh完成之前还有很多事件:如Spring上下文环境准备等事件,这些事件又是Spring容器启动必须要监听的。所以Spring定义了一个earlyApplicationListeners集合,这个集合中的Listener在factories文件中定义好,在容器Refresh之前预先实例化好,然后就可以监听Spring容器启动过程中的所有事件。

当registerListeners方法执行完成,我们的监听器已经添加到多播器SimpleApplicationEventMulticaster中了,并且earlyEvent 早期事件也已经执行完毕。但是我们发现,如果自定义了一个监听器去监听spring内置的事件,此时并没有被执行,那我们注册的监听器是如何被执行的呢?答案在finishRefresh方法中。

2.2.3 finishRefresh

org.springframework.context.support.AbstractApplicationContext#finishRefresh

1protected void finishRefresh() { 2clearResourceCaches(); 3 4initLifecycleProcessor(); 5 6getLifecycleProcessor().onRefresh(); 7//容器中的类全部初始化完毕后,触发刷新事件 8publishEvent(new ContextRefreshedEvent(this)); 9 10LiveBeansView.registerApplicationContext(this); 11} 12 13

如果我们想要实现在spring容器中所有bean创建完成后做一些扩展功能,我们就可以实现ApplicationListener这样我们就可以实现其功能了。至此,Spring中同步的事件监听发布模式我们就讲解完了,当然Spring还支持异步的消息监听执行机制。

2.2.4 spring中异步的监听执行机制

我们回过头来看一下ApplicationEventMulticaster#pushEvent方法

1protected void publishEvent(Object event, @Nullable ResolvableType eventType) { 2Assert.notNull(event, "Event must not be null"); 3 4 5ApplicationEvent applicationEvent; 6if (event instanceof ApplicationEvent) { 7applicationEvent = (ApplicationEvent) event; 8} 9else { 10applicationEvent = new PayloadApplicationEvent<>(this, event); 11if (eventType == null) { 12eventType = ((PayloadApplicationEvent<?>) applicationEvent).getResolvableType(); 13} 14} 15if (this.earlyApplicationEvents != null) { 16this.earlyApplicationEvents.add(applicationEvent); 17} 18else { 19//获取当前注入的发布器,执行发布方法 20getApplicationEventMulticaster().multicastEvent(applicationEvent, eventType); 21} 22if (this.parent != null) { 23if (this.parent instanceof AbstractApplicationContext) { 24((AbstractApplicationContext) this.parent).publishEvent(event, eventType); 25} 26else { 27this.parent.publishEvent(event); 28} 29} 30 31

最终执行发布事件的是ApplicationEventMulticaster#multicastEvent方法,下面我们再来一起看一下multicastEvent方法

1public void multicastEvent(final ApplicationEvent event, @Nullable ResolvableType eventType) { 2 ResolvableType type = (eventType != null ? eventType : resolveDefaultEventType(event)); 3 Executor executor = getTaskExecutor(); 4//拿到所有的监听器,如果异步执行器不为空,异步执行 5 for (ApplicationListener<?> listener : getApplicationListeners(event, type)) { 6 if (executor != null) { 7 executor.execute(() -> invokeListener(listener, event)); 8 } 9 else { 10//执行监听方法 11 invokeListener(listener, event); 12 } 13 } 14} 15 16

可以看到,异步事件通知主要依靠SimpleApplicationEventMulticaster 类中的Executor去实现的,如果这个变量不配置的话默认事件通知是同步的, 否则就是异步通知了,要实现同时支持同步通知和异步通知就得从这里下手;我们上文已经分析过了在initApplicationEventMulticaster方法中有这样一段代码

1if (beanFactory.containsLocalBean(APPLICATION_EVENT_MULTICASTER_BEAN_NAME)) { 2this.applicationEventMulticaster = 3beanFactory.getBean(APPLICATION_EVENT_MULTICASTER_BEAN_NAME, ApplicationEventMulticaster.class); 4if (logger.isTraceEnabled()) { 5logger.trace("Using ApplicationEventMulticaster [" + this.applicationEventMulticaster + "]"); 6} 7} 8 9

如果BeanFactory中已经有了SimpleApplicationEventMulticaster则不会重新创建,那么我们可以再spring中注册一个SimpleApplicationEventMulticaster并且向其中注入对应的Executor这样我们就可以得到一个异步执行监听的SimpleApplicationEventMulticaster了,我们的通知就会通过Executor异步执行。这样可以大大提高事件发布的效率。

在springboot项目中我们可以增加一个配置类来实现

1@Configuration 2@EnableAsync 3public class Config { 4@Bean(AbstractApplicationContext.APPLICATION_EVENT_MULTICASTER_BEAN_NAME) 5public SimpleApplicationEventMulticaster simpleApplicationEventMulticaster(){ 6SimpleApplicationEventMulticaster simpleApplicationEventMulticaster = new SimpleApplicationEventMulticaster(); 7simpleApplicationEventMulticaster.setTaskExecutor(taskExecutor()); 8return simpleApplicationEventMulticaster; 9} 10 11@Bean 12public TaskExecutor taskExecutor() { 13ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); 14executor.setCorePoolSize(5); 15executor.setMaxPoolSize(20); 16executor.setQueueCapacity(100); 17executor.setKeepAliveSeconds(300); 18executor.setThreadNamePrefix("thread-"); 19executor.setRejectedExecutionHandler(new ThreadPoolExecutor.DiscardPolicy()); 20executor.setWaitForTasksToCompleteOnShutdown(true); 21return executor; 22} 23} 24 25

spring项目中我们也可以增加如下xml配置

1<!--定义事件异步处理--> 2<bean id="commonTaskExecutor" 3class="org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor"> 4<!-- 线程池维持处于Keep-alive状态的线程数量。如果设置了allowCoreThreadTimeOut为true,该值可能为0。 5并发线程数,想达到真正的并发效果,最好对应CPU的线程数及核心数 --> 6<property name="corePoolSize" value="5" /> 7<!-- 最大线程池容量 --> 8<property name="maxPoolSize" value="20" /> 9<!-- 超过最大线程池容量后,允许的线程队列数 --> 10<property name="queueCapacity" value="100" /> 11<!-- 线程池维护线程所允许的空闲时间 .单位毫秒,默认为60s,超过这个时间后会将大于corePoolSize的线程关闭,保持corePoolSize的个数 --> 12<property name="keepAliveSeconds" value="300" /> 13<!-- 允许核心线程超时: false(默认值)不允许超时,哪怕空闲;true则使用keepAliveSeconds来控制等待超时时间,最终corePoolSize的个数可能为0 --> 14<property name="allowCoreThreadTimeOut" value="true" /> 15 16<!-- 线程池对拒绝任务(无线程可用)的处理策略 --> 17<property name="rejectedExecutionHandler"> 18<bean class="java.util.concurrent.ThreadPoolExecutor$CallerRunsPolicy" /> 19<!-- java.util.concurrent.ThreadPoolExecutor$CallerRunsPolicy:主线程直接执行该任务,执行完之后尝试添加下一个任务到线程池中 --> 20<!-- java.util.concurrent.ThreadPoolExecutor$AbortPolicy:直接抛出java.util.concurrent.RejectedExecutionException异常 --> 21</property> 22</bean> 23 24<!--名字必须是applicationEventMulticaster,因为AbstractApplicationContext默认找个--> 25<bean id="applicationEventMulticaster" class="org.springframework.context.event.SimpleApplicationEventMulticaster"> 26<!--注入任务执行器 这样就实现了异步调用--> 27<property name="taskExecutor" ref="commonTaskExecutor"></property> 28</bean> 29 30

3 小结

本文主要讲解了观察者模式在spring中的应用及事件监听机制,JDK 也有实现提供事件监听机制Spring 的事件机制也是基于JDK 来扩展的。Spring 的事件机制默认是同步阻塞的,想要提升对应的效率要考虑异步事件。

点赞
收藏

评论区

加载中...

相关推荐

Oracle 分组与拼接字符串同时使用

SELECTT.,ROWNUMIDFROM(SELECTT.EMPLID,T.NAME,T.BU,T.REALDEPART,T.FORMATDATE,SUM(T.S0)S0,MAX(UPDATETIME)CREATETIME,LISTAGG(TOCHAR(

MySQL部分从库上面因为大量的临时表tmp_table造成慢查询

背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_

FLV文件格式

1.        FLV文件对齐方式FLV文件以大端对齐方式存放多字节整型。如存放数字无符号16位的数字300(0x012C),那么在FLV文件中存放的顺序是:|0x01|0x2C|。如果是无符号32位数字300(0x0000012C),那么在FLV文件中的存放顺序是:|0x00|0x00|0x00|0x01|0x2C。2.  

mysql设置时区

mysql设置时区mysql\_query("SETtime\_zone'8:00'")ordie('时区设置失败,请联系管理员!');中国在东8区所以加8方法二:selectcount(user\_id)asdevice,CONVERT\_TZ(FROM\_UNIXTIME(reg\_time),'08:00','0

Javascript中创建函数的几种方法

//工厂函数模式//无法解决对象识别问题functionperson0(name,age,job){varobjnewObject();obj.namename;obj.ageage;obj.jobjob;returno

PHP状态模式

状态设计模式状态模式的作用是允许对象在状态改变时改变其行为对象中频繁的状态非常依赖于条件语句,就自身来说条件语句并没有什么问题,不过,如果选项太多,以至于程序出现混乱,或者增加或改变选项需要太多的是时间。<?php/CreatedbyPhpStorm.User:ge

观察者模式在spring中的应用 - HelloWorld