• spring event的事件驱动模型的最佳实践@EventListene



           我们知道观察者模式可以实现代码的解耦,而spring的event模型就是这种设计模式的极佳体现。一个事件包含:事件发布、监听、和事件源。在spring中我们可以通过ApplicationContext的publishEvent方法去发布事件;通过实现ApplicationListener接口来自定义自己的监听器;继承ApplicationEvent类来实现事件源。下面以一个实例来说明:

    1.spring下使用event模型

    1.1 定义event

    /**
     * event的基类
     *
     * @author 94977
     * @create 2018/7/22
     */
    public abstract class BaseEvent extends ApplicationEvent {
        public BaseEvent(Object source) {
            super(source);
        }
    }
    
    public class FaceEvent extends BaseEvent {
    
        /**
        * @author 94977
        * @time 2018/7/22 15:50
        * @param * @param null
        * @return
        * @description  必须要实现的构造方法
        */
        public FaceEvent(User user) {
            super(user);
        }
    }
    

    1.2 event的监听处理类。监听类实现ApplicationListener 里onApplicationEvent方法即可

    @Component
    public class FaceEventListener implements ApplicationListener {
    
        @Override
        public void onApplicationEvent(ApplicationEvent event) {
            if(event instanceof FaceEvent){
                User user = (User) event.getSource();
                LOGGER.info("===> 收到人脸事件:  {}",user);
                // .....
                System.out.println("人脸事件处理结束。。。");
            }
        }
    }
    

           当然通过event instanceof FaceEvent判断事件源来处理的方式不是很优雅。有更好的方式,接口ApplicationListener支持泛型,可以通过泛型来判断处理的事件源。如下只处理FaceEvent源。

    @Component
    public class FaceEventListener extends BaseEventListener implements ApplicationListener<FaceEvent> {
    
        @Override
        public void onApplicationEvent(FaceEvent event) {
            User user = (User) event.getSource();
            LOGGER.info("===> 收到人脸事件:  {}",user);
            // .....
            System.out.println("人脸事件处理结束。。。");
    
        }
    }
    

    如果要实现有序的监听,实现SmartApplicationListener 接口即可

    1.3 发布事件

    @Service
    public class FaceHandler {
    
        @Autowired
        private ApplicationContext applicationContext;
    
        public void handle(){
            User user = new User();
            user.setAge(34);
            user.setUsername("人脸事件");
            user.setHobby("抓拍");
            //发布事件
            applicationContext.publishEvent(new FaceEvent(user));
    		//进行其他业务处理
        }
    
    

    以上即可。

    2.evnet模型的注意点

    • 事件没要处理的监听器,就会被抛弃。
    • 一个事件可以同时被多个监听处理类监听处理。
    • 以上处理事件都是同步的,如果发布事件处的业务存在事务,监听器处理也会在相同的事务中。这个一定要注意!如果对于事件的处理不想受到影响,可以onApplicationEvent方法上加@Aync支持异步(参考taskExecutor的使用)。
    • 原理部分可以参考 博客 事件体系

    3. 一种更优雅的方式——@EventListener

    3.1 发布事件

           我们可以通过工具类发布来避免在代码耦合注入ApplicationContext,工具类实现ApplicationEventPublisherAware 接口,具体可参考spring的aware学习
           这里有一个小细节,如果通过注入ApplicationContext的方式来发布事件,idea在代码左边会有一个类似耳机的小图标,点击可以跳到监听此发布事件的监听者位置,用工具类发布事件就没有此提示了。
    在这里插入图片描述

    3.2 定义事件源

    public abstract class BaseEvent<T> extends ApplicationEvent {
    
        private static final long serialVersionUID = 895628808370649881L;
    
        protected T eventData;
    
        public BaseEvent(Object source, T eventData){
            super(source);
            this.eventData = eventData;
        }
    
        public BaseEvent(T eventData){
            super(eventData);
        }
        
        public T getEventData() {
            return eventData;
        }
        public void setEventData(T eventData) {
            this.eventData = eventData;
        }
    }
    
    

    需要发布的事件继承此BaseEvent

    public class FaceEvent extends BaseEvent<User> {
        
        public FaceEvent(User user) {
            super(user);
        }
    
        public FaceEvent(Object source, User user){
            super(source,user);
        }
    
    }
    

    如果代码结构较复杂,多处发布相同的事件,建议发布事件时将this作为source传递,便于通过分析日志确定发布源。

    3.3 监听事件@EventListener

           在spring4.2中我们可以以更加简洁的方式来监听event的发布,监听事件我们不必再实现ApplicationListener接口了,只要在方法上添加注解@EventListener即可:

    	@EventListener
        public void onApplicationEvent(FaceEvent event) {
            User user = (User) event.getSource();
            String name = Thread.currentThread().getName();
            LOGGER.info("===> 收到人脸事件:  {},线程名为: {}",user,name);
        }
    

    会根据方法参数类型来自动监听相应事件的发布。
           如果要监听多个事件类型的发布,可以在@EventListener(classes = {FaceEvent.class,ArmEvent.class})指定,spring会多次调用此方法来处理多个事件。但是注意此时,方法参数不能有多个,否则会发生转换异常,可以将使用多个事件的父类作为唯一的方法参数来接收处理事件,但除非必要否则并不推荐监听多个事件的发布。

    • 如果有多个监听器监听同一事件,我们可以在方法上使用spring的@order注解来定义多个监听器的顺序,如:
    
    	@EventListener
        @Order(4)
        public void onApplicationEvent(FaceEvent event) {
            User user = (User) event.getSource();
            LOGGER.info("===> A 收到人脸事件:  {}",user);
        }
    
    
        @EventListener({FaceEvent.class,ArmEvent.class})
        @Order(3)
        public void onApplicationEvent3(Object event) {
    
            if(event instanceof FaceEvent){
                LOGGER.info("===> B 收到人脸事件:  {}",((FaceEvent) event).getEventData());
            }else if(event instanceof ArmEvent){
                ArmEvent armEvent = (ArmEvent) event;
                LOGGER.info("===> B 收到臂膀事件:  {}",armEvent.getEventData());
            }
        }
    

    这真的是很方便。

    • @EventListener还有一个属性,condition()里可以使用SPEL表达式来过滤监听到事件,即只有符合某种条件的才进行接收处理。暂时还用不到。

    3.4 监听事件时的事务隔离

    • @TransactionalEventListener和@EventListener都可以监听事件,但前者可以对发布事件和监听事件进行一些事务上的隔离。@TransactionalEventListenerr指不和发布事件的方法在同一个事务内,发布事件的方法事务结束后才会执行本监听方法,监听逻辑内发生异常不会回滚发布事件方法的事务。
    
        @Transactional(rollbackFor = Exception.class)
        public void handle(){
            User user = new User();
            user.setAge(34);
            user.setUsername("人脸事件");
            user.setHobby("抓拍");
    
            //处理完上面的逻辑后,发布事件
            EventPublisherUtil.publishEvent(new FaceEvent(user));
     
            //数据库添加操作
            Integer integer = deviceAlarmService.addDevice();
        }
    

    可以看到发布事件的方法处在事务控制中,我们使用@TransactionalEventListener来监听事件:

    
    	@TransactionalEventListener(fallbackExecution = true)
        public void onApplicationEvent(FaceEvent event) {
            User user = event.getEventData();
            LOGGER.info("===> A 收到人脸事件:  {}}",user);
    
            //@TransactionalEventListener指不和发布事件的在同一个事务内,发布事件的方法事务结束后才会执行本方法
           // ,本方法发生异常不会回滚发布事件的事务,
            throw new  RuntimeException("监听事件抛出异常");
        }
    

    运行结果,addDevice正常在数据库插入数据,但是修改为@EventListener监听则插入数据失败。

    • @TransactionalEventListener有一个属性为fallbackExecution,默认为false,指发布事件的方法没有事务控制时,监听器不进行监听事件,此为默认情况! fallbackExecution=true,则指发布事件的方法没有事务控制时,监听方法仍可以监听事件进行处理。
    	/**
    	 * Whether the event should be processed if no transaction is running.
    	 */
    	boolean fallbackExecution() default false;
    
    • 刚才我们说到使用@TransactionalEventListener会在发布事件的方法事务结束后执行监听方法,但其实我们还可以进行细化的控制。它有一个属性为TransactionPhase,默认为TransactionPhase.AFTER_COMMIT,即事务提交后。还可以根据需要选择AFTER_COMPLETION、BEFORE_COMMIT、AFTER_ROLLBACK。
             但仍需注意,如果fallbackExecution=false,且发布事件的方法没有事务控制时,监听器根本不会监听到事件,此处的TransactionPhase也就没有意义了。
  • 相关阅读:
    Atitit attilax要工作研究的要素 纪要 方案 趋势 方向 概念 理论
    Atitit 常见每日流程日程日常工作.docx v7 r8f
    Atitit it 互联网 软件牛人的博客列表
    Atitit 信息链(Information Chain)的概念理解 attilax总结
    Atitit 知识点的体系化 框架与方法 如何了解 看待xxx
    Atitit 聚合搜索多个微博 attilax总结
    Atitit 企业知识管理PKM与PIM
    Atitit 项目沟通管理 Atitit 沟通之道 attilax著.docx
    Atitit 项目管理软件 在线服务 attilax总结 1. 项目管理协作的历史 1 1.1. Worktile 406k 1 1.2. Teambition  584k in baidu
    Atitit.每周末总结 于每周一计划日程表 流程表 v8 import 上周遗漏日志补充 检查话费 检查流量情况 Crm问候 Crm表total and 问候
  • 原文地址:https://www.cnblogs.com/seasail/p/12179402.html
Copyright © 2020-2023  润新知