SpringBoot三大组件之监听器(Listener)

SpringBoot三大组件之监听器(Listener)

一、概述

Listener是servlet规范中定义的一种特殊类。用于监听servletContext、HttpSession和servletRequest等域对象的创建和销毁事件。监听域对象的属性发生修改的事件。用于在事件发生前、发生后做一些必要的处理。一般是获取在线人数等业务需求。

如做完某一件事情以后,需要广播一些消息或者通知,告诉其他的模块进行一些事件处理,一般来说,可以一个一个发送请求去通知,但是有一种更好的方式,那就是事件监听,事件监听也是设计模式中发布-订阅模式、观察者模式的一种实现。

观察者模式:

简单的来讲就是你在做事情的时候身边有人在盯着你,当你做的某一件事情是旁边观察的人感兴趣的事情的时候,他会根据这个事情做一些其他的事,但是盯着你看的人必须要到你这里来登记,否则你无法通知到他(或者说他没有资格来盯着你做事情)。

二、定义监听器

Spring提供了两种实现方式:

ApplicationListener接口

@EventListener注解

SmartApplicationListener接口:继承于ApplicationListener接口,支持事件类型过滤和顺序控制

要想顺利的创建监听器,并起作用,这个过程中需要这样几个角色:

事件(event)可以封装和传递监听器中要处理的参数,如对象或字符串,并作为监听器中监听的目标。

监听器(listener)具体根据事件发生的业务处理模块,这里可以接收处理事件中封装的对象或字符串。

事件发布者(publisher)事件发生的触发者。

三、ApplicationListener接口的实现形式

ApplicationListener接口的定义如下:

public interface ApplicationListener 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 {

@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 {

@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 handleHaveReturn(PersonSaveEvent saveEvent) {

log.info("handle save event -> {}", saveEvent);

List 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 redisTemplate;

@Autowired

public CacheWarmUpListener(ProductService productService,

RedisTemplate redisTemplate) {

this.productService = productService;

this.redisTemplate = redisTemplate;

}

@EventListener(ApplicationReadyEvent.class)

public void warmUpCache() {

List 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 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 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 {

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 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) {

// 动态增加等待时间或告警

}

}

六、总结

过滤器:用于属性甄别,对象收集(不可改变过滤对象的属性和行为)

监听器:用于对象监听,行为记录(不可改变监听对象的属性和行为)

拦截器:用于对象拦截,行为干预(可以改变拦截对象的属性和行为)

相关数据

玉器刻章鱼代表什么
线上365bet注册

玉器刻章鱼代表什么

⏳ 10-11 💖 748
铜锣山矿山公园日期:2024-09-03大中小
线上365bet注册

铜锣山矿山公园日期:2024-09-03大中小

⏳ 08-23 💖 50
热水器放水怎么放,海尔热水器放水怎么放
线上365bet注册

热水器放水怎么放,海尔热水器放水怎么放

⏳ 08-25 💖 902