一、概述
Listener
是servlet
规范中定义的一种特殊类。用于监听servletContext
、HttpSession
和servletRequest
等域对象的创建和销毁事件。监听域对象的属性发生修改的事件。用于在事件发生前、发生后做一些必要的处理。一般是获取在线人数等业务需求。
如做完某一件事情以后,需要广播一些消息或者通知,告诉其他的模块进行一些事件处理,一般来说,可以一个一个发送请求去通知,但是有一种更好的方式,那就是事件监听,事件监听也是设计模式中发布-订阅模式、观察者模式的一种实现。
观察者模式:
简单的来讲就是你在做事情的时候身边有人在盯着你,当你做的某一件事情是旁边观察的人感兴趣的事情的时候,他会根据这个事情做一些其他的事,但是盯着你看的人必须要到你这里来登记,否则你无法通知到他(或者说他没有资格来盯着你做事情)。
二、定义监听器
Spring
提供了两种实现方式:
- ApplicationListener接口
- @EventListener注解
- SmartApplicationListener接口:继承于
ApplicationListener
接口,支持事件类型过滤和顺序控制
要想顺利的创建监听器,并起作用,这个过程中需要这样几个角色:
- 事件(
event
)可以封装和传递监听器中要处理的参数,如对象或字符串,并作为监听器中监听的目标。 - 监听器(
listener
)具体根据事件发生的业务处理模块,这里可以接收处理事件中封装的对象或字符串。 - 事件发布者(
publisher
)事件发生的触发者。
三、ApplicationListener接口的实现形式
ApplicationListener
接口的定义如下:
java
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
启动时候的事情。
java
@Component
public class MyApplicationListener implements ApplicationListener<ApplicationEvent> {
@Override
public void onApplicationEvent(ApplicationEvent event) {
System.out.println("事件触发:" + event.getClass().getName());
}
}
然后启动自己的springboot
项目:
java
@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
java
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
方法
java
@Component
public class MyNoAnnotationListener implements ApplicationListener<MyTestEvent> {
@Override
public void onApplicationEvent(MyTestEvent event) {
System.out.println("非注解监听器:" + event.getMsg());
}
}
事件发布
有了事件,有了事件监听者,那么什么时候触发这个事件呢?
每次想让监听器收到事件通知的时候,就可以调用一下事件发布的操作。首先在类里自动注入ApplicationEventPublisher
,这个也就是我们的ApplicationContext
,它实现了这个接口。
java
@Component
public class MyTestEventPublisher {
@Autowired
private ApplicationEventPublisher applicationEventPublisher;
/**
* 事件发布方法
*/
public void pushListener(String msg) {
applicationEventPublisher.publishEvent(new MyTestEvent(this, msg));
}
}
测试
用一个http
请求来模拟
java
@RestController
public class TestEventListenerController1 {
@Autowired
private MyTestEventPubLisher publisher;
@RequestMapping(value = "/test/testPublishEvent111")
public void testPublishEvent() {
publisher.pushListener("我来了!");
}
}
启动项目,可以看到控制台输出,测试完成:
text
事件触发:com.test.personal.unannotation.MyTestEvent // 这个是上面那个事件监控的的输出
非注解监听器:我来了!
四、EventLister的使用
4.1 使用实例
新建事件类PersonUpdateEvent
java
@Component
@Data
@NoArgsConstructor
@AllArgsConstructor
public class PersonUpdateEvent {
private Integer id;
private String name;
private Integer age;
}
PersonSaveEvent
java
@Component
@Data
@NoArgsConstructor
@AllArgsConstructor
public class PersonSaveEvent {
private Integer id;
private String name;
private Integer age;
}
4.2.1 单一事件监听器
发布事件
java
@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);
}
}
监听事件
java
@Slf4j
@Service
public class EventListenerService {
@EventListener
public void handleForPersonSaveEvent(PersonSaveEvent saveEvent) {
log.info("saveEvent -> {}", saveEvent);
}
}
结果
text
saveEvent -> PersonSaveEvent(id=1, name=i余数, age=18)
4.2.2 使用classes实现多事件监听器
发布事件
在上一个示例的基础上,再多加一个PersonUpdateEvent
事件。
java
public void publishPersonUpdateEvent() {
PersonUpdateEvent updateEvent = new PersonUpdateEvent();
updateEvent.setId(1);
updateEvent.setName("i余数");
updateEvent.setAge(19);
eventPublisher.publishEvent(updateEvent);
}
监听事件
java
@EventListener(classes = {PersonSaveEvent.class, PersonUpdateEvent.class})
public void handleForPersonSaveAndUpdateEvent(Object event) {
log.info("multi handle event -> {}", event);
}
验证结果
可以监听到多个事件
text
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"
中的任意一个,则事件会被处理,否则不会。
发布事件
java
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);
}
监听事件
java
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
的事件不满足条件,所以不会执行。
text
只处理id等于1的 -> PersonSaveEvent(id=1, name=i余数, age=18)
4.3 有返回值的监听器
4.3.1 返回一个单一对象
发布事件
java
public void publishPersonSaveEvent() {
PersonSaveEvent saveEvent = new PersonSaveEvent();
saveEvent.setId(1);
saveEvent.setName("i余数");
saveEvent.setAge(18);
eventPublisher.publishEvent(saveEvent);
}
监听事件
java
@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
自动当作一个事件被发送。
text
handle save event -> PersonSaveEvent(id=1, name=i余数, age=18)
handle update event -> PersonUpdateEvent(id=1, name=i余数, age=18)
4.3.2 返回一个集合
将监听器稍作修改,使其返回一个集合。
java
@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;
}
查看结果可以发现,集合中的每个对象都被当作一个单独的事件进行发送。
text
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
后手动发布。
java
@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
,即异步是生效的。
text
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
:
java
@Slf4j
@Component
public class MyErrorHandler implements ErrorHandler {
@Override
public void handleError(Throwable t) {
log.info("handle error -> {}", t.getClass());
}
}
将自定义ErrorHandler
绑定到SimpleApplicationEventMulticaster
这里涉及一个@PostConstruce
java
@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
。
text
handle event -> PersonSaveEvent(id=1, name=i余数, age=18)
handle error -> class java.lang.reflect.UndeclaredThrowableException
4.5.2 异步异常处理
使用SimpleAsyncUncaughtExceptionHandler
来处理@Async
抛出的异常。
java
@Configuration
public class AsyncConfig implements AsyncConfigurer {
@Override
public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
return new SimpleAsyncUncaughtExceptionHandler();
}
}
监听器代码:人为的抛出一个异常。
java
@Async
@EventListener
public void handleForPersonSaveEvent(PersonSaveEvent saveEvent) throws AuthException {
log.info("handle event -> {}", saveEvent);
throw new AuthException("test exception");
}
结果:SimpleAsyncUncaughtExceptionHandler
捕获到了@Async
方法抛出的异常
text
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
排好序。
java
@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
中指定的顺序执行的。
text
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
缓存,提升接口响应速度。
java
@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:定义自定义事件
java
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中发布事件
java
@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:多监听器处理事件
java
@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());
}
}
配置异步支持:
java
@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请求的处理时间,识别慢接口。
java
@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();
}
}
注册监听器:
java
@Bean
public ServletListenerRegistrationBean<RequestMetricsListener> metricsListener() {
return new ServletListenerRegistrationBean<>(new RequestMetricsListener());
}
统计结果示例:
text
GET /api/products 平均耗时 45ms | 95分位 120ms
POST /api/orders 平均耗时 250ms | 最大耗时 3200ms(需优化)
5.4 应用优雅停机(ContextClosedEvent)
需求描述:在应用关闭时,确保完成:1)停止接收新请求 2)等待进行中的任务完成 3)释放资源。
java
@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
分布式锁失败时,触发重试机制并记录竞争情况。
自定义事件
java
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...
}
事件发布
java
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;
}
}
监听处理
java
@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) {
// 动态增加等待时间或告警
}
}
六、总结
- 过滤器:用于属性甄别,对象收集(不可改变过滤对象的属性和行为)
- 监听器:用于对象监听,行为记录(不可改变监听对象的属性和行为)
- 拦截器:用于对象拦截,行为干预(可以改变拦截对象的属性和行为)