目录

一、观察者模式

1.1 模型介绍

1.2 观察者模式Demo

1.2.1 观察者实体

1.2.2 主题实体

1.2.3 测试代码

二、Spring监听器的介绍

2.1 事件(ApplicationEvent)

2.1.1 Spring内置事件

2.1.2 Spring内置事件

2.2 事件监听器(ApplicationListener)

2.2.1 事件监听器-基于接口

2.2.1.1 SmartApplicationListener

2.2.1.2 GenericApplicationListener

2.2.2 事件监听器-基于注解

2.3 事件发布者(ApplicationEventPublisher)

2.4 事件广播器(ApplicationEventMulticaster)

三、Spring监听器的使用

3.1 新建监听器

3.1.1 通过实现ApplicationListener接口创建监听器

3.1.2 使用@EventListener注解创建监听器

3.2 内置的事件类型

3.3 自定义事件与监听器Demo

3.3.1 构建两个自定义事件

3.3.2 构建监听

3.3.3 发布事件


一、观察者模式

Spring的监听器用到了观察者模式,工厂模式(EventListenerFactory),适配器模式(ApplicationListenerMethodAdapter),而其中最重要的就是基于观察者模式,它能实现事件与事件监听者直接的解耦。所以在讲解监听器之前,我们先讲一下观察者模式。

1.1 模型介绍

观察者模式(Observer Pattern)是一种行为设计模式,它用于在对象之间建立一对多的依赖关系。在该模式中,当一个对象的状态发生变化时,它会自动通知其依赖对象(称为观察者),使它们能够自动更新。

观察者模式的工作原理如下:

  1. 主题对象维护一个观察者列表,并提供方法用于添加和删除观察者。
  2. 当主题的状态发生变化时(监听到某种事件发生),它会遍历观察者列表,并调用每个观察者的通知方法。
  3. 观察者接收到通知后,根据通知进行相应的更新操作。

以我的理解,就是一个主题会去监听一种事件,和这个事件相关的观察者都会被加到这个主题对象中。当主题对象监听到这个事件发生了,就会去通知自己维护的所有观察者,这些观察者就回去执行相应的逻辑。也就是一种主题对应一种事件,要观察这个事件的观察者都添加到这个主题中,由主题去监听这个事件是否发生,事件发生后主题回去通知所有的观察者,观察者们再去执行相应的逻辑

观察者模式角色:

  • 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事件体系包括三个组件:

  1. 事件广播器(事件多播器、事件发布器),可以理解为就是applicationContext。
  2. 事件ApplicationEvent。
  3. 事件监听器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);
    }
}

Logo

开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!

更多推荐