一. 监听器的使用
为什么要学习监听器呢?学习监听器主要学习监听器的设计思想。 比如,我们之前研究过的nacos,他就是使用监听器进行集成的。所以了解监听器的原理,就很重要了。
首先, 我们要知道监听器如何使用。
1.1 Spring事件的原理
原理: 是观察者模式
Spring的事件监听有三个组成部分:
1. 事件(ApplicationEvent):要广播,发送的消息. 监听器监听的事情
2. 监听器(ApplicationListener): 观察者模式中的观察者, 监听器监听特定事件, 并在内部定义了事件发生后的相应逻辑.
3. 事件发布器(ApplicationEventMulticaster):对应于观察者模式中的被观察者/主题.负责通知观察者. 对外提供发布事件和增删事件监听器的接口.维护事件和事件监听器之间的关系.并在事件发生时负责通知事件监听器.
1.2 认识监听器
上面认识了监听器. 接下来看一个例子. 通过例子来理解.
就好比现在有一个消息, 比如说: 下单后减库存. 减库存就是一个事件, 这个事件需要一个事件播放器, 将事件播放出去. 然后另一端事件监听器, 接收到信息,进行处理.
比如:下面的demo
有一个订单Order :
package com.lxl.www.events; /** * Description * * DATE 2020/11/17. * * @author lxl. */ public class Order { private Integer id; public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } }
接下来, 有一个订单事件. 订单的操作,带来的库存的增减. 就是一个订单事件
package com.lxl.www.events; import org.springframework.context.ApplicationEvent; import java.io.Serializable; /** * Description * 订单的事件 * * 事件的分类: 分为自定义事件和内置事件 * DATE 2020/11/17. * * @author lxl. */ public class OrderEvent extends ApplicationEvent implements Serializable { private static final long serialVersionUID = 1L; private String name; public OrderEvent(Object event, String name) { super(event); this.name = name; } public String getName() { return name; } public void setName(String name) { this.name = name; } }
第三: 事件监听器 ,事件监听器用来监听事件. 当OrderEvent发布减库存消息的时候, 事件监听器就能听到.
package com.lxl.www.events; import org.springframework.context.ApplicationListener; import org.springframework.stereotype.Component; /** * Description * OrderEvent的事件监听器 * * * DATE 2020/11/17. * * @author lxl. */ @Component public class OrderEventListenter implements ApplicationListener<OrderEvent> { /** * 当某一个事件发布的时候, 就会触发事件监听器 * @param event the event to respond to */ @Override public void onApplicationEvent(OrderEvent event) { if (event.getName().equals("减库存")) { System.out.println("事件监听器 监听到 减库存"); } } }
是不是和mq相差不多.
mq也是一个订阅者,一个发布者.
下面写一个main方法, 运行看看监听器的效果
package com.lxl.www.events; import org.springframework.beans.factory.parsing.SourceExtractor; import org.springframework.context.annotation.AnnotationConfigApplicationContext; /** * 监听器的使用 */ public class MainClass { public static void main(String[] args) { AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(MainConfig.class); /** * 使用场景: 比如有一个订单, 由用户下单了,那么对应的就要减库存.其实下单和减库存不需要是串行. * 通常, 我们会使用一个mq去处理减库存的情况. 也就是采用异步的方式. * * 那么, 监听器的远离和mq是类似的. 我们可以手动设置采用同步还是异步的方式处理. */ Order order = new Order(); order.setId(1); System.out.println("下单"); // 发布事件. 当在这里发布事件, 那么就会被事件监听器监听到 ctx.publishEvent(new OrderEvent(order, "减库存")); System.out.println("日志....."); } }
输出结果
下单
事件监听器 监听到 减库存
日志.....
监听器使用的设计模式是: 观察者模式.
1.3 监听器的类型
监听器有两种类型: 一种是内置的监听器, 一种是自定义监听器.
1.3.1 内置监听器
spring设置了一个内置监听器的父类.
public abstract class ApplicationContextEvent extends ApplicationEvent { /** * Create a new ContextStartedEvent. * @param source the {@code ApplicationContext} that the event is raised for * (must not be {@code null}) */ public ApplicationContextEvent(ApplicationContext source) { super(source); } /** * Get the {@code ApplicationContext} that the event was raised for. */ public final ApplicationContext getApplicationContext() { return (ApplicationContext) getSource(); } }
实现了ApplicationContextEvent的类就是内置的监听器. 我们使用快捷键ctrl + H, 查看都有哪些类实现了 ApplicationContextEvent
一共有5各类实现了ApplicationContextEvent.
Event | 说明 |
ContextRefreshEvent | 当容器被实例化或者refresh时发布.如调用refresh()方法. 此处的实例化是指所有的bean都已被加载,后置处理器都被激活,所有单例bean都已被实例化,所有的容器对象 都已经准备好可使用. 如果容器支持热重载,则refresh()可以被触发多次(XmlWebApplicationContext支持热刷新, 而GenericApplicationContext不支持热刷新) |
ContextStartedEvent | 当容器启动时发布, 即调用start()方法, 已启用意味着所有的lifecycle都已显示收到了start的信号 |
ContextStoppedEvent | 当容器停止时发布. 即调用stop()方法, 既所有的lifecycle bean都已显示接收了stop信号, 关闭的容器可以通过start()方法重启 |
ContextClosedEvent | 当容器关闭时发布. 即调用close()方法, 关闭意味着所有的单例bean都已被销毁. 关闭的容器不能被重启或refresh() |
1. ContextRefreshEvent: 当容器被实例化或者refresh时发布
我们来看看一下源码.
从refresh()源码进入.
public AnnotationConfigApplicationContext(Class<?>... componentClasses) { // 进入构造函数, 首先调用自身的构造方法this(); // 调用自身的构造方法之前, 要先调用父类的构造方法 this(); // register配置注册类 register(componentClasses); // ioc容器刷新接口--非常重要 refresh(); }
/** * refresh是spring最核心的方法, 里面包含了整个spring ioc的全过程, 包括spring加载bean到销毁bean的全过程 * 学习spring, 就是学习里面的13个方法, 如果13个方法都学完了, 基本上就打通了 * @throws BeansException * @throws IllegalStateException */ @Override public void refresh() throws BeansException, IllegalStateException { synchronized (this.startupShutdownMonitor) { // 1. 准备刷新上下文环境 prepareRefresh(); // Tell the subclass to refresh the internal bean factory. //2. 获取告诉子类初始化bean工厂, 不同工厂不同实现 ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory(); ...... // Last step: publish corresponding event. //最后容器刷新 发布刷新时间(spring cloud是从这里启动的 ) finishRefresh(); } ...... } }
进入到finishRefresh()方法
protected void finishRefresh() { // Clear context-level resource caches (such as ASM metadata from scanning). // 清除上下文缓存 clearResourceCaches(); // Initialize lifecycle processor for this context. // 注册lifecycleProcessor声明周期处理器 // 作用: 当ApplicationContext启动或停止时, 他会通过LifecycleProcessor来与所有声明的bean进行交互 initLifecycleProcessor(); // Propagate refresh to lifecycle processor first. // 为实现了SmartLifeCycle并且isAutoStartup, 自动启动的Lifecycle调用start()方法 getLifecycleProcessor().onRefresh(); // 发布容器启动完毕事件 publishEvent(new ContextRefreshedEvent(this)); // Participate in LiveBeansView MBean, if active. LiveBeansView.registerApplicationContext(this); }
我们看到有一个发布事件. 这个事件的作用是通知容器已经启动完毕. 注意看, 里面发布的是什么事件? new ContextRefreshedEvent(this). 发布的是ContextRefreshedEvent事件.
下面有一个问题: 怎么样可以在所有的bean创建完以后做扩展代码呢?
上面我们说到了, 当所有的bean都创建完以后, 会调用publishEvent(new ContextRefreshedEvent(this));发布容器启动完毕的事件.
这时我们可以自定义一个监听器, 用来监听ContextRefreshedEvent事件.
/** * 自定义一个事件监听器, 用来监听ContextRefreshedEvent事件 */ @Component public class ContextRefreshedEventListener { /** * 声明这是一个事件监听器, 监听的是ContextRefreshedEvent事件. * @param event */ @EventListener(ContextRefreshedEvent.class) public void onApplicationEvent(ContextRefreshedEvent event) { .... // 在所有的bean创建完以后, 写一些逻辑代码 } }
然后, 在里面写上我们需要在容器都创建完毕之后执行的逻辑代码.
2. ContextClosedEvent: 当容器关闭时发布
还是先来看源码, spring是在何时发布的这个事件.
protected void doClose() { // Check whether an actual close attempt is necessary... if (this.active.get() && this.closed.compareAndSet(false, true)) { if (logger.isDebugEnabled()) { logger.debug("Closing " + this); } LiveBeansView.unregisterApplicationContext(this); try { // Publish shutdown event. publishEvent(new ContextClosedEvent(this)); } catch (Throwable ex) { logger.warn("Exception thrown from ApplicationListener handling ContextClosedEvent", ex); } // Stop all Lifecycle beans, to avoid delays during individual destruction. if (this.lifecycleProcessor != null) { try { this.lifecycleProcessor.onClose(); } catch (Throwable ex) { logger.warn("Exception thrown from LifecycleProcessor on context close", ex); } } // Destroy all cached singletons in the context's BeanFactory. destroyBeans(); // Close the state of this context itself. closeBeanFactory(); // Let subclasses do some final clean-up if they wish... onClose(); // Reset local application listeners to pre-refresh state. if (this.earlyApplicationListeners != null) { this.applicationListeners.clear(); this.applicationListeners.addAll(this.earlyApplicationListeners); } // Switch to inactive. this.active.set(false); } }
在doClose()的时候, 发布了publishEvent(new ContextClosedEvent(this));事件
我们看一看具体发布的是什么事件呢? 就是ContextClosedEvent事件
假如: 我们想要在容器关闭的时候做一些扩展, 就可以写一个监听器, 在容器关闭的时候监听ContextClosedEvent事件
Spring内置的事件, 我们就不用再自己定义了. 我们需要做的就是定义一个监听器, 监听事件就可以了.
1.3.2 自定义监听器
不是spring定义的监听器, 也就是我们自己定义的监听器就是自定义监听器. 下面来看看自定义监听器的两种类型.
类型一: 基于接口
@Component public class HelloEventListener implements ApplicationListener<OrderEvent> { @Override public void onApplicationEvent(OrderEvent event) { if (event.getName().equals("减库存")) { System.out.println("减库存...."); } } }
事件监听器需要实现ApplicationListener接口, 这是一个泛型接口, 泛型的类型就是事件的类型.
其次, 这个监听器需要是spring容器托管的bean, 因此加上了@Component注解, 里面只有一个方法onApplicationEvent, 就是事件触发时执行的内容.
类型二: 基于注解
@Component public class OrderEventListener { @EventListener(OrderEvent.class) public void onApplicationEvent(OrderEvent event) { if (event.getName().equals("减库存")) { System.out.println("减库存...."); } } }
在方法上面添加注解@EventListener(OrderEvent.class) 监听的是哪个事件呢?OrderEvent.class
我们在定义监听器的时候, 可以选择是基于接口的方式还是基于注解的方式.
二. 监听器源码
首先, 监听器的声明,调用,都是在refresh()方法里面进行,我们先来看看refresh()的整体脉络. 其中标红的部分是和监听器有关系的模块.
这里面的第五步, 第九步, 第十一步, 都详细的分析过. 下面主要看看和监听器有关的几步.
2.1 准备上下文环境prepareRefresh()
在准备上下文环境的时候, 我们看看做了哪些事情
1. 设置了容器当期的状态, 是激活状态
2. 初始化了属性源initPropertySources();.
在AbstractApplicationContext类中没有实现这个方法. 这是一个父类定义的方法. 比如:我们可以自定义一个类, 然后重写initPropertySource, 在改方法中设置一个环境变量abc, 那么在容器启动的时候, 就会去环境变量中检查, 是否环境变量中有这个属性, 如果没有就会抛出异常.
3. 接下来就是验证上面环境变量中指定的属性是否存在了. getEnvironment().validateRequiredProperties(); 不存在就抛出异常MissingRequiredPropertiesException
4. 然后接下来,和事件有关的一步, 创建了早期的事件监听器
// 创建早期的事件监听器. // Store pre-refresh ApplicationListeners... if (this.earlyApplicationListeners == null) { this.earlyApplicationListeners = new LinkedHashSet<>(this.applicationListeners); } else { // Reset local application listeners to pre-refresh state. this.applicationListeners.clear(); this.applicationListeners.addAll(this.earlyApplicationListeners); }
这里有一个问题, 什么是早期的事件监听器呢? 早对应的就是晚了. 早期指的是多早呢?
早期事件指的是事件监听器还没有注册到事件多播器的时候.
早期定义的事件不需要手动的publishEvent, 在RegisterListener()阶段会自动发布早期事件.
什么是早期的事件监听器呢? 早对应的就是晚了. 早期指的是多早呢?
早期事件指的是事件监听器还没有注册到事件多播器的时候.
早期定义的事件不需要手动的publishEvent, 在RegisterListener()阶段会自动发布早期事件.
在这里就定义了一个集合, 这个集合就是后面事件监听器集合. 在这里只是进行的初始化
5. 初始化保存早期事件的集合
this.earlyApplicationEvents = new LinkedHashSet<>();
在第一步: 对事件的操作就是初始化. 一共初始化了两个集合, 一个是早期事件监听器集合, 一个是早期的事件集合