springboot通知模块的设计- 设计模式(四)

人生之路坎坎坷坷,跌跌撞撞在所难免。但是,不论跌了多少次,你都必须坚强勇敢地站起来。任何时候,无论你面临着生命的何等困惑抑或经受着多少挫折,无论道路多艰难,希望变得如何渺茫,请你不要绝望,再试一次,坚持到底,成功终将属于勇不言败的你。

导读:本篇文章讲解 springboot通知模块的设计- 设计模式(四),希望对大家有帮助,欢迎收藏,转发!站点地址:www.bmabk.com,来源:原文

业务场景分析

用户下订单-商户处理订单-订单派送-订单完成-订单评价
这些流程就包含了很多通知用户和商家的推送信息

用户下单通知商家的初步实现

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) {

}

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/133913.html

(0)
飞熊的头像飞熊bm

相关推荐

发表回复

登录后才能评论
极客之音——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!