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)

springboot 之 “服务端推送事件SSE(server-send-events)”

秒杀多线程第六篇 经典线程同步 事件Event

在 Visual C++ 中关闭突出显示“事件”关键字

js的一些基础