SpringBoot三大组件之监听器(Listener)
一、概述
Listener是servlet规范中定义的一种特殊类。用于监听servletContext、HttpSession和servletRequest等域对象的创建和销毁事件。监听域对象的属性发生修改的事件。用于在事件发生前、发生后做一些必要的处理。一般是获取在线人数等业务需求。
如做完某一件事情以后,需要广播一些消息或者通知,告诉其他的模块进行一些事件处理,一般来说,可以一个一个发送请求去通知,但是有一种更好的方式,那就是事件监听,事件监听也是设计模式中发布-订阅模式、观察者模式的一种实现。
观察者模式:
简单的来讲就是你在做事情的时候身边有人在盯着你,当你做的某一件事情是旁边观察的人感兴趣的事情的时候,他会根据这个事情做一些其他的事,但是盯着你看的人必须要到你这里来登记,否则你无法通知到他(或者说他没有资格来盯着你做事情)。
二、定义监听器
Spring提供了两种实现方式:
- ApplicationListener接口
 - @EventListener注解
 - SmartApplicationListener接口:继承于
ApplicationListener接口,支持事件类型过滤和顺序控制 
要想顺利的创建监听器,并起作用,这个过程中需要这样几个角色:
- 事件(
event)可以封装和传递监听器中要处理的参数,如对象或字符串,并作为监听器中监听的目标。 - 监听器(
listener)具体根据事件发生的业务处理模块,这里可以接收处理事件中封装的对象或字符串。 - 事件发布者(
publisher)事件发生的触发者。 
三、ApplicationListener接口的实现形式
ApplicationListener接口的定义如下:
public interface ApplicationListener<E extends ApplicationEvent> extends EventListener {
    /**
     * Handle an application event.
     * @param event the event to respond to
     */
    void onApplicationEvent(E event);
}
它是一个泛型接口,泛型的类型必须是ApplicationEvent及其子类,只要实现了这个接口,那么当容器有相应的事件触发时,就能触发onApplicationEvent方法(自动去执行)。ApplicationEvent类的子类有很多.
3.1 简单使用1
使用方法很简单,就是实现一个ApplicationListener接口,并且将加入到容器中就行。
这里监听的是Application启动时候的事情。
@Component
public class MyApplicationListener implements ApplicationListener<ApplicationEvent> {
    @Override
    public void onApplicationEvent(ApplicationEvent event) {
        System.out.println("事件触发:" + event.getClass().getName());
    }
} 
然后启动自己的springboot项目:
@SpringBootApplication
@ServletComponentScan
@EnableAsync
@EnableTransactionManagement
public class SpringBootApplication {
    public static void main(String[] args) {
        SpringApplication.run(SpringBootApplication.class, args);
    }
}
结果
事件触发非注解的方式:org.springframework.boot.web.servlet.context.ServletWebServerInitializedEvent
3.2 简单使用2 自定义事件和监听器
定义事件
首先,我们需要定义一个事件(MyTestEvent),需要继承Spring的ApplicationEvent
public class MyTestEvent extends ApplicationEvent {
    private String msg;
    public MyTestEvent(Object source, String msg) {
        super(source);
        this.msg = msg;
    }
    public String getMsg() {
        return msg;
    }
    public void setMsg(String msg) {
        this.msg = msg;
    }
}
定义监听器
需要定义一下监听器,自己定义的监听器需要实现ApplicationListener,同时泛型参数要加上自己要监听的事件Class名,在重写的方法onApplicationEvent中,添加自己的业务处理:
当监听到具体的时间的时候,会自动调用onApplication方法
@Component
public class MyNoAnnotationListener implements ApplicationListener<MyTestEvent> {
    @Override
    public void onApplicationEvent(MyTestEvent event) {
        System.out.println("非注解监听器:" + event.getMsg());
    }
}
事件发布
有了事件,有了事件监听者,那么什么时候触发这个事件呢?
每次想让监听器收到事件通知的时候,就可以调用一下事件发布的操作。首先在类里自动注入ApplicationEventPublisher,这个也就是我们的ApplicationContext,它实现了这个接口。
@Component
public class MyTestEventPublisher {
    @Autowired
    private ApplicationEventPublisher applicationEventPublisher;
    /**
     *  事件发布方法
     */
    public void pushListener(String msg) {
        applicationEventPublisher.publishEvent(new MyTestEvent(this, msg));
    }
}
测试
用一个http请求来模拟
@RestController
public class TestEventListenerController1 {
    @Autowired
    private MyTestEventPubLisher publisher;
    @RequestMapping(value = "/test/testPublishEvent111")
    public void testPublishEvent() {
        publisher.pushListener("我来了!");
    }
}
启动项目,可以看到控制台输出,测试完成:
事件触发:com.test.personal.unannotation.MyTestEvent // 这个是上面那个事件监控的的输出
非注解监听器:我来了!
四、EventLister的使用
4.1 使用实例
新建事件类PersonUpdateEvent
@Component
@Data
@NoArgsConstructor
@AllArgsConstructor
public class PersonUpdateEvent {
    private Integer id;
    private String name;
    private Integer age;
}
PersonSaveEvent
@Component
@Data
@NoArgsConstructor
@AllArgsConstructor
public class PersonSaveEvent {
    private Integer id;
    private String name;
    private Integer age;
}
4.2.1 单一事件监听器
发布事件
@Service
public class EventPublisher {
    private ApplicationEventPublisher eventPublisher;
    @Autowired
    public void setEventPublisher(ApplicationEventPublisher eventPublisher) {
        this.eventPublisher = eventPublisher;
    }
    public void publishPersonSaveEvent() {
        PersonSaveEvent saveEvent = new PersonSaveEvent();
        saveEvent.setId(1);
        saveEvent.setName("i余数");
        saveEvent.setAge(18);
        eventPublisher.publishEvent(saveEvent);
    }
}
监听事件
@Slf4j
@Service
public class EventListenerService {
    @EventListener
    public void handleForPersonSaveEvent(PersonSaveEvent saveEvent) {
        log.info("saveEvent -> {}", saveEvent);
    }
}
结果
saveEvent -> PersonSaveEvent(id=1, name=i余数, age=18)
4.2.2 使用classes实现多事件监听器
发布事件
在上一个示例的基础上,再多加一个PersonUpdateEvent事件。
public void publishPersonUpdateEvent() {
    PersonUpdateEvent updateEvent = new PersonUpdateEvent();
    updateEvent.setId(1);
    updateEvent.setName("i余数");
    updateEvent.setAge(19);
    eventPublisher.publishEvent(updateEvent);
}
监听事件
@EventListener(classes = {PersonSaveEvent.class, PersonUpdateEvent.class})
public void handleForPersonSaveAndUpdateEvent(Object event) {
    log.info("multi handle event -> {}", event);
}
验证结果
可以监听到多个事件
multi handle event -> PersonSaveEvent(id=1, name=i余数, age=18)
multi handle event -> PersonUpdateEvent(id=1, name=i余数, age=19)
4.2.3 使用condition筛选监听的事件
可以通过condition属性指定一个SpEL表达式,如果返回“true”, “on”, “yes”, or “1”中的任意一个,则事件会被处理,否则不会。
发布事件
public void publishPersonSaveEvent() {
    PersonSaveEvent saveEvent = new PersonSaveEvent();
    saveEvent.setId(1);
    saveEvent.setName("i余数");
    saveEvent.setAge(18);
    eventPublisher.publishEvent(saveEvent);
    PersonSaveEvent saveEvent2 = new PersonSaveEvent();
    saveEvent2.setId(2);
    saveEvent2.setName("i余数");
    saveEvent2.setAge(18);
    eventPublisher.publishEvent(saveEvent2);
}
监听事件
public void publishPersonSaveEvent() {
    PersonSaveEvent saveEvent = new PersonSaveEvent();
    saveEvent.setId(1);
    saveEvent.setName("i余数");
    saveEvent.setAge(18);
    eventPublisher.publishEvent(saveEvent);
    PersonSaveEvent saveEvent2 = new PersonSaveEvent();
    saveEvent2.setId(2);
    saveEvent2.setName("i余数");
    saveEvent2.setAge(18);
    eventPublisher.publishEvent(saveEvent2);
}
结果验证
id为2的事件不满足条件,所以不会执行。
只处理id等于1的 -> PersonSaveEvent(id=1, name=i余数, age=18)
4.3 有返回值的监听器
4.3.1 返回一个单一对象
发布事件
public void publishPersonSaveEvent() {
    PersonSaveEvent saveEvent = new PersonSaveEvent();
    saveEvent.setId(1);
    saveEvent.setName("i余数");
    saveEvent.setAge(18);
    eventPublisher.publishEvent(saveEvent);
}
监听事件
@EventListener
public void handleForPersonUpdateEvent(PersonUpdateEvent updateEvent) {
    log.info("handle update event -> {}", updateEvent);
}
@EventListener
public PersonUpdateEvent handleHaveReturn(PersonSaveEvent saveEvent) {
    log.info("handle save event -> {}", saveEvent);
    PersonUpdateEvent updateEvent = new PersonUpdateEvent();
    updateEvent.setId(saveEvent.getId());
    updateEvent.setName(saveEvent.getName());
    updateEvent.setAge(saveEvent.getAge());
    return updateEvent;
}
验证结果
可以看到我们监听到了2个事件,PersonSaveEvent是我们主动发布的事件,PersonUpdateEvent是handleHaveReturn方法的返回值,会被Spring自动当作一个事件被发送。
handle save event -> PersonSaveEvent(id=1, name=i余数, age=18)
handle update event -> PersonUpdateEvent(id=1, name=i余数, age=18)
4.3.2 返回一个集合
将监听器稍作修改,使其返回一个集合。
@EventListener
public List<PersonUpdateEvent> handleHaveReturn(PersonSaveEvent saveEvent) {
    log.info("handle save event -> {}", saveEvent);
    List<PersonUpdateEvent> events = new ArrayList<>();
    PersonUpdateEvent updateEvent = new PersonUpdateEvent();
    updateEvent.setId(saveEvent.getId());
    updateEvent.setName(saveEvent.getName());
    updateEvent.setAge(saveEvent.getAge());
    events.add(updateEvent);
    PersonUpdateEvent updateEvent2 = new PersonUpdateEvent();
    BeanUtils.copyProperties(updateEvent, updateEvent2);
    events.add(updateEvent2);
    return events;
}
查看结果可以发现,集合中的每个对象都被当作一个单独的事件进行发送。
handle save event -> PersonSaveEvent(id=1, name=i余数, age=18)
handle update event -> PersonUpdateEvent(id=1, name=i余数, age=18)
handle update event -> PersonUpdateEvent(id=1, name=i余数, age=18)
4.3.3 返回一个数组
和返回值为集合一样,数组中的每个对象都被当作一个单独的事件进行发送。
4.4 异步监听器
创建两个监听器,一个同步一个异步,异步监听器就是在方法上加一个@Async注解,但需要注意以下限制:
- 监听器报错不会传递给事件发起者,因为双方已经不在同一个线程了。
 - 异步监听器的非空返回值不会被当作新的事件发布。如果需要发布新事件,需要注入
ApplicationEventPublisher后手动发布。 
@SpringBootApplication
@ServletComponentScan
@EnableAsync
@EnableTransactionManagement
public class SpringBootApplication {
    public static void main(String[] args) {
        SpringApplication.run(SpringBootApplication.class, args);
    }
}
@EventListener
public void handleForPersonSaveEvent(PersonSaveEvent saveEvent) {
    log.info("handle event -> {}", saveEvent);
}
@Async
@EventListener
public void handleForPersonSaveEventAsync(PersonSaveEvent saveEvent) {
    log.info("async handle event -> {}", saveEvent);
}
从执行结果可以看出,异步线程是task-1,不是主线程main,即异步是生效的。
handle event -> PersonSaveEvent(id=1, name=i余数, age=18)
async handle event -> PersonSaveEvent(id=1, name=i余数, age=18)
4.5 监听器异常处理
4.5.1 同步异常处理
使用SimpleApplicationEventMulticaster处理同步监听器抛出异常。先定义一个ErrorHandler:
@Slf4j
@Component
public class MyErrorHandler implements ErrorHandler {
    @Override
    public void handleError(Throwable t) {
        log.info("handle error -> {}", t.getClass());
    }
}
将自定义ErrorHandler绑定到SimpleApplicationEventMulticaster这里涉及一个@PostConstruce
@Slf4j
@Service
public class EventListenerService {
    @Autowired
    private SimpleApplicationEventMulticaster simpleApplicationEventMulticaster;
    @Autowired
    private MyErrorHandler errorHandler;
    @PostConstruct
    public void init() {
        simpleApplicationEventMulticaster.setErrorHandler(errorHandler);
    }
    @Order(1)
    @EventListener
    public void handleForPersonSaveEvent(PersonSaveEvent saveEvent) throws AuthException {
        log.info("handle event -> {}", saveEvent);
        throw new AuthException("test exception");
    }
}
结果:可以看到捕获的异常是UndeclaredThrowableException。
handle event -> PersonSaveEvent(id=1, name=i余数, age=18)
handle error -> class java.lang.reflect.UndeclaredThrowableException
4.5.2 异步异常处理
使用SimpleAsyncUncaughtExceptionHandler来处理@Async抛出的异常。
@Configuration
public class AsyncConfig implements AsyncConfigurer {
    @Override
    public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
        return new SimpleAsyncUncaughtExceptionHandler();
    }
}
监听器代码:人为的抛出一个异常。
@Async
@EventListener
public void handleForPersonSaveEvent(PersonSaveEvent saveEvent) throws AuthException {
    log.info("handle event -> {}", saveEvent);
    throw new AuthException("test exception");
}
结果:SimpleAsyncUncaughtExceptionHandler捕获到了@Async方法抛出的异常
INFO 4416 - [task-1] i.k.s.e.listener.EventListenerService: handle event -> PersonSaveEvent(id=1, name=i余数, age=18)
ERROR 4416 - [task-1] .a.i.SimpleAsyncUncaughtExceptionHandler: Unexpected exception occurred invoking async method: 
    public void xxxx.handleForPersonSaveEvent(xxxx.PersonSaveEvent) throws javax.security.auth.message.AuthException
4.6 监听器排序
如果同时有多个监听器监听同一个事件,默认情况下监听器的执行顺序是随机的,如果想要他们按照某种顺序执行,可以借助Spring的另外一个注解@Order实现。
创建三个监听器,并使用@Order排好序。
@Order(1)
@EventListener
public void handleForPersonSaveEvent(PersonSaveEvent saveEvent) {
    log.info("handle event1 -> {}", saveEvent);
}
@Order(2)
@EventListener
public void handleForPersonSaveEvent2(PersonSaveEvent saveEvent) {
    log.info("handle event2 -> {}", saveEvent);
}
@Order(3)
@EventListener
public void handleForPersonSaveEvent3(PersonSaveEvent saveEvent) {
    log.info("handle event3 -> {}", saveEvent);
}
从执行结果可以看到,确实是按照@Order中指定的顺序执行的。
handle event1 -> PersonSaveEvent(id=1, name=i余数, age=18)
handle event2 -> PersonSaveEvent(id=1, name=i余数, age=18)
handle event3 -> PersonSaveEvent(id=1, name=i余数, age=18)
五、使用场景
5.1 应用启动时缓存预热(系统事件监听)
需求描述: 在应用启动完成后,自动加载热门商品数据到Redis缓存,提升接口响应速度。
@Component
public class CacheWarmUpListener {
    private final ProductService productService;
    private final RedisTemplate<String, Product> redisTemplate;
    @Autowired
    public CacheWarmUpListener(ProductService productService,
                               RedisTemplate<String, Product> redisTemplate) {
        this.productService = productService;
        this.redisTemplate = redisTemplate;
    }
    @EventListener(ApplicationReadyEvent.class)
    public void warmUpCache() {
        List<Product> hotProducts = productService.getTop100HotProducts();
        hotProducts.forEach(product ->
                redisTemplate.opsForValue().set("product:" + product.getId(), product));
        System.out.println("=== 已预热" + hotProducts.size() + "条商品数据到Redis ===");
    }
}
关键点说明:
- 使用
ApplicationReadyEvent而非ApplicationStartedEvent,确保数据库连接等基础设施已就绪 - 通过构造函数注入依赖,避免字段注入的循环依赖问题
 - 预热数据量较大时建议采用分页异步加载
 
5.2 订单创建后发送多平台通知(自定义事件)
需求描述: 当订单创建成功后,需要同时发送短信通知用户、邮件通知客服、更新ERP系统库存。
步骤1:定义自定义事件
public class OrderCreatedEvent extends ApplicationEvent {
    private final Order order;
    public OrderCreatedEvent(Object source, Order order) {
        super(source);
        this.order = order;
    }
    public Order getOrder() {
        return order;
    }
}
步骤2:在Service中发布事件
@Service
public class OrderService {
    private final ApplicationEventPublisher eventPublisher;
    @Autowired
    public OrderService(ApplicationEventPublisher eventPublisher) {
        this.eventPublisher = eventPublisher;
    }
    @Transactional
    public Order createOrder(OrderCreateRequest request) {
        Order newOrder = createNewOrder();
        eventPublisher.publishEvent(new OrderCreatedEvent(this, newOrder));
        return newOrder;
    }
}
步骤3:多监听器处理事件
@Component
public class OrderNotificationListener {
    // 短信通知(最高优先级)
    @EventListener
    @Order(Ordered.HIGHEST_PRECEDENCE)
    public void sendSms(OrderCreatedEvent event) {
        Order order = event.getOrder();
        SmsService.send(order.getUserPhone(),
                "您的订单#" + order.getId() + "已创建,金额:" + order.getAmount());
    }
    // 邮件通知(异步处理)
    @Async
    @EventListener
    public void sendEmail(OrderCreatedEvent event) {
        Order order = event.getOrder();
        EmailTemplate template = EmailTemplate.buildOrderConfirm(order);
        EmailService.send(template);
    }
    // ERP系统库存更新(条件过滤)
    @EventListener(condition = "#event.order.items.?[isPhysicalProduct].size() > 0")
    public void updateErpInventory(OrderCreatedEvent event) {
        ERPInventoryService.updateStock(event.getOrder().getItems());
    }
}
配置异步支持:
@Configuration
@EnableAsync
public class AsyncConfig {
    @Bean(name = "notificationTaskExecutor")
    public Executor taskExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(5);
        executor.setMaxPoolSize(10);
        executor.setQueueCapacity(100);
        executor.setThreadNamePrefix("Notification-");
        executor.initialize();
        return executor;
    }
}
优势:
- 解耦核心业务与通知逻辑
 - 通过@Order控制短信优先于邮件发送
 - 使用@Async避免邮件发送阻塞主线程
 - 条件表达式跳过虚拟商品库存更新
 
5.3 全局请求耗时统计(ServletRequestListener)
需求描述: 统计所有API请求的处理时间,识别慢接口。
@Component
public class RequestMetricsListener implements ServletRequestListener {
    private static final ThreadLocal<Long> startTimeHolder = new ThreadLocal<>();
    @Override
    public void requestInitialized(ServletRequestEvent sre) {
        startTimeHolder.set(System.currentTimeMillis());
    }
    @Override
    public void requestDestroyed(ServletRequestEvent sre) {
        long startTime = startTimeHolder.get();
        long duration = System.currentTimeMillis() - startTime;
        HttpServletRequest request = (HttpServletRequest) sre.getServletRequest();
        String endpoint = request.getRequestURI();
        String method = request.getMethod();
        MetricsService.recordRequestMetrics(endpoint, method, duration);
        // 慢请求预警
        if (duration > 3000) {
            AlarmService.notifySlowRequest(endpoint, method, duration);
        }
        startTimeHolder.remove();
    }
}
注册监听器:
@Bean
public ServletListenerRegistrationBean<RequestMetricsListener> metricsListener() {
    return new ServletListenerRegistrationBean<>(new RequestMetricsListener());
}
统计结果示例:
GET /api/products 平均耗时 45ms | 95分位 120ms
POST /api/orders 平均耗时 250ms | 最大耗时 3200ms(需优化)
5.4 应用优雅停机(ContextClosedEvent)
需求描述:在应用关闭时,确保完成:1)停止接收新请求 2)等待进行中的任务完成 3)释放资源。
@Component
public class GracefulShutdownListener implements ApplicationListener<ContextClosedEvent> {
    private final ThreadPoolTaskExecutor taskExecutor;
    private final DataSource dataSource;
    @Autowired
    public GracefulShutdownListener(ThreadPoolTaskExecutor taskExecutor,
                                    DataSource dataSource) {
        this.taskExecutor = taskExecutor;
        this.dataSource = dataSource;
    }
    @Override
    public void onApplicationEvent(ContextClosedEvent event) {
        // 1. 关闭线程池
        shutdownExecutor(taskExecutor);
        // 2. 关闭数据库连接池
        if (dataSource instanceof HikariDataSource) {
            ((HikariDataSource) dataSource).close();
        }
        // 3. 其他清理工作...
        System.out.println("=== 资源释放完成,应用安全退出 ===");
    }
    private void shutdownExecutor(ExecutorService executor) {
        executor.shutdown();
        try {
            if (!executor.awaitTermination(30, TimeUnit.SECONDS)) {
                executor.shutdownNow();
            }
        } catch (InterruptedException e) {
            executor.shutdownNow();
            Thread.currentThread().interrupt();
        }
    }
}
停机流程:
- 收到SIGTERM信号
 - 关闭新请求入口
 - 等待30秒处理进行中请求
 - 强制关闭剩余任务
 - 释放数据库连接池
 - 应用退出
 
5.5 分布式锁异常恢复
需求描述: 当获取Redis分布式锁失败时,触发重试机制并记录竞争情况。
自定义事件
public class LockAcquireFailedEvent extends ApplicationEvent {
    private final String lockKey;
    private final int retryCount;
    public LockAcquireFailedEvent(Object source, String lockKey, int retryCount) {
        super(source);
        this.lockKey = lockKey;
        this.retryCount = retryCount;
    }
    // getters...
}
事件发布
public class DistributedLock {
    private final ApplicationEventPublisher eventPublisher;
    public boolean tryLock(String key, int maxRetries) {
        int attempts = 0;
        while (attempts < maxRetries) {
            if (RedisClient.acquireLock(key)) {
                return true;
            }
            attempts++;
            eventPublisher.publishEvent(new LockAcquireFailedEvent(this, key, attempts));
            Thread.sleep(100 * attempts);
        }
        return false;
    }
}
监听处理
@Component
public class LockFailureHandler {
    private static final Map<String, AtomicInteger> LOCK_CONTENTION = new ConcurrentHashMap<>();
    @EventListener
    public void handleLockFailure(LockAcquireFailedEvent event) {
        String lockKey = event.getLockKey();
        LOCK_CONTENTION.computeIfAbsent(lockKey, k -> new AtomicInteger(0))
                .incrementAndGet();
        // 竞争激烈时动态调整策略
        if (event.getRetryCount() > 3) {
            adjustBackoffStrategy(lockKey);
        }
    }
    @Scheduled(fixedRate = 10_000)
    public void reportContention() {
        LOCK_CONTENTION.forEach((key, count) ->
                MetricsService.recordLockContention(key, count.get()));
    }
    private void adjustBackoffStrategy(String key) {
        // 动态增加等待时间或告警
    }
}
六、总结
- 过滤器:用于属性甄别,对象收集(不可改变过滤对象的属性和行为)
 - 监听器:用于对象监听,行为记录(不可改变监听对象的属性和行为)
 - 拦截器:用于对象拦截,行为干预(可以改变拦截对象的属性和行为)
 

                
            
        
浙公网安备 33010602011771号