Spring 的事件监听机制是什么?你知道吗?

开发 前端
​在复杂的业务系统中,模块间的过度耦合往往会导致代码维护困难、扩展性受限。Spring 事件监听机制基于观察者模式,提供了一种优雅的解耦方案,使得组件间通过事件驱动实现松耦合通信。

在复杂的业务系统中,模块间的过度耦合往往会导致代码维护困难、扩展性受限。Spring 事件监听机制基于观察者模式,提供了一种优雅的解耦方案,使得组件间通过事件驱动实现松耦合通信。这种机制不仅被 Spring 框架内部使用(如容器生命周期事件),对外其也提供了灵活的扩展能力,我们可以基于这些扩展点轻松构建个性化的事件监听功能。

设计思想

  • 分层解耦设计:事件发布者与监听者通过事件对象进行间接通信,避免了直接方法调用带来的强耦合。设计契合了"开闭原则"——新增监听器无需修改发布者代码。
  • 精准的事件路由机制:通过泛型约束(ApplicationListener<E>)和事件类型匹配算法,确保事件只会被感兴趣的监听器处理,避免无效的事件传播。
  • 可扩展的广播策略:ApplicationEventMulticaster 接口抽象了事件分发逻辑,支持同步/异步分发、异常处理策略等扩展点,为复杂场景提供灵活支持。

核心概念及组件

  • 事件(ApplicationEvent):这是 Spring 中所有应用事件的基类,它是一个抽象类,定义了事件的基本属性,如事件源(source)、事件发生的时间(timestamp)等。我们可以根据自己的需求继承该类来创建自定义事件。
// 承载业务数据的载体,所有 Spring 应用事件的基类,必须由具体事件类继承
publicabstractclass ApplicationEvent extends EventObject {

    privatestaticfinallong serialVersionUID = 7099057708183571937L;

    // 记录事件发生的时间戳(毫秒级精度)
    privatefinallong timestamp;

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

    public ApplicationEvent(Object source, Clock clock) {
        super(source);
        this.timestamp = clock.millis();
    }

    public final long getTimestamp() {
        returnthis.timestamp;
    }
}

Spring 的内置事件

Spring 框架内部定义了一些常用的内置事件,这些事件在容器的生命周期中扮演着重要角色。以下是部分内置事件及其触发时机和典型应用场景:

事件类型

触发时机

典型应用场景

ContextRefreshedEvent

容器初始化完成或刷新时

缓存预热、配置加载

ContextStartedEvent

调用start()启动容器时

启动后台任务线程

ContextStoppedEvent

调用stop()停止容器时

释放资源、暂停定时任务

ContextClosedEvent

调用close()关闭容器时

数据库连接池销毁

RequestHandledEvent

HTTP 请求处理完毕时(需 Spring MVC 环境)

请求耗时统计、日志记录

  • 事件发布器(ApplicationEventPublisher):事件发布接口,它提供了发布事件的方法。在 Spring 容器中,通常我们会在需要发布事件的 Bean 中通过依赖注入的方式注入 ApplicationEventPublisher 接口的实现,并调用其 publishEvent 方法来发布事件。
/**
 * 封装事件发布功能的核心接口,同时也是 ApplicationContext 的父接口
 * @FunctionalInterface 标记函数式接口,支持 lambda 表达式实现
 */
@FunctionalInterface
public interface ApplicationEventPublisher {

    /**
     * 发布 ApplicationEvent 类型的事件(如 ContextRefreshedEvent)
     * 底层会将事件对象强转为 Object 类型,然后调用重载方法统一处理
     */
    default void publishEvent(ApplicationEvent event) {
        publishEvent((Object) event);
    }

    /**
     * 通用事件发布方法,支持任意对象类型的事件
     * 若参数非 ApplicationEvent 类型,Spring 会将其封装为 PayloadApplicationEvent
     * 事件最终通过 ApplicationEventMulticaster 广播给匹配的监听器
     * 官方设计提示:耗时操作建议监听器自行实现异步处理
     */
    void publishEvent(Object event);
}
  • 事件监听器(ApplicationListener):事件监听接口,定义了监听事件的方法。应用程序中可实现该接口,监听感兴趣的事件,并在 onApplicationEvent() 方法中编写处理事件的逻辑。当事件发布者发布事件时,Spring 容器会自动将事件通知到所有已注册的、对该事件感兴趣的事件监听器上。
/**
 * 订阅并处理特定事件,基于观察者模式,继承标准 EventListener 接口
 */
@FunctionalInterface
public interface ApplicationListener<E extends ApplicationEvent> extends EventListener {

    // 当匹配类型的事件发布时触发
    void onApplicationEvent(E event);
}
  • 事件广播器(ApplicationEventMulticaster):广播器接口,它负责管理事件监听器的注册与注销,并将事件分发给所有的监听器。在 Spring 容器中,常见如 SimpleApplicationEventMulticaster,它提供了基本的事件广播功能。
/**
 * 事件广播器接口,用于管理监听器注册表,实现事件路由。
 * 通常由 Spring 上下文内部实现,作为事件发布委托组件。
 */
publicinterface ApplicationEventMulticaster {

    /**
     * 添加监听器实例(编程式注册)
     */
    void addApplicationListener(ApplicationListener<?> listener);

    /**
     * 通过 Bean 名称添加监听器(适用于容器管理的 Bean)
     */
    void addApplicationListenerBean(String listenerBeanName);

    /**
     * 移除指定监听器实例
     */
    void removeApplicationListener(ApplicationListener<?> listener);

    /**
     * 通过Bean名称移除监听器
     */
    void removeApplicationListenerBean(String listenerBeanName);

    /**
     * 清空所有注册的监听器实例和Bean名称
     */
    void removeAllListeners();

    /**
     * 广播事件(自动推断事件类型)
     */
    void multicastEvent(ApplicationEvent event);

    /**
     * 广播事件(显式指定事件类型,支持泛型解析)
     */
    void multicastEvent(ApplicationEvent event, @Nullable ResolvableType eventType);
}

案例解析

为了更直观地了解 Spring 事件监听机制的使用,下面通过一个用户注册事件的案例进行说明。

  • 定义事件模型:
@Getter
public class UserRegisterEvent extends ApplicationEvent {
    private final String username;
    public UserRegisterEvent(Object source, String username) {
        super(source);
        this.username = username;
    }
}
  • 定义事件监听器:
// 方式1:实现 ApplicationListener 接口
@Component
publicclass EmailListener implements ApplicationListener<UserRegisterEvent> {
    @Override
    public void onApplicationEvent(UserRegisterEvent event) {
        sendEmail(event.getUsername());
    }
}

// 方式2:使用 @EventListener 注解
@Component
publicclass LogsListener {
    @EventListener
    public void handleEvent(UserRegisterEvent event) {
        addLogs(event.getUsername());
    }
}
  • 注册监听器:注册监听器的方式有很多种

a.一种如上述代码所示,通过 @Component 自动进行扫描

b.以配置 Bean 的方式,SpringBoot 自动装配等

c.我们还可以通过手动注册,applicationContext.addApplicationListener(new EmailListener());

  • 发布事件:
@Service
public class UserService {
    @Resource
    private ApplicationContext context;

    public void register(String username) {
        // 业务逻辑 ......
        context.publishEvent(new UserRegisterEvent(this, username));
    }
}

源码分析

看完上述案例后,我们带着以下几个问题来分析下源码,看看这些个组件之间到底是如何协作的。

  • ApplicationConext 接口是如何发布事件的
  • Spring 是何时、如何识别并加载事件监听器的
  • Spring 加载的监听器是如何找到对应事件的

在上述案例中,发布事件时,我们使用的是 context.publishEvent(new UserRegisterEvent(this, username)); 来进行事件发布的。ApplicationConext 接口具有事件发布能力是因为其继承了事件发布器接口 ApplicationEventPublisher:

public interface ApplicationContext extends EnvironmentCapable, ListableBeanFactory, HierarchicalBeanFactory,
  MessageSource, ApplicationEventPublisher, ResourcePatternResolver {}

但在其方法内部真正发布事件的其实是事件广播器:

@Override
public void publishEvent(ApplicationEvent event) {
    publishEvent(event, null);
}

protected void publishEvent(Object event, @Nullable ResolvableType eventType) {
    ......

    if (this.earlyApplicationEvents != null) {
        this.earlyApplicationEvents.add(applicationEvent);
    } else {
        // 获取事件广播器并将事件路由至对应的监听器上
        getApplicationEventMulticaster().multicastEvent(applicationEvent, eventType);
    }

    ......
}

那么,事件广播器又是何时被初始化的呢?它定义在 AbstractApplicationContext 中,是在容器刷新时被初始化出来的,也就是在执行 refresh() 方法时,方法逻辑复杂,这里只贴出关键部分:

public void refresh() throws BeansException, IllegalStateException {
    synchronized (this.startupShutdownMonitor) {
        ......
        // 预备刷新 beanFactory
        prepareBeanFactory(beanFactory);

        try {
            // 注册 bean 后置处理器
            registerBeanPostProcessors(beanFactory);

            // Initialize event multicaster for this context.
            // 初始化事件广播器
            initApplicationEventMulticaster();

            // 注册事件监听器
            registerListeners();

            // 实例化所有非懒加载的单例 bean
            finishBeanFactoryInitialization(beanFactory);

            // 发布相关事件(ContextRefreshedEvent)
            finishRefresh();
        } catch (BeansException ex) {
            ......
        } finally {
            ......
        }
    }
}

我们可以看到,在 refresh() 方法中,通过 initApplicationEventMulticaster() 方法初始化了初始化事件广播器,具体逻辑如下:

protected void initApplicationEventMulticaster() {
    ConfigurableListableBeanFactory beanFactory = getBeanFactory();
    // 判断容器中是否有名为 applicationEventMulticaster 的广播器,有的话使用这个进行初始化
    if (beanFactory.containsLocalBean(APPLICATION_EVENT_MULTICASTER_BEAN_NAME)) {
        this.applicationEventMulticaster =
                beanFactory.getBean(APPLICATION_EVENT_MULTICASTER_BEAN_NAME, ApplicationEventMulticaster.class);
        }
    }
    else {
        // 没有的话,初始化一个 SimpleApplicationEventMulticaster,并注册到容器中
        this.applicationEventMulticaster = new SimpleApplicationEventMulticaster(beanFactory);
        beanFactory.registerSingleton(APPLICATION_EVENT_MULTICASTER_BEAN_NAME, this.applicationEventMulticaster);
    }
}

初始化事件广播器的逻辑比较简单,首先是从容器中获取 beanName 为 applicationEventMulticaster 的广播器,如果用户自定义并向容器中注册了该名称的事件广播器,那么就会执行该流程。如果没有,则默认创建一个 SimpleApplicationEventMulticaster。至此,完成了事件发布器的初始化。

上文分析核心组件时,我们知道事件广播器的职责是管理事件监听器的注册与注销,并进行事件路由。那么,事件监听器的注册时机是什么时候呢?答案还是在 refresh() 方法中,通过调用 registerListeners() 方法,完成了监听器的注册流程。代码如下:

protected void registerListeners() {
    /**
     * 这里 getApplicationListeners() 是获取的成员变量 applicationListeners 的值
     * 是指通过 context.addApplicationListener(new EmailListener())编程式手动添加的
     * 或者是通过 spring.factories 自动装配进来的 Listener
     */
    for (ApplicationListener<?> listener : getApplicationListeners()) {
        getApplicationEventMulticaster().addApplicationListener(listener);
    }

    /**
     * 获取容器中所有实现 ApplicationListener 接口的 Bean 的名称
     * 这里不对 bean 进行初始化,交由 bean 的后置处理器在初始化后实际注册
     * 此处数组中的 beanName 不包含上述 getApplicationListeners() 方法中获取到的 ApplicationListener
     */
    String[] listenerBeanNames = getBeanNamesForType(ApplicationListener.class, true, false);
    for (String listenerBeanName : listenerBeanNames) {
        getApplicationEventMulticaster().addApplicationListenerBean(listenerBeanName);
    }

    // 处理早期应用事件(在事件广播器初始化前缓存的事件)
    Set<ApplicationEvent> earlyEventsToProcess = this.earlyApplicationEvents;
    this.earlyApplicationEvents = null;
    // 如果存在早期事件,通过多播器广播这些事件
    if (!CollectionUtils.isEmpty(earlyEventsToProcess)) {
        for (ApplicationEvent earlyEvent : earlyEventsToProcess) {
            getApplicationEventMulticaster().multicastEvent(earlyEvent);
        }
    }
}

在上述 registerListeners() 阶段,应用程序中通过 @Component 等注解定义的监听器 Bean 可能尚未实例化,因此只能注册其名称。在后续 Bean 初始化阶段,Bean 的后置处理器 ApplicationListenerDetector 确保这些监听器被实际注册到上下文中。ApplicationListenerDetector 后置处理器是在 refresh() 的 prepareBeanFactory() 方法中被添加到 Bean 工厂中的。

public Object postProcessAfterInitialization(Object bean, String beanName) {
    if (bean instanceof ApplicationListener) {
        // registerListeners() 阶段可能会检测不到某些监听器(例如延迟初始化的 Bean 或动态代理生成的 Bean)
        // 从缓存中获取该 Bean 是否为单例的标记
        Boolean flag = this.singletonNames.get(beanName);
        if (Boolean.TRUE.equals(flag)) {
            // 将 Bean 注册为应用监听器
            this.applicationContext.addApplicationListener((ApplicationListener<?>) bean);
        }
        elseif (Boolean.FALSE.equals(flag)) {
            // 如果是非单例 Bean,移除该 Bean 的标记,避免后续重复处理
            this.singletonNames.remove(beanName);
        }
    }
    return bean;
}

到这里,监听器的注册也完成了。事件监听器可以来自自动装配、来自编程式手动添加,也可以来自扫描 ApplicationListener 接口的实现。事件广播器和事件监听器都有了,接下来就是事件是如何路由到对应的监听器的呢?

事件的路由还是回归到事件的发布,事件发布时,最终是通过事件广播器路由事件的,也就是如下这个代码:

protected void publishEvent(Object event, @Nullable ResolvableType eventType) {
    ......

    if (this.earlyApplicationEvents != null) {
        this.earlyApplicationEvents.add(applicationEvent);
    } else {
        // 获取事件广播器并将事件路由至对应的监听器上
        getApplicationEventMulticaster().multicastEvent(applicationEvent, eventType);
    }

    ......
}

广播器通过调用 multicastEvent() 方法,对事件进行广播,最终找到事件对应的监听器,触发监听逻辑:

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);
        }
    }
}

protected void invokeListener(ApplicationListener<?> listener, ApplicationEvent event) {
    ErrorHandler errorHandler = getErrorHandler();
    if (errorHandler != null) {
        try {
            // 执行监听器
            doInvokeListener(listener, event);
        }
        catch (Throwable err) {
            errorHandler.handleError(err);
        }
    }
    else {
        doInvokeListener(listener, event);
    }
}

private void doInvokeListener(ApplicationListener listener, ApplicationEvent event) {
    try {
        // 调用监听器接口方法
        listener.onApplicationEvent(event);
    }
    catch (ClassCastException ex) {
        ......
    }
}

到这里,我们定义监听器时重写的 onApplicationEvent() 方法被调用了,事件的发布和处理流程就分析完了。

Spring 事件机制完美诠释了"不要打电话给我们,我们会通知你(Don’t call us, we’ll call you)的“好莱坞原则”。这种通过职责分离和抽象分层的设计使得系统模块既能保持独立演进,又能通过事件总线实现高效协作。

最后,我们再来看几个面试经常问到的问题:

同一个事件可以有多个监听器吗?

可以。Spring 事件监听机制天然支持多监听器对同一事件的订阅与响应。

  • 机制原理:当一个事件被发布时,Spring 会通过 ApplicationEventMulticaster 遍历所有注册的监听器,筛选出匹配事件类型的监听器并触发其逻辑。
  • 应用场景:例如用户注册事件可同时触发发送邮件、记录日志、初始化积分等操作,每个功能由独立监听器处理。
  • 顺序控制:可通过 @Order 注解或实现 Ordered 接口指定监听器的执行顺序,数值越小优先级越高。

事件监听器一定要实现 ApplicationListener<E> 接口吗?

不一定。Spring 提供了两种定义监听器的方式:

  • 接口实现:通过实现 ApplicationListener<E> 接口并指定泛型事件类型(如 UserRegisterEvent),适用于需要强类型约束的场景。
  • 注解驱动:使用 @EventListener 注解标注方法(如 @EventListener(UserRegisterEvent.class)),支持更灵活的事件类型匹配(包括非 ApplicationEvent 子类)。
  • 优势对比:注解方式允许一个类内定义多个监听方法,且支持条件过滤(condition 属性),代码更加简洁。
@Component
publicclass UserEventListener {

    // 默认处理:所有注册事件均触发
    @EventListener
    public void handleDefault(UserRegisterEvent event) {
        System.out.println("记录日志: 用户 " + event.getUsername() + " 注册成功");
    }

    // 指定渠道处理:仅当 channel=web 时触发
    @EventListener(condition = "#event.channel == 'web'")
    public void handleWebChannel(UserRegisterEvent event) {
        System.out.println("推送站内消息至用户: " + event.getUsername());
    }
}

事件监听操作是同步的还是异步的?

默认同步执行,但可通过配置实现异步。

  • 同步模式:事件发布后,所有监听器按顺序串行执行。
  • 异步模式:

a.全局异步:通过 SimpleApplicationEventMulticaster 的 setTaskExecutor() 方法设置一个 TaskExecutor。

b.局部异步:在监听方法上添加 @Async 注解,并启用 @EnableAsync 配置。

c.注意事项:异步模式下需处理线程上下文传递(如事务传播、ThreadLocal 变量)及异常回滚逻辑。

分布式系统中 Spring 事件监听机制的限制及替代方案?

  • 单机局限:Spring 事件仅在单个 JVM 内传播,无法跨服务边界。
  • 可靠性问题:无内置重试、持久化机制,若监听器执行失败可能导致事件丢失。
  • 替代方案:分布式系统中需集成消息队列,将事件转换为消息,通过 RabbitMQ、Kafka 等中间件实现跨服务事件分发。
责任编辑:武晓燕 来源: Java驿站
相关推荐

2024-04-30 09:02:48

2025-02-18 08:11:17

2024-10-10 16:53:53

守护线程编程

2024-08-20 08:29:55

2024-09-02 00:30:41

Go语言场景

2021-04-11 11:20:26

数字人民币数字货币区块链

2025-02-27 08:09:52

2023-12-20 08:23:53

NIO组件非阻塞

2015-08-24 09:23:25

2024-04-22 08:02:34

kafka消息队列高可用

2022-11-28 00:04:17

2024-01-15 12:16:37

2025-03-05 00:00:00

RTKRedux开发

2024-04-07 00:00:03

2024-07-30 08:22:47

API前端网关

2024-11-08 09:48:38

异步编程I/O密集

2025-01-14 11:07:30

JenkinsWAR目录

2023-12-12 08:41:01

2024-02-19 07:44:52

虚拟机Java平台

2020-11-17 08:30:06

LinuxSwapping 设计
点赞
收藏

51CTO技术栈公众号