【Spring框架】Spring监听器的简介和基本使用
本篇文章带你快速了解Spring事件监听器的使用方法和运作模式。
目录
2.2 事件监听器(ApplicationListener)
2.2.1.1 SmartApplicationListener
2.2.1.2 GenericApplicationListener
2.3 事件发布者(ApplicationEventPublisher)
2.4 事件广播器(ApplicationEventMulticaster)
3.1.1 通过实现ApplicationListener接口创建监听器
一、观察者模式
Spring的监听器用到了观察者模式,工厂模式(EventListenerFactory),适配器模式(ApplicationListenerMethodAdapter),而其中最重要的就是基于观察者模式,它能实现事件与事件监听者直接的解耦。所以在讲解监听器之前,我们先讲一下观察者模式。
1.1 模型介绍
观察者模式(Observer Pattern)是一种行为设计模式,它用于在对象之间建立一对多的依赖关系。在该模式中,当一个对象的状态发生变化时,它会自动通知其依赖对象(称为观察者),使它们能够自动更新。
观察者模式的工作原理如下:
- 主题对象维护一个观察者列表,并提供方法用于添加和删除观察者。
- 当主题的状态发生变化时(监听到某种事件发生),它会遍历观察者列表,并调用每个观察者的通知方法。
- 观察者接收到通知后,根据通知进行相应的更新操作。
以我的理解,就是一个主题会去监听一种事件,和这个事件相关的观察者都会被加到这个主题对象中。当主题对象监听到这个事件发生了,就会去通知自己维护的所有观察者,这些观察者就回去执行相应的逻辑。也就是一种主题对应一种事件,要观察这个事件的观察者都添加到这个主题中,由主题去监听这个事件是否发生,事件发生后主题回去通知所有的观察者,观察者们再去执行相应的逻辑。
观察者模式角色:
- Subject:抽象主题(抽象被观察者),抽象主题角色把所有观察者对象保存在一个集合里,每个主题都可以有任意数量的观察者,抽象主题提供一个接口,可以增加和删除观察者对象。
- ConcreteSubject:具体主题(具体被观察者),该角色将有关状态存入具体观察者对象,在具体主题的内部状态发生改变时,给所有注册过的观察者发送通知。
- Observer:抽象观察者,是观察者者的抽象类,它定义了一个更新接口,使得在得到主题更改通知时更新自己。
- ConcrereObserver:具体观察者,实现抽象观察者定义的更新接口,以便在得到主题更改通知时更新自身的状态。
1.2 观察者模式Demo
一个观察者模式demo包括以下部分:
- 观察者实体
- 主题实体
1.2.1 观察者实体
所以我们先写个观察者接口
interface Observer {
void update();
}
再构建两个观察者实现类
// 具体观察者A
class ConcreteObserverA implements Observer {
@Override
public void update() {
System.out.println("ConcreteObserverA收到更新通知");
}
}
// 具体观察者B
class ConcreteObserverB implements Observer {
@Override
public void update() {
System.out.println("ConcreteObserverB收到更新通知");
}
}
1.2.2 主题实体
然后定义主题接口
interface Subject {
// 注册新的观察者
void registerObserver(Observer observer);
// 删除指定观察者
void removeObserver(Observer observer);
// 通知全部观察者
void notifyObservers();
}
构建一个具体主题
// 具体主题
class ConcreteSubject implements Subject {
// 一个主题维护多个观察者
private List<Observer> observers = new ArrayList<>();
@Override
public void registerObserver(Observer observer) {
observers.add(observer);
}
@Override
public void removeObserver(Observer observer) {
observers.remove(observer);
}
@Override
public void notifyObservers() {
for (Observer observer : observers) {
observer.update();
}
}
public void doSomething() {
System.out.println("主题执行某些操作...");
// 执行操作后通知观察者
notifyObservers();
}
}
1.2.3 测试代码
public class ObserverPatternDemo {
public static void main(String[] args) {
// 创建主题和观察者
ConcreteSubject subject = new ConcreteSubject();
Observer observerA = new ConcreteObserverA();
Observer observerB = new ConcreteObserverB();
// 注册观察者
subject.registerObserver(observerA);
subject.registerObserver(observerB);
// 执行主题的操作,触发通知
subject.doSomething();
}
}
最后可以看到结果:
主题执行某些操作…
ConcreteObserverA收到更新通知
ConcreteObserverB收到更新通知
二、Spring监听器的介绍
Spring事件体系包括三个组件:
- 事件广播器(事件多播器、事件发布器),可以理解为就是applicationContext。
- 事件ApplicationEvent。
- 事件监听器ApplicationListener。
实现Spring事件机制主要有4个类:
- ApplicationEvent:事件,每个实现类表示一类事件,可携带数据。
- ApplicationListener:事件监听器,用于接收事件处理时间。
- ApplicationEventMulticaster:事件管理者,用于事件监听器的注册和事件的广播。
- ApplicationEventPublisher:事件发布者,委托ApplicationEventMulticaster完成事件发布。
2.1 事件(ApplicationEvent)
事件(ApplicationEvent) 是特定事件监听器被触发的原因。
2.1.1 Spring内置事件
内置事件中由系统内部进行发布,只需自己注入监听器即可。
Spring容器启动的时候在不同阶段会发布不同事件,我们可以根据自己的需求进行监听。(阿里的nacos服务自动注册原理就是监听到web容器初始化完成事件,完成自动注册发现)。例如Spring容器启动完之后,会发布一个ContextRefreshedEvent事件,我们可以实现自己的监听器进行监听。
@Component
// 设置用于监听ContextRefreshedEvent事件的监听器
public class MyListener2 implements ApplicationListener<ContextRefreshedEvent> {
@Override
public void onApplicationEvent(ContextRefreshedEvent event) {
System.out.println("-----------------:"+event.getSource().getClass());
}
}
可以使用Spring内置的事件来对注册到容器中的Bean进行拓展,重点看内置的ContextRefreshedEvent和ContextClosedEvent这两个事件,一个是在刚完成容器初始化的时候被发布的事件,一个是关闭容器时发布的事件。我们只要去编写监听这两个事件的监听器,来自定义实现一些逻辑,就能做到对注册到容器中的Bean进行拓展了。
内置的事件类型:
Event | 说明 |
ContextRefreshedEvent | 当容器被实例化或refreshed时发布。如调用refresh()方法,此处的实例化是指所有的bean都已被加载,后置处理器都被激活,所有单例bean都已被实例化,所有的容器对象都已准备好可使用。如果容器支持热重载,则refresh可以被触发多次(XmlWebApplicatonContext支持热刷新,而 GenericApplicationContext则不支持) |
ContextStartedEvent | 当容器启动时发布,即调用start()方法,已启用意味着所有的Lifecycle bean都已显式接收到了start 信号 |
ContextStoppedEvent | 当容器停止时发布,即调用stop()方法,即所有的Lifecycle bean都已显式接收到了stop信号,关闭的容器可以通过start()方法重启 |
ContextClosedEven36 | 当容器关闭时发布,即调用close方法,关闭意味着所有的单例bean都已被销毁。关闭的容器不能被重启 或refresh |
RequestHandledEvent | 这只在使用spring的DispatcherServlet时有效,当一个请求被处理完成时发布 |
内置事件类型的继承体系:
2.1.2 Spring内置事件
事件类需要继承ApplicationEvent抽象类,代码如下:
public class BigEvent extends ApplicationEvent {
private String name;
public BigEvent(Object source, String name) {
super(source);
this.name = name;
}
public String getName() {
return name;
}
}
这里为了简单测试,所以写的很简单。
事件类其实就是一种很简单的pojo,除了需要继承ApplicationEvent也没什么了,这个类有一个构造方法需要调用super()方法。
2.2 事件监听器(ApplicationListener)
事件监听器(ApplicationListener)对应于观察者模式中的观察者。作用为处理事件广播器发布的事件。
在Spring中监听器的实现主要有一下重要组件:
- ApplicationListener接口:事件监听者,观察者;
- ApplicationEvent抽象类:Spring 事件,记录事件源、事件内容、时间等数据;
- @EventListener注解:除了实现ApplicationListener接口注册监听器,也可以使用注解的方式;
- ApplicationEventPublisher接口:发布事件;
2.2.1 事件监听器-基于接口
Spring提供了继承于java.util.EventListener接口的应用监听器接口, ApplicationListener,此接口源码:
@FunctionalInterface
public interface ApplicationListener<E extends ApplicationEvent> extends EventListener {
/**
* Handle an application event.
* @param event the event to respond to
*/
void onApplicationEvent(E event);
}
使用接口实现监听器:
@FunctionalInterface
public interface ApplicationListener<E extends ApplicationEvent> extends EventListener {
/**
* Handle an application event.
* @param event the event to respond to
*/
void onApplicationEvent(E event);
}
事件监听器需要实现ApplicationListener接口,这是个泛型接口,泛型类类型就是事件类型,其次监听器是需要spring容器托管的bean,所以这里加了@component,只有一个方法,就是onApplicationEvent(),是事件发生后要触发的操作。
Spring还提供了对ApplicationListener接口的实现:SmartApplicationListener接口和GenericApplicationListener接口。
2.2.1.1 SmartApplicationListener
提供了监听器对泛型事件的支持,spring3.0 添加的。
源码:
public interface SmartApplicationListener extends ApplicationListener<ApplicationEvent>, Ordered {
boolean supportsEventType(Class<? extends ApplicationEvent> var1);
boolean supportsSourceType(Class<?> var1);
}
2.2.1.2 GenericApplicationListener
增强对泛型事件的支持(支持泛型方式不同与SmartApplicationListener),spring4.2 添加的。
源码:
public interface GenericApplicationListener extends ApplicationListener<ApplicationEvent>, Ordered {
boolean supportsEventType(ResolvableType var1);
boolean supportsSourceType(Class<?> var1);
}
2.2.2 事件监听器-基于注解
@Component
public class OrderEventListener {
@EventListener(OrderEvent.class)
public void onApplicationEvent(OrderEvent event) {
if(event.getName().equals("减库存")){
System.out.println("减库存.......");
}
}
}
注解是加到onApplicationEvent()方法上的(方法名可以自定义,这个方法就是监听器监听到事件发生后触发的执行逻辑)。
这里要注意,加注解的方法不能是private修饰的方法,否则会报错。
@EventListener的错误尝试:
道了@EventListener的原理,我们其实可以做一些猜测,如下:
methodA是正常的用法;
methodB方法的修饰符是private;
methodC则是监听的ContextRefreshedEvent,但下面方法的入参却是ContextClosedEvent;
后两者都有问题:
可以看到,编译器直接黄底提示了methodB的@EventListener注解,其实从前面我们已经猜到,因为最后我们的调用是由监听器ApplicationListenerMethodAdapter对象直接调用的方法ABC,所以方法必须可被其他对象调用,即public。
而后者会在执行广播响应事件时报参数非法异常也是意料之中。
2.3 事件发布者(ApplicationEventPublisher)
事件发布者,职责为发布事件。
spring的ApplicationContext 本来就实现了ApplicationEventPublisher接口,因此应用上下文本来就是
一个事件发布者,在AbstractApplicationContext中实现了事件发布的业务。
上下文对象调用publishEvent()方法,将事件对象传入即可将该事件发布。
applicationContext.publishEvent(new HelloEvent(this,"lgb"));
事件发布之后就会被已经注册到Spring容器中对应的事件监听器监听到,会触发监听器onApplicationEvent()操作。
2.4 事件广播器(ApplicationEventMulticaster)
Spring事件机制是观察者模式的一种实现,但是除了发布者和监听者者两个角色之外,还有一个EventMultiCaster的角色负责把事件转发给监听者。
事件发布器(ApplicationEventMulticaster)又叫事件发布器、事件多播器。对应于观察者模式中的被观察者/主题, 负责通知观察者。对外提供发布事件和增删事件监听器的接口,维护事件和事件监听器之间的映射关系,并在事件发生时负责通知相关监听器。
其工作流程如下:
EventMultiCaster注册着所有的Listener,在上面发布事件的代码中发布者调用applicationEventPublisher.publishEvent(msg); 是会将事件发送给了EventMultiCaster,然后EventMultiCaster根据事件类型决定转发给那个Listener。
讲完了Spring事件体系中的三个组件,我们就明白了他们之间的关系和运作流程。下面我们再详细讲一下如何使用它。
三、Spring监听器的使用
3.1 新建监听器
3.1.1 通过实现ApplicationListener接口创建监听器
import org.springframework.context.ApplicationListener;
import org.springframework.context.event.ContextRefreshedEvent;
import org.springframework.stereotype.Component;
@Component
public class MyContextRefreshedListener implements ApplicationListener<ContextRefreshedEvent> {
@Override
public void onApplicationEvent(ContextRefreshedEvent event) {
System.out.println("应用程序上下文已刷新");
// 在这里可以执行一些初始化操作
}
}
- 我们创建了一个名为MyContextRefreshedListener的监听器,它实现了ApplicationListener接口,并且传入的泛型是ContextRefreshedEvent,这意味着这个监听器监听的事件类型是ContextRefreshedEvent。MyContextRefreshedListener重写了 onApplicationEvent 方法。因为Spring会在容器refresh()时发布ContextRefreshedEvent事件,所以onApplicationEvent方法在应用程序上下文被刷新时触发。
- 使用 @Component 注解将该监听器声明为一个Spring管理的组件,这样Spring会自动将其纳入到应用程序上下文中,并在适当的时候触发监听。
3.1.2 使用@EventListener注解创建监听器
除了手动写个类外,我们也可以找个现成的类,该类不需要继承或实现任何其他类,只需要在它的某个方法上加上 @EventListener 注解就可以实现监听器的功能,如下:
@Component
public class MyListener {
@EventListener(ContextRefreshedEvent.class)
public void methodA(ContextRefreshedEvent event) {
System.out.println("应用程序上下文已刷新");
// 在这里可以执行一些初始化操作
}
}
- 在一个现有的类的某个方法上,加上@EventListener(ContextRefreshedEvent.class),Spring会在加载这个类时,为其创建一个监听器,这个监听器监听的事件类型是ContextRefreshedEvent,当此事件发生时,将触发执行该方法methodA。
- 使用 @Component 注解将该类声明为一个Spring管理的组件,这样Spring会自动将其纳入到应用程序上下文中,并在适当的时候触发监听。
- 我们可以在这个类中写上多个方法,每个方法通过注解监听着不同的事件类型,这样我们就仅需使用一个类,却构建了多个监听器。
上述两种方法的效果是一样的。那么最后,我们就完成了Spring中一个内置监听器的简单示例:当启动一个基于Spring的应用程序时,当应用程序上下文被刷新时,ContextRefreshedEvent事件将被触发,然后MyContextRefreshedListener监听器的onApplicationEvent方法将被调用。
3.2 内置的事件类型
我们在demo中使用了一个 ContextRefreshedEvent 的事件,这个事件是Spring内置的事件,除了该事件,Spring还内置了一些其他的事件类型,分别在以下情况下触发:
- ContextRefreshedEvent:
当应用程序上下文被刷新时触发。这个事件在ApplicationContext初始化或刷新时被发布,适用于执行初始化操作和启动后的后续处理。例如,初始化缓存、预加载数据等。
- ContextStartedEvent:
当应用程序上下文启动时触发。这个事件在调用ApplicationContext的start()方法时被发布,适用于在应用程序启动时执行特定的操作。例如,启动定时任务、启动异步消息处理等。
- ContextStoppedEvent:
当应用程序上下文停止时触发。这个事件在调用ApplicationContext的stop()方法时被发布,适用于在应用程序停止时执行清理操作。例如,停止定时任务、关闭数据库连接等。
- ContextClosedEvent:
当应用程序上下文关闭时触发。这个事件在调用ApplicationContext的close()方法时被发布,适用于在应用程序关闭前执行最后的清理工作。例如,释放资源、保存日志等。
- RequestHandledEvent:
在Web应用程序中,当一个HTTP请求处理完成后触发。这个事件在Spring的DispatcherServlet处理完请求后被发布,适用于记录请求日志、处理统计数据等。
- ApplicationEvent:
这是一个抽象的基类,可以用于定义自定义的应用程序事件。你可以创建自定义事件类,继承自ApplicationEvent,并定义适合你的应用场景的事件类型。
这些内置监听器是由Spring框架自动发布的。不需要程序员去发布。
3.3 自定义事件与监听器Demo
在学习完上面的内容后,我们现在可以手动写个Spring的事件,以及对应的监听器的demo了
3.3.1 构建两个自定义事件
Spring通过继承ApplicationEvent抽象类来创建自定义事件。下面我们来建立一个继承自ApplicationEvent的自定义事件类。
// 事件A
public class CustomEventA extends ApplicationEvent {
private String message;
public CustomEventA(Object source, String message) {
super(source);
this.message = message;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
}
// 事件B
public class CustomEventB extends ApplicationEvent {
private String message;
public CustomEventB(Object source, String message) {
super(source);
this.message = message;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
}
3.3.2 构建监听
我们选用@EventListener注解来实现监听器。
@Component
public class MyListener {
@EventListener(CustomEventA.class)
public void methodA(CustomEventA event) {
System.out.println("========我监听到事件A了:" + event.getMessage());
// 在这里可以执行一些其他操作
}
@EventListener(CustomEventB.class)
public void methodB(CustomEventB event) {
System.out.println("========我监听到事件B了:" + event.getMessage());
// 在这里可以执行一些其他操作
}
}
3.3.3 发布事件
@Component
// 这里将这个CustomEvent事件发布器也设置成一个事件监听器了,去监听ContextRefreshedEvent事件,当这个时间发生之后就会自动去发布CustomEvent事件
public class CustomEventPublisher implements ApplicationContextAware, ApplicationListener<ContextRefreshedEvent> {
private ApplicationContext applicationContext;
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
// 利用容器刷新好的消息作为触发,去发布两条自定义的事件
@Override
public void onApplicationEvent(ContextRefreshedEvent event) {
// 创建事件
CustomEventA eventA = new CustomEventA(applicationContext , "我是AAAA");
CustomEventB eventB = new CustomEventB(applicationContext , "我是BBBB");
// 发布事件
applicationContext.publishEvent(eventA);
applicationContext.publishEvent(eventB);
}
}
开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!
更多推荐
所有评论(0)