Spring Boot事件监听机制

2 阅读9分钟

一、事件机制概述

1.1 什么是事件驱动模型?

事件驱动模型是一种编程范式,它的核心思想是:当某件事情发生时,通知对此感兴趣的各方进行响应。这种模式在现实生活中随处可见:

  • 报纸订阅:有新报纸时,报社会通知所有订阅者
  • 交通信号:红灯亮起时,车辆和行人会做出相应反应
  • 手机通知:收到消息时,手机会发出提示音

在软件开发中,这种模式被称为观察者模式(Observer Pattern),也称为发布-订阅模式。

1.2 Spring事件机制的优势

Spring框架内置了完善的事件机制,具有以下优势:

优势说明
解耦事件发布者不需要知道监听者的存在,降低组件间耦合
扩展性新增监听器不影响现有代码,符合开闭原则
异步处理支持异步事件处理,提升系统响应速度
可维护性业务逻辑分离,代码结构更清晰
可测试性事件发布和监听可独立测试

1.3 Spring事件机制架构

┌─────────────────────────────────────────────────────────────────┐
│                    Spring 事件机制架构                           │
└─────────────────────────────────────────────────────────────────┘
 
    ┌──────────────┐         ┌──────────────────────┐
    │   Publisher  │ ──────> │ ApplicationEvent     │
    │  (发布者)    │         │    Multicaster       │
    └──────────────┘         │   (事件广播器)        │
                              └──────────┬───────────┘
                                         │
                    ┌────────────────────┼────────────────────┐
                    │                    │                    │
                    ▼                    ▼                    ▼
           ┌──────────────┐     ┌──────────────┐     ┌──────────────┐
           │  Listener A  │     │  Listener B  │     │  Listener C  │
           │  (监听者A)   │     │  (监听者B)   │     │  (监听者C)   │
           └──────────────┘     └──────────────┘     └──────────────┘

二、核心组件详解

2.1 ApplicationEvent:事件基类

所有Spring事件都继承自ApplicationEvent

public abstract class ApplicationEvent extends EventObject {
    
    private final long timestamp;
    
    public ApplicationEvent(Object source) {
        super(source);
        this.timestamp = System.currentTimeMillis();
    }
    
    public long getTimestamp() {
        return this.timestamp;
    }
}

关键属性

  • source:事件源对象,表示事件最初发生的对象
  • timestamp:事件发生的时间戳

2.2 ApplicationListener:事件监听接口

监听器需要实现ApplicationListener接口:

@FunctionalInterface
public interface ApplicationListener<E extends ApplicationEvent> 
        extends EventListener {
    
    void onApplicationEvent(E event);
}

泛型参数:指定监听的事件类型,实现类型安全的事件处理。

2.3 ApplicationEventPublisher:事件发布接口

发布事件的接口:

@FunctionalInterface
public interface ApplicationEventPublisher {
    
    default void publishEvent(ApplicationEvent event) {
        publishEvent((Object) event);
    }
    
    void publishEvent(Object event);
}

ApplicationContext继承了此接口,因此可以通过注入ApplicationContext来发布事件。

2.4 ApplicationEventMulticaster:事件广播器

事件广播器负责将事件分发给所有匹配的监听器:

public interface ApplicationEventMulticaster {
    
    void addApplicationListener(ApplicationListener<?> listener);
    
    void removeApplicationListener(ApplicationListener<?> listener);
    
    void addApplicationListenerBean(String listenerBeanName);
    
    void removeApplicationListenerBean(String listenerBeanName);
    
    void removeAllListeners();
    
    void multicastEvent(ApplicationEvent event);
    
    void multicastEvent(ApplicationEvent event, @Nullable ResolvableType eventType);
}

默认实现SimpleApplicationEventMulticaster

三、内置事件介绍

3.1 Spring框架内置事件

事件触发时机
ContextRefreshedEventApplicationContext初始化或刷新完成时
ContextStartedEventApplicationContext启动时(调用start()方法)
ContextStoppedEventApplicationContext停止时(调用stop()方法)
ContextClosedEventApplicationContext关闭时

3.2 Spring Boot内置事件

Spring Boot在启动过程中会发布一系列事件:

┌─────────────────────────────────────────────────────────────────┐
│                Spring Boot 启动事件时序                          │
└─────────────────────────────────────────────────────────────────┘
 
ApplicationStartingEvent
       │
       │  开始启动,listeners已加载
       ▼
ApplicationEnvironmentPreparedEvent
       │
       │  Environment准备完成
       ▼
ApplicationContextInitializedEvent
       │
       │  ApplicationContext已创建并初始化
       ▼
ApplicationPreparedEvent
       │
       │  Bean定义已加载,但Bean未实例化
       ▼
ApplicationStartedEvent
       │
       │  ApplicationContext已刷新,应用已启动
       ▼
ApplicationReadyEvent
       │
       │  应用已就绪,可以处理请求
       ▼
ApplicationFailedEvent (启动失败时)

3.3 监听内置事件示例

@Component
public class StartupEventListener implements ApplicationListener<ApplicationStartedEvent> {
    
    private static final Logger log = LoggerFactory.getLogger(StartupEventListener.class);
    
    @Override
    public void onApplicationEvent(ApplicationStartedEvent event) {
        log.info("应用启动完成!耗时: {}ms", 
            event.getApplicationContext().getStartupDate());
    }
}
 
@Component
public class ContextRefreshedEventListener {
    
    private static final Logger log = LoggerFactory.getLogger(ContextRefreshedEventListener.class);
    
    @EventListener
    public void onContextRefreshed(ContextRefreshedEvent event) {
        log.info("上下文刷新完成,Bean数量: {}", 
            event.getApplicationContext().getBeanDefinitionCount());
    }
}

四、自定义事件实战

4.1 创建自定义事件

public class UserRegisteredEvent extends ApplicationEvent {
    
    private final Long userId;
    private final String username;
    private final String email;
    private final LocalDateTime registeredAt;
    
    public UserRegisteredEvent(Object source, Long userId, String username, String email) {
        super(source);
        this.userId = userId;
        this.username = username;
        this.email = email;
        this.registeredAt = LocalDateTime.now();
    }
    
    public Long getUserId() { return userId; }
    public String getUsername() { return username; }
    public String getEmail() { return email; }
    public LocalDateTime getRegisteredAt() { return registeredAt; }
}

4.2 发布事件

@Service
public class UserService {
    
    private final ApplicationEventPublisher eventPublisher;
    
    public UserService(ApplicationEventPublisher eventPublisher) {
        this.eventPublisher = eventPublisher;
    }
    
    public void registerUser(UserRegistrationDto dto) {
        User user = new User();
        user.setUsername(dto.getUsername());
        user.setEmail(dto.getEmail());
        user.setPassword(encodePassword(dto.getPassword()));
        
        userRepository.save(user);
        
        eventPublisher.publishEvent(
            new UserRegisteredEvent(this, user.getId(), user.getUsername(), user.getEmail())
        );
    }
}

4.3 监听事件

方式一:实现ApplicationListener接口

@Component
public class WelcomeEmailListener implements ApplicationListener<UserRegisteredEvent> {
    
    private final EmailService emailService;
    
    public WelcomeEmailListener(EmailService emailService) {
        this.emailService = emailService;
    }
    
    @Override
    public void onApplicationEvent(UserRegisteredEvent event) {
        emailService.sendWelcomeEmail(event.getEmail(), event.getUsername());
    }
}

方式二:使用@EventListener注解

@Component
public class UserActivityListener {
    
    private static final Logger log = LoggerFactory.getLogger(UserActivityListener.class);
    
    @EventListener
    public void onUserRegistered(UserRegisteredEvent event) {
        log.info("新用户注册: userId={}, username={}", 
            event.getUserId(), event.getUsername());
    }
    
    @EventListener
    public void onUserRegisteredForPoints(UserRegisteredEvent event) {
        log.info("为新用户发放积分: userId={}", event.getUserId());
    }
}

五、事件监听方式对比

5.1 ApplicationListener vs @EventListener

特性ApplicationListener@EventListener
实现方式实现接口注解声明
事件类型泛型指定方法参数指定
条件过滤需手动判断支持@ConditionalOnProperty
返回值处理可返回新事件进行链式发布
异步支持需配合@Async直接支持@Async
SpEL条件不支持支持condition属性
代码简洁性较繁琐简洁

5.2 @EventListener高级用法

条件过滤

@EventListener(condition = "#event.username.startsWith('admin')")
public void onAdminUserRegistered(UserRegisteredEvent event) {
    log.info("管理员用户注册: {}", event.getUsername());
}

链式事件发布

@EventListener
public UserActivatedEvent onUserRegistered(UserRegisteredEvent event) {
    log.info("用户注册,准备激活");
    return new UserActivatedEvent(this, event.getUserId());
}

监听多种事件

@EventListener({UserRegisteredEvent.class, UserLoginEvent.class})
public void onUserActivity(ApplicationEvent event) {
    if (event instanceof UserRegisteredEvent) {
        log.info("用户注册事件");
    } else if (event instanceof UserLoginEvent) {
        log.info("用户登录事件");
    }
}

六、异步事件处理

6.1 为什么需要异步事件?

同步事件处理会阻塞主线程,影响系统响应速度。对于耗时操作(如发送邮件、生成报表),应该使用异步处理。

6.2 启用异步支持

@Configuration
@EnableAsync
public class AsyncConfig {
    
    @Bean
    public Executor taskExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(5);
        executor.setMaxPoolSize(10);
        executor.setQueueCapacity(100);
        executor.setThreadNamePrefix("async-event-");
        executor.initialize();
        return executor;
    }
}

6.3 异步事件监听器

方式一:@Async + @EventListener

@Component
public class AsyncUserListener {
    
    @Async
    @EventListener
    public void onUserRegistered(UserRegisteredEvent event) {
        try {
            Thread.sleep(3000);
            log.info("异步处理用户注册事件: {}", event.getUsername());
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
    }
}

方式二:@TransactionalEventListener(事务相关)

@Component
public class TransactionalUserListener {
    
    @TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)
    public void onUserRegisteredAfterCommit(UserRegisteredEvent event) {
        log.info("事务提交后处理用户注册事件: {}", event.getUsername());
    }
    
    @TransactionalEventListener(phase = TransactionPhase.BEFORE_COMMIT)
    public void onUserRegisteredBeforeCommit(UserRegisteredEvent event) {
        log.info("事务提交前处理用户注册事件: {}", event.getUsername());
    }
    
    @TransactionalEventListener(phase = TransactionPhase.AFTER_ROLLBACK)
    public void onUserRegisteredAfterRollback(UserRegisteredEvent event) {
        log.info("事务回滚后处理用户注册事件: {}", event.getUsername());
    }
}

6.4 事务阶段说明

阶段说明
BEFORE_COMMIT事务提交前执行,可参与当前事务
AFTER_COMMIT事务成功提交后执行(最常用)
AFTER_ROLLBACK事务回滚后执行
AFTER_COMPLETION事务完成后执行(提交或回滚)

七、事件传播与顺序

7.1 ApplicationEventMulticaster配置

@Configuration
public class EventMulticasterConfig {
    
    @Bean
    public ApplicationEventMulticaster applicationEventMulticaster() {
        SimpleApplicationEventMulticaster multicaster = 
            new SimpleApplicationEventMulticaster();
        multicaster.setTaskExecutor(taskExecutor());
        return multicaster;
    }
    
    @Bean
    public Executor taskExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(5);
        executor.setMaxPoolSize(10);
        executor.setThreadNamePrefix("event-multicaster-");
        executor.initialize();
        return executor;
    }
}

7.2 监听器执行顺序

使用@Order注解控制监听器执行顺序:

@Component
@Order(1)
public class FirstUserListener {
    
    @EventListener
    public void onUserRegistered(UserRegisteredEvent event) {
        log.info("第一个执行: {}", event.getUsername());
    }
}
 
@Component
@Order(2)
public class SecondUserListener {
    
    @EventListener
    public void onUserRegistered(UserRegisteredEvent event) {
        log.info("第二个执行: {}", event.getUsername());
    }
}
 
@Component
@Order(Ordered.LOWEST_PRECEDENCE)
public class LastUserListener {
    
    @EventListener
    public void onUserRegistered(UserRegisteredEvent event) {
        log.info("最后执行: {}", event.getUsername());
    }
}

Order值规则

  • 数值越小,优先级越高
  • 默认值为Ordered.LOWEST_PRECEDENCE(最低优先级)

7.3 实现SmartApplicationListener

对于更复杂的顺序控制:

@Component
public class SmartUserListener implements SmartApplicationListener {
    
    @Override
    public boolean supportsEventType(Class<? extends ApplicationEvent> eventType) {
        return UserRegisteredEvent.class.isAssignableFrom(eventType);
    }
    
    @Override
    public boolean supportsSourceType(Class<?> sourceType) {
        return UserService.class.isAssignableFrom(sourceType);
    }
    
    @Override
    public void onApplicationEvent(ApplicationEvent event) {
        UserRegisteredEvent userEvent = (UserRegisteredEvent) event;
        log.info("Smart监听器处理: {}", userEvent.getUsername());
    }
    
    @Override
    public int getOrder() {
        return 10;
    }
}

八、实战案例

8.1 用户注册完整流程

public class UserRegisteredEvent extends ApplicationEvent {
    private final Long userId;
    private final String username;
    private final String email;
    
    public UserRegisteredEvent(Object source, Long userId, String username, String email) {
        super(source);
        this.userId = userId;
        this.username = username;
        this.email = email;
    }
    
    // getters...
}
 
@Service
public class UserService {
    
    private final UserRepository userRepository;
    private final ApplicationEventPublisher eventPublisher;
    
    @Transactional
    public User register(UserRegistrationDto dto) {
        User user = new User();
        user.setUsername(dto.getUsername());
        user.setEmail(dto.getEmail());
        user.setPassword(encodePassword(dto.getPassword()));
        
        user = userRepository.save(user);
        
        eventPublisher.publishEvent(
            new UserRegisteredEvent(this, user.getId(), user.getUsername(), user.getEmail())
        );
        
        return user;
    }
}
 
@Component
@Order(1)
public class UserRegistrationLogger {
    
    private static final Logger log = LoggerFactory.getLogger(UserRegistrationLogger.class);
    
    @EventListener
    public void onUserRegistered(UserRegisteredEvent event) {
        log.info("用户注册成功: userId={}, username={}, email={}", 
            event.getUserId(), event.getUsername(), event.getEmail());
    }
}
 
@Component
@Order(2)
public class WelcomeEmailSender {
    
    private final EmailService emailService;
    
    @Async
    @TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)
    public void onUserRegistered(UserRegisteredEvent event) {
        emailService.sendWelcomeEmail(event.getEmail(), event.getUsername());
    }
}
 
@Component
@Order(3)
public class UserPointsInitializer {
    
    private final PointsService pointsService;
    
    @Async
    @TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)
    public void onUserRegistered(UserRegisteredEvent event) {
        pointsService.initPoints(event.getUserId(), 100);
    }
}
 
@Component
@Order(4)
public class UserStatisticsUpdater {
    
    private final StatisticsService statisticsService;
    
    @EventListener
    public void onUserRegistered(UserRegisteredEvent event) {
        statisticsService.incrementUserCount();
    }
}

8.2 订单创建事件处理

public class OrderCreatedEvent extends ApplicationEvent {
    private final Long orderId;
    private final Long userId;
    private final BigDecimal totalAmount;
    private final List<OrderItem> items;
    
    public OrderCreatedEvent(Object source, Long orderId, Long userId, 
            BigDecimal totalAmount, List<OrderItem> items) {
        super(source);
        this.orderId = orderId;
        this.userId = userId;
        this.totalAmount = totalAmount;
        this.items = items;
    }
    
    // getters...
}
 
@Service
public class OrderService {
    
    private final OrderRepository orderRepository;
    private final ApplicationEventPublisher eventPublisher;
    
    @Transactional
    public Order createOrder(OrderDto dto) {
        Order order = buildOrder(dto);
        order = orderRepository.save(order);
        
        eventPublisher.publishEvent(new OrderCreatedEvent(
            this, order.getId(), order.getUserId(), 
            order.getTotalAmount(), order.getItems()
        ));
        
        return order;
    }
}
 
@Component
public class OrderEventListener {
    
    @Async
    @TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)
    public void onOrderCreated(OrderCreatedEvent event) {
        inventoryService.decreaseStock(event.getItems());
    }
    
    @Async
    @TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)
    public void sendOrderNotification(OrderCreatedEvent event) {
        notificationService.sendOrderConfirmation(event.getUserId(), event.getOrderId());
    }
    
    @Async
    @TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)
    public void updateUserPoints(OrderCreatedEvent event) {
        pointsService.addPoints(event.getUserId(), 
            event.getTotalAmount().multiply(new BigDecimal("10")).intValue());
    }
}

九、最佳实践与注意事项

9.1 事件命名规范

// ✅ 好的命名:动词+名词+Event
public class UserRegisteredEvent extends ApplicationEvent { }
public class OrderCreatedEvent extends ApplicationEvent { }
public class PaymentCompletedEvent extends ApplicationEvent { }
 
// ❌ 不好的命名
public class UserEvent extends ApplicationEvent { }
public class OrderInfoEvent extends ApplicationEvent { }

9.2 事件设计原则

原则说明
单一职责每个事件只表示一个业务动作
不可变性事件创建后不应被修改
必要信息包含监听器需要的所有信息
避免大对象不要在事件中传递大对象或敏感信息

9.3 常见陷阱

陷阱一:在事务中发布事件但监听器执行失败

// ❌ 问题:如果监听器抛异常,整个事务回滚
@Transactional
public void registerUser(UserDto dto) {
    User user = userRepository.save(dto);
    eventPublisher.publishEvent(new UserRegisteredEvent(this, user));
}
 
// ✅ 解决:使用@TransactionalEventListener
@Async
@TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)
public void onUserRegistered(UserRegisteredEvent event) {
    // 事务提交后执行,不影响主事务
}

陷阱二:循环事件发布

// ❌ 问题:A事件触发B监听器,B监听器发布A事件,形成循环
@EventListener
public void onEventA(EventA event) {
    eventPublisher.publishEvent(new EventB(this));  // 触发EventB
}
 
@EventListener
public void onEventB(EventB event) {
    eventPublisher.publishEvent(new EventA(this));  // 又触发EventA,死循环!
}

陷阱三:异步事件中的异常被吞掉

// ❌ 问题:异步事件中的异常不会传播到调用方
@Async
@EventListener
public void onUserRegistered(UserRegisteredEvent event) {
    throw new RuntimeException("处理失败");  // 异常被吞掉
}
 
// ✅ 解决:配置异步异常处理器
@Component
public class AsyncExceptionHandler implements AsyncUncaughtExceptionHandler {
    @Override
    public void handleUncaughtException(Throwable ex, Method method, Object... params) {
        log.error("异步事件处理异常: method={}, params={}", 
            method.getName(), Arrays.toString(params), ex);
    }
}

9.4 性能优化建议

@Configuration
@EnableAsync
public class AsyncConfig implements AsyncConfigurer {
    
    @Override
    public Executor getAsyncExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(10);
        executor.setMaxPoolSize(50);
        executor.setQueueCapacity(200);
        executor.setKeepAliveSeconds(60);
        executor.setThreadNamePrefix("event-async-");
        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
        executor.initialize();
        return executor;
    }
    
    @Override
    public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
        return new AsyncExceptionHandler();
    }
}

9.5 事件监控

@Component
public class EventMonitorListener implements ApplicationListener<ApplicationEvent> {
    
    private final MeterRegistry meterRegistry;
    
    @Override
    public void onApplicationEvent(ApplicationEvent event) {
        meterRegistry.counter("events.published", 
            "type", event.getClass().getSimpleName())
            .increment();
    }
}

十、总结

10.1 核心组件回顾

组件职责
ApplicationEvent事件载体,封装事件数据
ApplicationListener事件监听接口,处理事件
@EventListener注解式监听,更简洁
ApplicationEventPublisher事件发布接口
ApplicationEventMulticaster事件广播器,分发事件

10.2 监听器选择指南

┌─────────────────────────────────────────────────────────────────┐
│                    事件监听器选择决策树                          │
└─────────────────────────────────────────────────────────────────┘
 
需要什么特性?
       │
       ├── 简单监听? ────────────> @EventListener
       │
       ├── 需要事务感知? ────────> @TransactionalEventListener
       │
       ├── 需要异步处理? ────────> @Async + @EventListener
       │
       ├── 需要条件过滤? ────────> @EventListener(condition = "...")
       │
       ├── 需要链式发布? ────────> @EventListener 返回新事件
       │
       └── 需要精细控制? ────────> 实现SmartApplicationListener

10.3 最佳实践总结

  1. 事件设计:事件类应不可变,包含必要信息
  2. 事务边界:使用@TransactionalEventListener确保事务正确性
  3. 异步处理:耗时操作使用@Async异步处理
  4. 异常处理:配置AsyncUncaughtExceptionHandler处理异步异常
  5. 执行顺序:使用@Order控制监听器执行顺序
  6. 避免循环:注意事件链式发布可能导致循环
  7. 监控日志:添加事件监控和日志便于排查问题

Spring Boot事件机制是实现解耦和扩展的重要手段,掌握它能帮助我们构建更加灵活、可维护的应用程序。


参考资料:

Vue模板语法与指令详解 | v-if、v-for、v-model、v-bind完整教程