springboot事件event的使用(关键词:ApplicationEvent,@EventListener,publishEvent)
Posted 百里东君~
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了springboot事件event的使用(关键词:ApplicationEvent,@EventListener,publishEvent)相关的知识,希望对你有一定的参考价值。
spring事件,大家应该都不陌生,使用观察者模式进行设计,让业务代码可以更加解耦。那么他的使用场景有哪些呢?
当我们保存一个新注册的用户信息,可能需要发送邮件通知用户注册成功,并且还要发送短信通知用户,那么我们不应该在代码中让这3块业务操作同步执行,因为后2则是调用第三方进行操作的,避免不了http的开销,假如网络出了问题,那么会影响到我们新用户的注册,并且后两者可有可无,不应该影响我们的主业务流程。所以我们可以使用线程池(使得后2个业务操作在线程池中运行,但这2个业务操作不应该耦合在一起,毕竟他们也不分前后,不能因为邮件服务出了网络故障导致短信服务也进行不了)那我们就需要启动2个线程分别去执行这2个业务,为了让代码书写更加优雅且解耦,建议使用spring的异步事件来书写demo
讲完了案例,那就直接进入主题讲实际操作吧,以上面该场景书写demo
一、基础
新建一个springboot项目
1、创建一个ApplicationContextUtil工具类
并在main启动的时候初始化,后续有使用到
/**
* @author liuyuexi
* @description 上下文工具类
* @copyright Copyright (C) 2022 yuec <br>
* @company yuec
* @date 2022/5/4 23:33
*/
public class ApplicationContextUtil
/**应用上下文*/
private static ApplicationContext ctx;
/**
* 初始化ApplicationContext
* @param ctx
* @return void
* @author yuec
* @date 2022/5/4 23:33
*/
public static void initApplicationContext(ApplicationContext ctx)
ApplicationContextUtil.ctx = ctx;
/**
* @author liuyuexi
* @description 获取spring上下文
* @copyright Copyright (C) 2022 yuec <br>
* @company yuec
* @date 2022/5/4 23:34
*/
public static ApplicationContext getCtx()
return ApplicationContextUtil.ctx;
//开启异步事件,后续用到
@EnableAsync
@SpringBootApplication
public class MyEventTestApplication
public static void main(String[] args)
ConfigurableApplicationContext run = SpringApplication.run(MyEventTestApplication.class, args);
ApplicationContextUtil.initApplicationContext(run);
2、编写自定义事件类,需要继承spring的ApplicationEvent
/**
* @author liuyuexi
* @description 保存新用户推送事件
* @copyright Copyright (C) 2022 yuec <br>
* @company yuec
* @date 2022/5/4 23:17
*/
@Slf4j
public class SaveEvent extends ApplicationEvent
public SaveEvent(SaveEventDto source)
super(source);
log.info("保存新用户推送事件");
3、编写邮件和短信事件监听类,需要使用spring注解@EventListener
@Slf4j
@Component
public class EmailListener
@Async
@EventListener(SaveEvent.class)
public void emailDeal(SaveEvent event)
SaveEventDto saveEventDto = (SaveEventDto) event.getSource();
log.info("邮件服务处理消息=", saveEventDto);
//业务处理
@Slf4j
@Component
public class SmsListener
@Async
@EventListener(SaveEvent.class)
public void smsDeal(SaveEvent event)
SaveEventDto saveEventDto = (SaveEventDto) event.getSource();
log.info("短信服务处理消息=", saveEventDto);
//业务处理
4、编写一个controller进行测试
首先创建一个dto对象
@Data
public class SaveEventDto
private String userName;
编写一个测试类
@Slf4j
@RestController("/test")
public class TestController
@GetMapping("/save/name")
private String saveColor(@PathVariable("name") String name)
//保存颜色到数据库
log.info("Controller保存颜色到数据库", name);
//发送时间
SaveEventDto saveEventDto = new SaveEventDto();
saveEventDto.setUserName(name);
log.info("Controller发送事件", saveEventDto);
ApplicationContextUtil.getCtx().publishEvent(new SaveEvent(saveEventDto));
return "success";
5、执行测试
idea有个友好的提示(小耳机),可以很清楚看到,我们推送的事件有哪些监听者进行消费
控制台日志打印
6、补充:异步添加线程池优化
@Configuration
@EnableAsync
public class AsyncConfig implements AsyncConfigurer
/**
* 手动注入线程池
*/
@Override
public Executor getAsyncExecutor()
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
//核心线程池数量
executor.setCorePoolSize(Runtime.getRuntime().availableProcessors());
//最大线程池数量
executor.setMaxPoolSize(Runtime.getRuntime().availableProcessors() * 5);
//线程池的队列容量
executor.setQueueCapacity(Runtime.getRuntime().availableProcessors() * 2);
//线程池名称前缀
executor.setThreadNamePrefix("async-executor-");
executor.initialize();
return executor;
@Override
public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler()
return new SimpleAsyncUncaughtExceptionHandler();
可以看到目前spring的事件可以很简单就完成
二、看法
spring事件尽管能够较好的解耦,但却要特别注意使用的业务场景,因为我也遇到过一些系统,当他们变更数据库某个字段时候,需要发事件去异步同步es或mongodb,但是每天变更的数据量非常多,而且每次只是更新某一条数据而不是批量去让线程池处理,可能会造成了这块业务频繁争抢线程资源,针对这种其实更适合发送到mq队列中,也可以避免大量更新操作导出线程阻塞,或者因为服务重启发版等操作,造成消息丢失。
以上是关于springboot事件event的使用(关键词:ApplicationEvent,@EventListener,publishEvent)的主要内容,如果未能解决你的问题,请参考以下文章
springboot事件event的使用(关键词:ApplicationEvent,@EventListener,publishEvent)
SpringBoot -- 事件(Application Event)