• SpringBoot Event 观察者模式,实现业务解耦


    业务背景

    实际业务开发过程中,业务逻辑可能非常复杂,核心业务+ N个子业务。如果都放到一块儿 去做,代码可能会很长,耦合度不断攀升,维护起来也麻烦,甚至头疼。还有一些业务场景 不需要在一次请求中同步完成,比如邮件发送、短信发送等。

    当然 MQ 也可以解决这个问题,但MQ相对较重,非必要不提升架构复杂度。针对这些问题,我们了解一下 Spring Event

    Spring Event 同步使用

    Spring Event ( Application Event ) 其实就是一个观察者设计模式,一个Bean处理完成任务后希望通知其它Bean或者说一个Bean想观察监听另一个Bean的行为。

    1. 自定义事件

    定义事件,继承ApplicationEvent的类成为一个事件类

    import lombok.Data;
    import lombok.ToString;
    import org.springframework.context.ApplicationEvent;
    
    @Data
    @ToString
    public class OrderProductEvent extends ApplicationEvent {
        private String orderId;
    
        public OrderProductEvent(String source, String orderId) {
            super(source);
            this.orderId = orderId;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    2. 定义监听器

    监听并处理事件,实现 ApplicationListener 接口 或者 使用 @EventListener 注解

    import lombok.SneakyThrows;
    import lombok.extern.slf4j.Slf4j;
    import org.springframework.context.ApplicationListener;
    import org.springframework.stereotype.Component;
    
    /** 实现 ApplicationListener 接口,并指定监听的事件类型 */
    @Slf4j
    @Component
    public class OrderProductListener implements ApplicationListener<OrderProductEvent> {
    
        /** 使用 onApplicationEvent 方法对消息进行接收处理 */
        @SneakyThrows
        @Override
        public void onApplicationEvent(OrderProductEvent event) {
            String orderId = event.getOrderId();
            long start = System.currentTimeMillis();
            Thread.sleep(2000);
            long end = System.currentTimeMillis();
            log.info("{}-校验订单商品价格耗时:({}) 毫秒 ", orderId, (end - start));
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    3. 定义发布者

    发布事件,通过 ApplicationEventPublisher 发布事件

    import lombok.RequiredArgsConstructor;
    import lombok.extern.slf4j.Slf4j;
    import org.springframework.context.ApplicationContext;
    import org.springframework.stereotype.Service;
    
    @Slf4j
    @Service
    @RequiredArgsConstructor
    public class OrderService {
        private final ApplicationContext applicationContext;
    
        // 下单
        public String buyOrder(String orderId) {
            // 1.查询订单详情 TODO
            // 2.校验订单价格(同步处理)
            long start = System.currentTimeMillis();
            applicationContext.publishEvent(new OrderProductEvent(this, orderId));
            // 3.短信通知(异步处理)
            long end = System.currentTimeMillis();
            log.info("任务全部完成,总耗时:({}) 毫秒", end - start);
            return "购买成功";
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    4. 单测执行

    import org.springframework.beans.factory.annotation.Autowired;
    
    @SpringBootTest
    public class OrderServiceTest {
        @Autowired
        private OrderService orderService;
    
        @Test
        public void buyOrderTest() {
            orderService.buyOrder("732171109");
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    SpringEvent 异步使用

    有些业务场景不需要在一次请求中同步完成,比如邮件发送、短信发送等。

    1. 自定义事件

    import lombok.AllArgsConstructor;
    import lombok.Data;
    import lombok.NoArgsConstructor;
    
    @Data
    @NoArgsConstructor
    @AllArgsConstructor
    public class MsgEvent {
        public String orderId;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    2. 定义监听器

    推荐使用 @EventListener 注解

    import lombok.SneakyThrows;
    import lombok.extern.slf4j.Slf4j;
    import org.springframework.context.event.EventListener;
    import org.springframework.stereotype.Component;
    
    @Slf4j
    @Component
    public class MsgListener {
        @SneakyThrows
        @EventListener(MsgEvent.class)
        public void sendMsg(MsgEvent event) {
            String orderId = event.getOrderId();
            long start = System.currentTimeMillis();
            log.info("开发发送短信");
            log.info(" 开发发送邮件 ");
            Thread.sleep(4000);
            long end = System.currentTimeMillis();
            log.info("{}:发送短信、邮件耗时:({}) 毫秒", orderId, (end - start));
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    3. 定义发布者

    import lombok.RequiredArgsConstructor;
    import lombok.extern.slf4j.Slf4j;
    import org.springframework.context.ApplicationContext;
    import org.springframework.stereotype.Service;
    
    @Slf4j
    @Service
    @RequiredArgsConstructor
    public class OrderService {
        private final ApplicationContext applicationContext;
    
         /** 下单 */
        public String buyOrder(String orderId) {
            // 1.查询订单详情 TODO
            // 2.校验订单价格(同步处理)
            long start = System.currentTimeMillis();
            applicationContext.publishEvent(new OrderProductEvent(this, orderId));
            // 3.短信通知(异步处理)
            applicationContext.publishEvent(new MsgEvent(orderId));
            long end = System.currentTimeMillis();
            log.info("任务全部完成,总耗时:({}) 毫秒", end - start);
            return "购买成功";
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    4. 开启异步

    启动类增加 @EnableAsync 注解

    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    import org.springframework.scheduling.annotation.EnableAsync;
    
    @EnableAsync
    @SpringBootApplication
    public class MingYueSpringbootEventApplication {
        public static void main(String[] args) {
            SpringApplication.run(MingYueSpringbootEventApplication.class, args);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    Listener 类需要开启异步的方法增加 @Async 注解

    import lombok.SneakyThrows;
    import lombok.extern.slf4j.Slf4j;
    import org.springframework.context.event.EventListener;
    import org.springframework.scheduling.annotation.Async;
    import org.springframework.stereotype.Component;
    
    @Slf4j
    @Async
    @Component
    public class MsgListener {
        @SneakyThrows
        @EventListener(MsgEvent.class)
        public void sendMsg(MsgEvent event) {
            String orderId = event.getOrderId();
            long start = System.currentTimeMillis();
            log.info("开发发送短信");
            log.info(" 开发发送邮件 ");
            Thread.sleep(4000);
            long end = System.currentTimeMillis();
            log.info("{}:发送短信、邮件耗时:({}) 毫秒", orderId, (end - start));
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
  • 相关阅读:
    大模型技术实践(四)|参数高效微调技术解析及AdaLoRA的应用
    element表格分页+数据过滤筛选
    5 分钟,教你用 Docker 部署一个 Python 应用
    【Linux学习】OpenCV+ROS 实现人脸识别(Ubantu16.04)
    使用excel文件生成sql脚本
    Elasticsearch-使用Logstash同步Mysql
    node.js+vue+Web的疫情大数据平台分析系统
    【EI会议】第二届声学,流体力学与工程国际学术会议(AFME 2023)
    2023年电工杯B题问题二三思路讲解+创新点
    一次日常需求处理带给我的思考
  • 原文地址:https://blog.csdn.net/qwesxd/article/details/126008460