业务场景分析
用户下订单-商户处理订单-订单派送-订单完成-订单评价
这些流程就包含了很多通知用户和商家的推送信息
用户下单通知商家的初步实现
import com.zm.notice.one.service.OrderService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class UserController {
@Autowired
private OrderService orderService;
@GetMapping("/order/submit")
public void orderSubmit(){
orderService.submit();
}
}
/**
* 订单业务
*/
public interface OrderService {
void submit();
}
import com.zm.notice.one.service.NoticeService;
import com.zm.notice.one.service.OrderService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Slf4j
@Service
public class OrderServiceImpl implements OrderService {
@Autowired
private NoticeService noticeService;
// 记得加事务
@Override
public void submit() {
// 此处省略一堆代码。。。。。。。。
log.info("用户订单提交了");
// 通知业务不能影响订单提交,此处应该使用异步(异步线程池或MQ)
noticeService.sendMsg();
}
}
/**
* 通知业务接口
*/
public interface NoticeService {
void sendMsg();
}
import com.zm.notice.one.service.NoticeService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
@Slf4j
@Service
public class NoticeServiceImpl implements NoticeService {
@Override
public void sendMsg() {
// 刚开始的代码
log.info("弹窗和语音提醒:您有一单新的订单~");
// 用户反馈 可能上厕所了听不到啊,那就加发短信提醒
log.info("短信通知:您有一单新的订单~");
// 用户又反馈 短信被拦截了啊,骚扰短信 投诉
log.info("打电话提醒:您有一单新的订单~");
// 用户又反馈我不想接短信了 我就想接电话 。。。。。。
// 用户又反馈我不想接到电话 我就想接短信 。。。。。。
// 程序员:mmp 老子不干了!!!
/*
缺点:
1.代码频繁改动,不符合开闭原则
2.代码没解耦,越加越多 屎山代码就是这样来的
*/
}
}
如何解决呢?
多实现类+注解+ApplicationContextAware 进行代码改造
import java.lang.annotation.*;
/**
* 是否执行发送通知的自定义注解
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface SendMsg {
/**
* 是否执行发送通知
*/
boolean task() default false;
}
import com.zm.notice.one.service.NoticeService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
@SendMsg(task = true)
@Slf4j
@Service("VoiceNotice")
public class VoiceNotice implements NoticeService {
@Override
public void sendMsg() {
log.info("VoiceNotice 弹窗和语音提醒:您有一单新的订单~");
}
}
import com.zm.notice.one.service.NoticeService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
@SendMsg(task = true)
@Slf4j
@Service("MsgNotice")
public class MsgNotice implements NoticeService {
@Override
public void sendMsg() {
log.info("MsgNotice 短信通知:您有一单新的订单~");
}
}
import com.zm.notice.one.service.NoticeService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
@SendMsg(task = true)
@Slf4j
@Service("PhoneNotice")
public class PhoneNotice implements NoticeService {
@Override
public void sendMsg() {
log.info("PhoneNotice 打电话提醒:您有一单新的订单~");
}
}
import com.zm.notice.one.service.NoticeService;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;
import java.util.Map;
/**
* 注意:
* 从ApplicationContextAware获取ApplicationContext上下文的情况,
* 仅仅适用于当前运行的代码和已启动的Spring代码处于同一个Spring上下文,
* 否则获取到的ApplicationContext是空的。
* 定时任务是没办法获取到项目所在Spring容器启动之后的ApplicationContext
*/
@Component
public class NoticeServiceFactory implements ApplicationContextAware {
private static Map<String, NoticeService> serviceMap;
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
serviceMap = applicationContext.getBeansOfType(NoticeService.class);
}
/**
* 执行所有实现类
*
*/
public void task() {
serviceMap.forEach((k,v)-> {
SendMsg annotation = v.getClass().getAnnotation(SendMsg.class);
if (annotation != null && annotation.task()) {
v.sendMsg();
}
});
}
}
但是这种方式没有实现事务提交后,才进行消息通知,无法保证一致性
解决办法也是有的,可以通过aop+注解实现,不推荐
SpringBoot 事件发布监听机制使用(推荐)
观察者模式想必大家都不陌生~ 可以使用SpringBoot 事件发布监听机制实现消息通知
参考链接
链接: SpringBoot 中发布ApplicationEventPublisher,监听ApplicationEvent 异步操作.
链接: SpringBoot 事件发布监听机制使用、分析、注意点 (一篇到位).
链接: 深入分析SpringBoot下的事件/监听机制以及实现所有事件的异步处理.
链接: springboot ApplicationEvent事件监听与异步.
链接: Spring事务事件监控
代码
import org.springframework.context.ApplicationEvent;
/**
* 发送通知事件
*/
public class SendMsgEvent extends ApplicationEvent {
// 参数可以通过构造方法传入
public SendMsgEvent(Object source) {
super(source);
}
}
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.event.EventListener;
import org.springframework.core.annotation.Order;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;
@Slf4j
@Component
public class MsgNoticeEventListener {
//@Async(“自定义的异步线程池”)
@EventListener
@Order(2)// 可以控制执行的顺序先后,值越小权重越大 通知方法都走异步,意义也不大
// 也可以控制在事务提交后执行 使用@TransactionalEventListener
// @TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)
public void sendMsg(SendMsgEvent event) {
log.info("MsgNoticeEventListener 短信通知:您有一单新的订单~");
}
}
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.event.EventListener;
import org.springframework.core.annotation.Order;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;
@Slf4j
@Component
public class PhoneNoticeEventListener {
//@Async(“自定义的异步线程池”)
@EventListener
@Order(1)
public void sendMsg(SendMsgEvent event) {
log.info("PhoneNoticeEventListener 打电话提醒:您有一单新的订单~");
}
}
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.event.EventListener;
import org.springframework.core.annotation.Order;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;
@Slf4j
@Component
public class VoiceNoticeEventListener {
//@Async(“自定义的异步线程池”)
@EventListener
@Order(3)
public void sendMsg(SendMsgEvent event) {
log.info("VoiceNoticeEventListener 弹窗和语音提醒:您有一单新的订单~");
}
}
import com.zm.notice.one.service.OrderService;
import com.zm.notice.three.SendMsgEvent;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.stereotype.Service;
@Slf4j
@Service
public class OrderServiceImpl implements OrderService {
@Autowired
private ApplicationEventPublisher applicationEventPublisher;
// 记得加事务
@Override
public void submit() {
// 此处省略一堆代码。。。。。。。。
log.info("用户订单提交了");
SendMsgEvent complaintEvent = new SendMsgEvent(this);
applicationEventPublisher.publishEvent(complaintEvent);
}
}
这样实现的好处
- 彻底解耦了,每个类只需关注自己的业务,自行处理自己内部的逻辑
- 场景不限于推送通知,只要是涉及需要异步消息的都可以看情况使用
使用时注意事务和同步异步方法
使用@TransactionalEventListener监听事件
TransactionPhase.BEFORE_COMMIT 事务提交前
TransactionPhase.AFTER_COMMIT 事务提交后
TransactionPhase.AFTER_ROLLBACK 事务回滚后
TransactionPhase.AFTER_COMPLETION 事务完成后
@Async异步事件监听
没有此注解事件监听方法与主方法为一个事务。
使用此注解将脱离原有事务,BEFORE_COMMIT也无法拦截事务提交前时刻
此注解需要配合@EnableAsync一起使用
那么
不使用异步
// 监听方法与主方法为一个事务,BEFORE_COMMIT 事务提交前触发事件
// 那么方法将与事件方法 共同成功或失败
@TransactionalEventListener(phase =TransactionPhase.BEFORE_COMMIT)
public void sendMsg(SendMsgEvent event) {
}
使用异步
@Async // 使用此注解将脱离原有事务
// 那么在原有事务提交后,触发事件 用于异步发送消息等,或最终消息中间件来实现一致性
@TransactionalEventListener(phase =TransactionPhase.AFTER_COMMIT)
public void sendMsg(SendMsgEvent event) {
}
使用注意事项
链接: @TransactionalEventListener Spring 事务绑定事件的深入学习与使用