zoukankan      html  css  js  c++  java
  • spring事件详解

    一、场景

    假设现在某电商平台天狗有这么个需求,在用户下完单之后,发送短信给用户。

    public void order(){
      // 下单成功
      System.out.println("下单成功...");
      // 发送短信
      sendSms();
    }

    目前来看没什么问题,假设一个月后,天狗平台业务有调整,需要下单完通知仓储物流系统,准备发送货物,代码如下:

    public void order(){
      // 下单成功
      System.out.println("下单成功...");
      // 发送短信
      sendSms();
      // 通知车队发货 
      notifyCar();
    }

    三个月之后,自营效果不明显,砍掉了自营仓库,那代码如下:

    public void order(){
      // 下单成功
      System.out.println("下单成功...");
      // 发送短信
      sendSms();
      // 车队没了,注释掉这行代码 
      // notifyCar();
    }

    经过半年的发展,天狗平台决定还是要走自营的路线,代码如下:

    public void order(){
      // 下单成功
      System.out.println("下单成功...");
      // 发送短信
      sendSms();
      // 车队买回来了
      notifyCar()
    }

    二、问题分析

    平时业务变化非常的快,这是需要从整个公司角度来看,那对于技术层面,如何应对这样的场景呢?以增量的方式应对变化的需求,以此引出spring的监听机制。

     注:其实mq消息中间件也可以解决这样的问题,不过消息中间件解决的是系统与系统之间的问题,spring监听机制则可以在某应用内使用。

    三、使用spring监听机制

    新建springboot项目,这个就不细说了。

    A、创建事件类

    public class OrderSuccessEvent extends ApplicationEvent {
    
        /**
         * 创建订单完成事件类。事件类需继承ApplicationEvent类
         */
        public OrderSuccessEvent(Object source) {
            super(source);
        }
    }

    B、订单方法

    /**
     * 订单服务
     */
    @Service
    public class OrderService {
    
        @Autowired
        private ApplicationContext applicationContext;
    
        public void order() {
            // 下单成功
            System.out.println("下单成功...");
            // 发布通知
            applicationContext.publishEvent(new OrderSuccessEvent(this));
            System.out.println("main线程结束...");
        }
    }

    C、短信服务

    /**
     * 短信服务,监听OrderSuccessEvent。需实现ApplicationListener接口。
     */
    @Service
    public class SmsService implements ApplicationListener<OrderSuccessEvent> {
    
        @Override
        public void onApplicationEvent(OrderSuccessEvent event) {
            this.sendSms();
        }
    
        /**
         * 发送短信
         */
        public void sendSms() 
            System.out.println("发送短信...");
        }
    }

    测试:

    @RunWith(SpringRunner.class)
    @SpringBootTest
    public class Test {
    
        @Autowired
        private OrderService orderService;
    
        @Test
        public void testSpringEvent() {
            orderService.order();
        }
    }

    输出:

    下单成功...
    发送短信...
    main线程结束...

    那这么实现有什么好处呢?

    比如后续又要新建个发货服务,如下:

    /**
     * 物流服务
     */
    @Service
    public class CarService implements ApplicationListener<OrderSuccessEvent> {
        @Override
        public void onApplicationEvent(OrderSuccessEvent event) {
            this.dispatch();
        }
    
        public void dispatch() {
            System.out.println("发车咯...");
        }
    }

    当然也可以使用注解方式,如下:

    /**
     * 物流服务
     */
    @Service
    public class CarService {
        
        /**
         * 发送短信 @EventListener指定监听的事件
         */
        @EventListener(OrderSuccessEvent.class)
        public void dispatch() {
            System.out.println("发车咯...");
        }
    }

    发现了什么问题没有,上面这种是同步监听的方式,下完单之后,需要等发送短信结束、发货结束,这影响了下单的时间。

    因此需要使用异步监听的方式。

    四、异步监听

    当SimpleApplicationEventMulticaster中的Executor不为null,就会执行异步通知。

    @Configuration
    public class AsyncEventConfig {
    
        @Bean(name = "applicationEventMulticaster")
        public ApplicationEventMulticaster simpleApplicationEventMulticaster() {
            SimpleApplicationEventMulticaster eventMulticaster
                    = new SimpleApplicationEventMulticaster();
    
            eventMulticaster.setTaskExecutor(new SimpleAsyncTaskExecutor());
            return eventMulticaster;
        }
    
    }

    五、@TransactionalEventListener

    假设现在有这么个场景,用户注册后发送激活码,代码如下:

    void saveUser(User u) {
        //保存用户信息
        userDao.save(u);
        //触发保存用户事件
        applicationContext.publishEvent(new SaveUserEvent(u.getId()));
    }
    
    @EventListener
    void onSaveUserEvent(SaveUserEvent event) {
        //获取事件中的信息(用户id)
        Integer id = event.getEventData();
        //查询数据库,获取用户(此时如果用户还未插入数据库,则返回空)
        User u = userDao.getUserById(id);
        //这里可能报空指针异常!
        String phone = u.getPhoneNumber();
        MessageUtils.sendMessage(phone);
    }

    为了保障监听事件处理类一定能获取到数据,则使用@TransactionalEventListener,代码如下:

    @TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)
    void onSaveUserEvent(SaveUserEvent event) {
        Integer id = event.getEventData();
        User u = userDao.getUserById(id);
        String phone = u.getPhoneNumber();
        MessageUtils.sendMessage(phone);
    }

    解释下TransactionPhase

    public enum TransactionPhase {
    // 指定目标方法在事务commit之前执行
    BEFORE_COMMIT,
    
    // 指定目标方法在事务commit之后执行
    AFTER_COMMIT,
    
    // 指定目标方法在事务rollback之后执行
    AFTER_ROLLBACK,
    
    // 指定目标方法在事务完成时执行,这里的完成是指无论事务是成功提交还是事务回滚了
    AFTER_COMPLETION
    }

    如果发布事件的地方没有手动加事务,那么需要额外加个配置:

    /**
     * fallbackExecution默认是false,代表发生事件地方没有事务时是否继续执行
     */
    @TransactionalEventListener(fallbackExecution = true)
    如果想给予我更多的鼓励,求打

    因为,我的写作热情也离不开您的肯定支持,感谢您的阅读,我是【阿里马云】!

  • 相关阅读:
    一些业内有名的网站收集
    WCF重载
    FCKEditor fckconfig.js配置,添加字体和大小 附:中文字体乱码问题解决
    查询第几条到第几条的数据的SQL语句
    SPOJ 9939 Eliminate the Conflict
    UVA 10534 Wavio Sequence
    HDU 3474 Necklace
    POJ 2823 Sliding Window
    UVA 437 The Tower of Babylon
    UVA 825 Walking on the Safe Side
  • 原文地址:https://www.cnblogs.com/alimayun/p/14721639.html
Copyright © 2011-2022 走看看