Spring Events——事件监听机制—原理篇

前言

上文我们说到,Spring 事件监听机制本质也是观察者模式应用,观察者与被观察者之间存在某种关联,当被观察者做出一些行为动作之后,会以事件的形式通知到观察者,而观察者根据事件类型,做出相关反应,如以下 UML 类图所示:

这样的好处在于主要与次要功能逻辑解耦,同时方便后期扩展,维护;不管是新增观察者或是被观察者,都非常容易扩展。这也符合面向对象所倡导的 开闭原则单一职责

原理

Spring 事件总览

Spring 事件体系包括三个模块:事件(event)事件监听器(listener)事件广播器(multicast)

其中,Spring 充分考虑扩展性、兼容性,定义了接口、抽象等规范,同时也定义了默认实现类,除了 Spring 内置事件使用,也极容易在应用层自定义事件扩展。

Spring 定义关键规范有:ApplicationEvent、ApplicationListener、ApplicationEventPublisher、ApplicationEventMulticaster,如下 UML 类图:

  1. ApplicationEvent: 可以理解为是上面模型中的 Subject,是 Spring 事件规范,也是事件信息载体,我们可以扩展自定义扩展该事件为应用层服务。
  2. ApplicationListener:可以理解为是上面模型中的 Observer,这是一个函数式接口,当监听到事件并要执行时,通过调用 onApplicationEvent 执行。
  3. ApplicationEventPublisher:函数式接口,定义了事件发布的规范,任何的事件发布器ApplicationEventPublisher 都是通过调用 publishEvent 来进行事件的发布。
  4. ApplicationEventMulticaster:事件广播器,管理监听器和发布事件,ApplicationContext 通过委托 ApplicationEventMulticaster 来发布事件。

接下来我们看看 Spring 事件的基本实现~

ApplicationEvent

事件类型抽象类定义:

java 复制代码
public abstract class ApplicationEvent extends EventObject {

    private static final long serialVersionUID = 7099057708183571937L;

    /** System time when the event happened. */
    private final long timestamp;

    public ApplicationEvent(Object source) {
        super(source);
        this.timestamp = System.currentTimeMillis();
    }


    public final long getTimestamp() {
        return this.timestamp;
    }

}

具体事件类型,可按需进行定义。

ApplicationListener

监听器,接口定义:

java 复制代码
@FunctionalInterface
public interface ApplicationListener<E extends ApplicationEvent> extends EventListener {

    /**
     * Handle an application event.
     * @param event the event to respond to
     */
    void onApplicationEvent(E event);

}

具体监听器可按需自定义。

ApplicationEventMulticaster

事件管理器、广播器,抽象实现类:AbstractApplicationEventMulticaster -> 默认实现类:SimpleApplicationEventMulticaster

接口定义:

java 复制代码
public interface ApplicationEventMulticaster {

    // ...

    /**
     * 新增 lisener
     */
    void addApplicationListener(ApplicationListener<?> listener);


    /**
     * 广播事件,推送给相关 listener 处理
     */
    void multicastEvent(ApplicationEvent event);

    // ...

}

默认实现类:

java 复制代码
public class SimpleApplicationEventMulticaster extends AbstractApplicationEventMulticaster {

   @Override
    public void multicastEvent(ApplicationEvent event) {
        multicastEvent(event, resolveDefaultEventType(event));
    }

    @Override
    public void multicastEvent(final ApplicationEvent event, @Nullable ResolvableType eventType) {
        ResolvableType type = (eventType != null ? eventType : resolveDefaultEventType(event));
        Executor executor = getTaskExecutor();
        for (ApplicationListener<?> listener : getApplicationListeners(event, type)) {
            if (executor != null) {
                executor.execute(() -> invokeListener(listener, event));
            }
            else {
                invokeListener(listener, event);
            }
        }
    }

}

相信熟悉观察者模式的你,应该很熟悉上述的实现逻辑,这种设计方式非常常见,Tomcat、Netty 等都广泛使用,逻辑清晰,因此,具体含义不再赘述。

Spring 内置事件

Spring 定义了一些内置事件,在启动时触发,主要有以下几个:

  1. ContextStartedEvent :上下文开始事件,当使用 ConfigurableApplicationContext 的 start() 方法启动 Spring 应用上下文时触发。这个事件是 ApplicationContextEvent 的一个子类。
  2. ContextRefreshedEvent:上下文更新事件,该事件会在 ApplicationContext 被初始化或者更新时发布。也可以在调用 ConfigurableApplicationContext 接口中的 refresh() 方法时被触发。
  3. ContextStoppedEvent:上下文停止事件,当容器调用ConfigurableApplicationContext的 Stop() 方法停止容器时触发该事件。
  4. ContextClosedEvent:上下文关闭事件,当 ApplicationContext 被关闭时触发该事件。容器被关闭时,其管理的所有单例Bean都被销毁。
  5. RequestHandleEvent:请求处理事件,在 Web 应用中,当一个 http 请求(request)结束触发该事件。

ContextStartedEvent

这个事件可以用来执行一些在应用程序上下文完全启动后需要立即执行的操作,比如启动某些后台任务或者触发某些只有在上下文启动后才能执行的操作。

java 复制代码
import org.springframework.context.ApplicationListener;
import org.springframework.context.event.ContextStartedEvent;
import org.springframework.stereotype.Component;

@Component
public class MyContextStartEventHandler implements ApplicationListener<ContextStartedEvent> {

    @Override
    public void onApplicationEvent(ContextStartedEvent event) {
        System.out.println("Context Started Event Received.");
        // do logic
    }
}

在 Spring 的配置文件或者启动类中,需要确保 Spring 应用上下文是可配置的,并且调用了 start() 方法:

java 复制代码
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class SpringApplication {
    public static void main(String[] args) {
        ConfigurableApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
        context.start();
        // ...
        context.stop();
    }
}

这样,当 Spring 上下文启动时,MyContextStartEventHandler 事件会被调用,可以在此做一些特殊的启动逻辑。

ContextRefreshedEvent

当 Spring 应用上下文被初始化或刷新时触发。这通常发生在配置改变或者初始化应用上下文时。这个事件也是 ApplicationContextEvent 的一个子类,其中:

  1. 这里的初始化是指:所有的 Bean 被成功装载,后处理 Bean 被检测并激活,所有 Singleton Bean 被预实例化,ApplicationContext 容器已就绪可用。
  2. 可以在 ConfigurableApplicationContext 接口中使用 refresh() 方法来发生。

具体触发时机:

  1. 初始化时:首次创建应用上下文,并完成了所有 bean 的加载、配置、初始化后,Spring 会发布一个 ContextRefreshedEvent。这标志着应用上下文已经完全准备好,所有的 bean 都已经就绪可以使用。
  2. 刷新时:如果在应用运行期间,应用上下文被显式地刷新(例如,通过调用ConfigurableApplicationContext 的 refresh() 方法),Spring 也会发布ContextRefreshedEvent。刷新操作可能会重新加载或重新配置 beans,这在开发过程中动态调整配置时非常有用。

监听这个事件可以让在 Spring 容器加载完所有的 bean 之后执行一些特定的操作,比如检查数据完整性、启动自动任务等。使用例子如:

java 复制代码
import org.springframework.context.ApplicationListener;
import org.springframework.context.event.ContextRefreshedEvent;
import org.springframework.stereotype.Component;

@Component
public class MyContextRefreshedEventHandler implements ApplicationListener<ContextRefreshedEvent> {

    @Override
    public void onApplicationEvent(ContextRefreshedEvent event) {
        System.out.println("Context Refreshed Event Received.");
        // do logic
    }
}

ContextStoppedEvent、ContextClosedEvent

ContextStoppedEvent 是 ApplicationContextEvent 的子类,Spring 容器停止时发布该事件,监听这个事件可以在Spring 容器停止时执行一些清理工作或其他必要的操作。

ContextClosedEvent 也是 ApplicationContextEvent 的子类,在 Spring 容器关闭上下文时发布该事件。

ContextStoppedEvent

  1. 触发时机:当应用上下文的 stop() 方法被调用时触发。这通常用于暂时停止应用上下文,可能是为了进行某些维护操作或者是因为应用不再需要继续运行,但未来可能会重新启动。
  2. 用途:停止应用上下文通常涉及停止所有实现了 Lifecycle 接口的组件,但不会销毁bean。这意味着可以通过调用 start() 方法来重新启动应用上下文。
  3. 场景:适用于那些需要暂停和可能稍后恢复的应用,例如,基于调度或外部事件动态启停的组件。

ContextClosedEvent

  1. 触发时机:当应用上下文的 close() 方法被调用时触发。这标志着应用上下文的生命周期结束,通常在应用即将关闭时发生。
  2. 用途:关闭应用上下文涉及释放所有资源,销毁所有的 singleton bean,并且清理所有的回调和事件监听器。一旦应用上下文被关闭,它就不能再被重新启动。
  3. 场景:适用于应用程序的正常关闭,确保所有资源都被适当地清理和释放

RequestHandledEvent

在Spring框架中,RequestHandledEvent 是一个用于 Web 应用的事件,在每个 HTTP 请求处理完成后被触发。

这个事件提供了关于请求处理的详细信息,如请求的持续时间、请求和响应的状态、客户端的信息等。

RequestHandledEvent 是 ApplicationEvent 的一个子类,因此它遵循Spring的事件发布机制。

RequestHandledEvent 可以用于监控和日志记录目的,例如:

  1. 记录所有请求的处理时间,帮助识别性能瓶颈。
  2. 监控请求的成功或失败状态,用于错误跟踪和分析。
  3. 收集统计数据,如请求频率、响应时间等,用于系统性能分析。

Spring 事件扩展

上文详细讲解了 Spring 内置事件使用,其实,扩展事件也非常容易,三步走:

  1. 自定义实现 ApplicationEvent 的事件类
  2. 自定义 ApplicationListener 的监听器
  3. 使用 ApplicationEventPublisher 发布事件

如此,就可以正常使用。

业务层自定义使用参考:Spring Events实战篇

小结

观察者机制是常见的系统解耦利器,在各类著名软件中都广泛应用,实现原理简单易懂。尤其在 Spring 中,简单的定义就可以使用事件机制,通俗易懂、易扩展,推荐你使用起来!

相关推荐
许野平16 分钟前
Rust: 利用 chrono 库实现日期和字符串互相转换
开发语言·后端·rust·字符串·转换·日期·chrono
duration~31 分钟前
Maven随笔
java·maven
zmgst34 分钟前
canal1.1.7使用canal-adapter进行mysql同步数据
java·数据库·mysql
跃ZHD43 分钟前
前后端分离,Jackson,Long精度丢失
java
blammmp1 小时前
Java:数据结构-枚举
java·开发语言·数据结构
暗黑起源喵1 小时前
设计模式-工厂设计模式
java·开发语言·设计模式
WaaTong1 小时前
Java反射
java·开发语言·反射
齐 飞2 小时前
MongoDB笔记01-概念与安装
前端·数据库·笔记·后端·mongodb
九圣残炎2 小时前
【从零开始的LeetCode-算法】1456. 定长子串中元音的最大数目
java·算法·leetcode
wclass-zhengge2 小时前
Netty篇(入门编程)
java·linux·服务器