• 使用SpringEvent事件通知做异步消息处理


    前言

    我们在项目中经常会碰见类似诸如用户支付完订单后,通知用户支付成功,并且商品库存对应减少这样的需求,也就是做完主业务流程之后,希望异步执行一些其他的操作。接下来我们来探究解决这个问题的几种方案来引出SpringEvent,这里的方案就先不讨论MQ队列

    实现

    针对上面的业务需求,我们大概会做如下的代码。

    • 同步情况
    @Service
    public class OrderService {
    
        public void pay() {
            System.out.println("支付成功逻辑...");
            System.out.println("商品库存减少...");
            System.out.println("短信通知...");
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 异步情况
    @Service
    public class OrderService {
    
        public void pay() {
            System.out.println("支付成功逻辑...");
            //简单异步,也可以用线程池,效果一样能说明问题
            new Thread(()->{
                System.out.println("商品库存减少...");
                System.out.println("短信通知...");
            }).start();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    假设商品库存减少和短信通知的代码超过了几十行,可想而知,代码复杂度就上去了。如果说支付成功逻辑之后,只走商品库存减少或者短信通知两个业务还好,万一以后再加其他代码呢,那就跟垒积木一样,对于后期维护极其不友好,全部逻辑都冗余在一起,违背了单一职责原则

    这时候我们极其需要一种能高度解耦的一种方式,将这些代码抽离出去,各自维护,有比较好的边界,接下来即将引出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();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    再将直接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(){
            //短信通知逻辑...
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    方案2:SpringEvent

    接下来就是我们的SpringEvent闪亮登场,Event顾名思义就是Spring事件监听机制。

    • 添加依赖
     <dependency>
            <groupId>org.springframeworkgroupId>
            <artifactId>spring-contextartifactId>
            <version>4.3.12.RELEASEversion>
        dependency>
    
    • 1
    • 2
    • 3
    • 4
    • 5

    不过如果你是SpringBoot项目,基本不用引入了,肯定都有这个依赖的。

    • 创建事件对象

      我们这里有两个事件,分别为减少库存短信通知,所以接下来我们要创建两个事件对象GoodsReduceEventSMSNotifyEvent,他们都要继承ApplicationEvent

      /**
       * 库存减少事件
       */
      public class GoodsReduceEvent extends ApplicationEvent {
      
          private String message;
      
          public String getMessage() {
              return message;
          }
      
          public GoodsReduceEvent(Object source, String message){
              super(source);
              
          }
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      /**
      *@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;
          }
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
    • 创建监听器

      既然事件对象有两个,那么监听器我们也得创建两个GoodsReduceListenerSMSMessageNotifyListener。这里要写一个对应的业务方法,在方法上加上@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());
          }
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      /**
      *@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());
          }
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
    • 发布事件

      接下来就是改造刚才的下单逻辑代码,使用ApplicationEventPublisher来统一发布事件。

      @Service
      public class OrderService {
          @Autowired
          ApplicationEventPublisher applicationEventPublisher;
      
          public void pay() {
              System.out.println("支付成功逻辑...");
              //库存减少
              applicationEventPublisher.publishEvent(new GoodsReduceEvent(this,"减少库存"));
              //短信通知
              applicationEventPublisher.publishEvent(new SMSNotifyEvent(this,"发送短信"));
          }
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
    • 效果

    在这里插入图片描述

    • 使用异步

      上面演示的其实是同步情况,如果想异步的话,还是可以使用@EnableAsync注解。

      @Configuration
      @EnableAsync
      public class Config{
          
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5

      在对应的监听器上加@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());
            }
        }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18

    至此,两种方案都写出来了,大家可以比较一下哪种比较好,楼主还是比较喜欢使用SpringEvent,虽然说可能代码会多那么一点,但是解耦性和语义性上来说还是比较好的。

  • 相关阅读:
    【springboot进阶】RestTemplate集成okhttp3并自定义日志打印
    分布式监控树
    线程池指令系统
    华为机试真题 C++ 实现【探索地块建立】【2022.11 Q4 新题】
    优秀的项目管理办公室(PMO)的5条规则
    java计算机毕业设计高校教材征订管理系统MyBatis+系统+LW文档+源码+调试部署
    (十)defer关键字
    linux-tools-$(uname -r) linux-headers-$(uname -r)工具安装:
    操作系统4小时速成:进程管理占考试40%,进程状态,组织,通信,线程拥有调度,进程拥有资源,进程和线程的区别
    MySQL面试知识点总结(持续更新)
  • 原文地址:https://blog.csdn.net/javaboyweng/article/details/125891865