前言
我们在项目中经常会碰见类似诸如用户支付完订单后,通知用户支付成功,并且商品库存对应减少
这样的需求,也就是做完主业务流程之后,希望异步执行一些其他的操作
。接下来我们来探究解决这个问题的几种方案来引出SpringEvent
,这里的方案就先不讨论MQ队列
。
实现
针对上面的业务需求,我们大概会做如下的代码。
- 同步情况
@Service
public class OrderService {
public void pay() {
System.out.println("支付成功逻辑...");
System.out.println("商品库存减少...");
System.out.println("短信通知...");
}
}
- 异步情况
@Service
public class OrderService {
public void pay() {
System.out.println("支付成功逻辑...");
//简单异步,也可以用线程池,效果一样能说明问题
new Thread(()->{
System.out.println("商品库存减少...");
System.out.println("短信通知...");
}).start();
}
}
假设商品库存减少和短信通知
的代码超过了几十行,可想而知,代码复杂度就上去了。如果说支付成功逻辑之后,只走商品库存减少或者短信通知
两个业务还好,万一以后再加其他代码呢,那就跟垒积木一样,对于后期维护极其不友好,全部逻辑都冗余在一起,违背了单一职责原则
。
这时候我们极其需要一种能高度解耦的一种方式,将这些代码抽离出去,各自维护,有比较好的边界,接下来即将引出SpringEvent
。
方案1:封装成方法
既然说代码放一起比较复杂,那我们就封装成方法,用调用方法的方式来执行,这样以后开发人员去找对应的方法去维护对应的业务就可以了。
@Service
public class OrderService {
@Autowired
GoodsService goodsService;
@Autowired
SMSService smsService;
public void pay() {
System.out.println("支付成功逻辑...");
//简单异步,也可以用线程池,效果一样能说明问题
new Thread(()->{
//减少库存逻辑
goodsService.reduceStock();
//短信通知逻辑
smsService.smsNotify();
}).start();
}
}
再将直接new Thread
方式优化,使用EnableAsync
注解的话,代码如下
@Configuration
@EnableAsync
public class Config{
}
@Service
public class OrderService {
@Autowired
GoodsService goodsService;
@Autowired
SMSService smsService;
public void pay() {
System.out.println("支付成功逻辑...");
//减少库存逻辑
goodsService.reduceStock();
//短信通知逻辑
smsService.smsNotify();
}
}
@Service
public class GoodsService{
@Async
public void reduceStock(){
//减少库存逻辑....
}
}
public class SMSService{
@Async
public void smsNotify(){
//短信通知逻辑...
}
}
方案2:SpringEvent
接下来就是我们的SpringEvent
闪亮登场,Event顾名思义就是Spring事件监听机制。
- 添加依赖
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>4.3.12.RELEASE</version>
</dependency>
不过如果你是SpringBoot
项目,基本不用引入了,肯定都有这个依赖的。
-
创建事件对象
我们这里有两个事件,分别为
减少库存
和短信通知
,所以接下来我们要创建两个事件对象GoodsReduceEvent
、SMSNotifyEvent
,他们都要继承ApplicationEvent
。/** * 库存减少事件 */ public class GoodsReduceEvent extends ApplicationEvent { private String message; public String getMessage() { return message; } public GoodsReduceEvent(Object source, String message){ super(source); } }
/** *@Description 短信发送事件 *@Author wengzhongjie *@Date 2022/7/20 10:15 *@Version */ public class SMSNotifyEvent extends ApplicationEvent { private String message; public String getMessage() { return message; } public SMSNotifyEvent(Object source, String message){ super(source); this.message = message; } }
-
创建监听器
既然事件对象有两个,那么监听器我们也得创建两个
GoodsReduceListener
、SMSMessageNotifyListener
。这里要写一个对应的业务方法,在方法上加上@EventListener
注解,然后在方法参数上使用对应的事件对象。/** *@Description 监听库存减少事件的监听器 *@Author wengzhongjie *@Date 2022/7/20 14:22 *@Version */ @Component public class GoodsReduceListener { @EventListener public void goodsReduce(GoodsReduceEvent goodsReduceEvent) { System.out.println(Thread.currentThread().getName()+"------"+goodsReduceEvent.getMessage()); } }
/** *@Description 监听发送短信事件的监听器 *@Author wengzhongjie *@Date 2022/7/20 14:24 *@Version */ @Component public class SMSMessageNotifyListener { /** * 发送短信 */ @EventListener public void sendSMS(SMSNotifyEvent event) throws InterruptedException { System.out.println(Thread.currentThread().getName()+"------"+event.getMessage()); } }
-
发布事件
接下来就是改造刚才的下单逻辑代码,使用
ApplicationEventPublisher
来统一发布事件。@Service public class OrderService { @Autowired ApplicationEventPublisher applicationEventPublisher; public void pay() { System.out.println("支付成功逻辑..."); //库存减少 applicationEventPublisher.publishEvent(new GoodsReduceEvent(this,"减少库存")); //短信通知 applicationEventPublisher.publishEvent(new SMSNotifyEvent(this,"发送短信")); } }
-
效果
-
使用异步
上面演示的其实是同步情况,如果想异步的话,还是可以使用
@EnableAsync
注解。@Configuration @EnableAsync public class Config{ }
在对应的监听器上加
@Async
注解即可。/** *@Description 监听发送短信事件的监听器 *@Author wengzhongjie *@Date 2022/7/20 14:24 *@Version */ @Component public class SMSMessageNotifyListener { /** * 发送短信 */ @Async @EventListener public void sendSMS(SMSNotifyEvent event) throws InterruptedException { System.out.println(Thread.currentThread().getName()+"------"+event.getMessage()); } }
至此,两种方案都写出来了,大家可以比较一下哪种比较好,楼主还是比较喜欢使用SpringEvent
,虽然说可能代码会多那么一点,但是解耦性和语义性上来说还是比较好的。