作者简介:大家好,我是smart哥,前中兴通讯、美团架构师,现某互联网公司CTO
联系qq:184480602,加我进群,大家一起学习,一起进步,一起对抗互联网寒冬
假设现在有这么一个业务场景:
用户在京西商城下单成功后,平台要发送短信通知用户下单成功
我们最直观的想法是直接在order()方法中添加发送短信的业务代码:
public void order(){
// 下单操作
log.info("下单成功, 订单号:{}", orderNumber);
// 发送短信
sendSms();
}
public void sendSms(){
// 省略...
}
咋一看没什么不妥,但随着时间推移,上面的代码就会暴露出局限性:
一个月后,京西搞了自建物流体系,用户下单成功后,还需要通知物流系统发货
于是你又要打开OrderService修改order()方法:
public void order(){
// 下单成功
log.info("下单成功, 订单号:{}", orderNumber);
// 发送短信
sendSms();
// 通知车队发货
notifyCar();
}
嗯,完美。
又过了一个月,东哥被抓了,股价暴跌,决定卖掉自己的车队,所以下单后就不用通知车队了
重新修改OrderService:
public void order(){
// 下单成功
log.info("下单成功, 订单号:{}", orderNumber);
// 发送短信
sendSms();
// 车队没了,注释掉这行代码
// notifyCar();
}
又过了一个月,东哥明尼苏达州荣耀归来:回来做我的兄弟一起开车吧。
好的,东哥。
public void order(){
// 下单成功
log.info("下单成功, 订单号:{}", orderNumber);
// 发送短信
sendSms();
// 车队买回来了,放开这段代码
notifyCar()
}
车队回来了,你却受不了这大起大落异常刺激的生活,决定离职。
就在这时候,组长拉住了你,语重心长地和你说:
小伙子,知道什么叫“以增量的方式应对变化的需求”吗?听过Spring事件监听机制吗?
说时迟那时快,组长拿来一支笔和一张纸,唰唰唰画了一张图:
你顿时心领神会,扑通一声跪在了地上,开始敲起了代码。
还是用之前的SpringBoot项目即可,只要你引入了spring-boot-starter-web。
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
OrderService
/**
* 订单服务
*/
@Service
public class OrderService {
@Autowired
private ApplicationContext applicationContext;
public void order() {
// 下单成功
System.out.println("下单成功...");
// 发布通知(传入了当前对象)
applicationContext.publishEvent(new OrderSuccessEvent(this));
System.out.println("main线程结束...");
}
}
OrderSuccessEvent(继承ApplicationEvent,自定义事件)
public class OrderSuccessEvent extends ApplicationEvent {
/**
* Create a new ApplicationEvent.
*
* @param source the object on which the event initially occurred (never {@code null})
*/
public OrderSuccessEvent(Object source) {
super(source);
}
}
SmsService(实现ApplicationListener,监听OrderSuccessEvent)
/**
* 短信服务,监听OrderSuccessEvent
*/
@Service
public class SmsService implements ApplicationListener<OrderSuccessEvent> {
@Override
public void onApplicationEvent(OrderSuccessEvent event) {
this.sendSms();
}
/**
* 发送短信
*/
public void sendSms() {
System.out.println("发送短信...");
}
}
Test
@RunWith(SpringRunner.class)
@SpringBootTest
public class Test {
@Autowired
private OrderService orderService;
@Test
public void testSpringEvent() {
orderService.order();
}
}
输出:
下单成功...
发送短信...
main线程结束...
流程示意图(为什么SmsService能监听到?更详细的流程会在山寨版Spring事件监听机制中介绍):
如果后期针对下单成功有新的操作,可以新写一个事件监听类:
/**
* 物流服务
*/
@Service
public class CarService implements ApplicationListener<OrderSuccessEvent> {
@Override
public void onApplicationEvent(OrderSuccessEvent event) {
this.dispatch();
}
public void dispatch() {
System.out.println("发车咯...");
}
}
这就是以增量的方式应对变化的需求,而不是去修改已有的代码(ServiceA)。同样的,对老项目改造时也是如此,如果你不知道原来的接口是干嘛的,最好不要去动它,宁愿新写一个接口,即一般提倡“对扩展开放,对修改关闭”。
上面SmsService既是一个服务,还是一个Listener,因为它既有@Service又实现了ApplicationListener接口。
但是仅仅是为了一个监听回调方法而实现一个接口,未免麻烦,所以Spring提供了注解的方式:
/**
* 短信服务,监听OrderSuccessEvent,但不用实现ApplicationListener
*/
@Service
public class SmsService {
/**
* 发送短信 @EventListener指定监听的事件
*/
@EventListener(OrderSuccessEvent.class)
public void sendSms() {
try {
Thread.sleep(1000L * 5);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("发送短信...");
}
}
看似很完美了,但是你注意到Spring默认的事件机制是同步的:
如果针对OrderService下单成功的操作越来越多,比如下单后需要完成的对应操作有十几个,那么等十几个其他服务都调用完毕,东哥可能又去明尼苏达州了。
所以,你必须想办法把Spring的事件机制改成异步的,尽可能快地返回下单的结果本身,而不是等其他附属服务全部完成(涉及到其他问题暂时按下不表)。
要想把Spring事件机制改造成异步通知,最粗暴的方法是:
OrderService
/**
* 订单服务
*/
@Service
public class OrderService {
@Autowired
private ApplicationContext applicationContext;
public void order() {
// 下单成功
System.out.println("下单成功...");
// 发布通知
new Thread(() ->{
applicationContext.publishEvent(new OrderSuccessEvent(this));
}).start();
System.out.println("main线程结束...");
// 等SmsService结束
try {
Thread.sleep(1000L * 5);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
SmsService
/**
* 短信服务,监听OrderSuccessEvent
*/
@Service
public class SmsService implements ApplicationListener<OrderSuccessEvent> {
@Override
public void onApplicationEvent(OrderSuccessEvent event) {
this.sendSms();
}
/**
* 发送短信
*/
public void sendSms() {
try {
Thread.sleep(1000L * 3);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("发送短信...");
}
}
输出:
下单成功...
main线程结束...
发送短信...
当然,这种做法其实违背了Spring事件机制的设计初衷。人家会想不到你要搞异步通知?
当SimpleApplicationEventMulticaster中的Executor不为null,就会执行异步通知。
@Configuration
public class AsyncEventConfig {
@Bean(name = "applicationEventMulticaster")
public ApplicationEventMulticaster simpleApplicationEventMulticaster() {
SimpleApplicationEventMulticaster eventMulticaster
= new SimpleApplicationEventMulticaster();
eventMulticaster.setTaskExecutor(new SimpleAsyncTaskExecutor());
return eventMulticaster;
}
}
把OrderService改回来:
@Service
public class OrderService {
@Autowired
private ApplicationContext applicationContext;
public void order() {
// 下单成功
System.out.println("下单成功...");
// 发布通知
applicationContext.publishEvent(new OrderSuccessEvent(this));
System.out.println("main线程结束...");
// 等SmsService结束
try {
Thread.sleep(1000L * 5);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
此时仍然是异步的。
最后说一句,Spring事件机制适合单体应用,同一个JVM且并发不大的情况,如果是分布式应用,推荐使用MQ。Spring事件监听机制和MQ有相似的地方,也有不同的地方。MQ允许跨JVM,因为它本身是独立于项目之外的,切提供了消息持久化的特性,而Spring事件机制哪怕使用了异步,本质是还是一种方法调用,宕机了就没了。
之前在知乎,有知友留言,为什么OrderService发布事件要放入this,感觉没什么用:
大家有注意到这个细节吗~你们觉得有什么用呢?
作者简介:大家好,我是smart哥,前中兴通讯、美团架构师,现某互联网公司CTO
进群,大家一起学习,一起进步,一起对抗互联网寒冬