• Spring Event 观察者模式, 业务解耦神器


    观察者模式在实际开发过程中是非常常见的一种设计模式。

    Spring Event的原理就是观察者模式,只不过有Spring的加持,让我们更加方便的使用这一设计模式。

    一、什么是观察者模式

    概念: 观察者模式又叫发布-订阅模式。

    发布指的是当目标对象的状态改变时,它就向它所有的观察者对象发布状态更改的消息,以让这些观察者对象知晓。

    举例:

    网上有一个非常符合观察者模式的例子

    当温度有变化,对应的仪表盘也会跟着变化。

    一个仪表盘可以当作一个观察者,去掉一个仪表盘或者新增一个仪表盘跟目标对象(温度)是解耦的,不是强绑定关系。

    一句话:感知变化,相应变化


    二、观察者模式 VS 责任链模式

    这两种设计模式是有相似的地方,但其实有很大的区别。

    我们先来看相似的点,就好比上面的这个例子,我们是不是也可以用责任链模式来实现?

    当然可以了。

    当温度变化了,一条一条链路的执行下去就是了。

    当然如果是我,这个功能在选择设计模式的时候,我还是会选择使用观察者模式。

    1、区别

    我个人认为主要有四点区别:

    第一点:我们也会称观察者模式为发布订阅模式,作为订阅者来讲,每个订阅者是平级的,也就是每个观察者对象是平级的,但责任链可以有先后次序。

    比如我们在电商场景中,有个电商活动,这个商品需要先走 包邮活动->满减送->会员折扣活动->积分抵扣活动。

    这个责任链的顺序不同会导致最终优惠的价格不同。

    第二点:所有观察者一般接收统一参数,但责任链获取的参数可能是上一个链路已经处理完成的

    就好比上面的电商活动,会员折扣活动计算后的价格,还会传入到积分抵扣活动中。

    第三点:观察者的对象都会执行,但责任链这我们可以在得到满意结果直接返回。

    比如我想查一个份数据,这个数据可以先从A -> B -> C,三个接口获得。只要返回数据,这个链路就不用往下走了。

    第四点:观察者模式可以做异步操作,我们说的MQ发布订阅模式,就是完全异步,但是责任链不太适合走异步。


    三、代码示例

    1、观察者模式有哪些角色

    抽象被观察者: 定义了一个接口,包含了注册观察者、删除观察者、通知观察者等方法。

    具体被观察者: 实现了抽象被观察者接口,维护了一个观察者列表,并在状态发生改变时通知所有注册的观察者。

    抽象观察者: 定义了一个接口,包含了更新状态的方法。

    具体观察者: 实现了抽象观察者接口,在被观察者状态发生改变时进行相应的处理。

    1. 抽象被观察者
    /**
     * 抽象被观察者
     */
    public interface ISubject {
    
        /**
         * 新增观察者
         */
        boolean attach(IObserver observer);
    
        /**
         * 删除观察者
         */
        boolean detach(IObserver observer);
    
        /**
         * 通知观察者
         */
        void notify(String event);
    }
    
    1. 抽象观察者
    /**
     *  抽象观察者
     */
    public interface IObserver {
       
        /**
         * 观察者所执行方法
         */
        void update(String event);
    }
    
    1. 具体被观察者
    /**
     *  具体被观察者
     */
    public class ConcreteSubject implements ISubject {
    
        private List observers = new ArrayList<>();
    
        @Override
        public boolean attach(IObserver observer) {
            return this.observers.add(observer);
        }
    
        @Override
        public boolean detach(IObserver observer) {
            return this.observers.remove(observer);
        }
    
        @Override
        public void notify(String event) {
            System.out.println("被观察者: 数据变更 = " + event);
            for (IObserver observer : this.observers) {
                 observer.update(event);
            }
        }
    }
    
    1. 具体观察者
    /**
     * 具体观察者
     */
    public class ConcreteObserver implements IObserver {
    
        @Override
        public void update(String event) {
            System.out.println("观察者: 收到被观察者的温度变动: " + event);
        }
    }
    
    1. 测试
    /**
     *  测试
     */
    public class ClientTest {
        
        public static void main(String[] args) {
            // 被观察者
            ISubject subject = new ConcreteSubject();
            // 观察者
            IObserver observer = new ConcreteObserver();
            // 将观察者注册
            subject.attach(observer);
            // 被观察者通知观察者
            subject.notify("温度从6变到7");
        }
    }
    

    运行结果

    被观察者: 数据变更 = 温度从6变到7
    观察者: 收到被观察者的温度变动: 温度从6变到7
    

    当然上面这种模式也太傻了吧,下面就通过Spring Event实现观察者模式,非常方便。


    四、Spring Event 实现观察者模式

    Spring 基于观察者模式实现了自身的事件机制,由三部分组成:

    事件 ApplicationEvent: 通过继承它,实现自定义事件。

    事件发布者 ApplicationEventPublisher: 通过它,可以进行事件的发布。

    事件监听器 ApplicationListener: 通过实现它,进行指定类型的事件的监听。

    这里以下面案例实现,当一个用户出现欠费,那么通过观察者模式通过 短信通知,邮箱通知,微信通知,到具体用户

    1、事件 UserArrearsEvent

    继承 ApplicationEvent 类,用户欠费事件。

    /**
     * 用户欠费事件 继承ApplicationEvent
     */
    public class UserArrearsEvent extends ApplicationEvent {
    
        /**
         * 用户名
         */
        private String username;
        
        public UserArrearsEvent(Object source, String username) {
            super(source);
            this.username = username;
        }
    
        public String getUsername() {
            return username;
        }
    }
    

    2、被观察者 UserArrearsService

    /**
     *  被观察者 实现ApplicationEventPublisherAware 接口
     */
    @Service
    public class UserArrearsService implements ApplicationEventPublisherAware {
    
        private Logger logger = LoggerFactory.getLogger(getClass());
    
        private ApplicationEventPublisher applicationEventPublisher;
    
        @Override
        public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
            this.applicationEventPublisher = applicationEventPublisher;
        }
    
        public void arrears(String username) {
            // 执行欠费逻辑
            logger.info("被观察者 用户欠费,用户名称", username);
            // 发布
            applicationEventPublisher.publishEvent(new UserArrearsEvent(this, username));
        }
    
    }
    
    1. 实现 ApplicationEventPublisherAware 接口,从而将 ApplicationEventPublisher 注入到其中。

    2. 在执行完注册逻辑后,调用 ApplicationEventPublisher的 publishEvent 方法,发布 UserArrearsEvent 事件。

    3、观察者 EmailService

    /**
     *  观察者 邮箱欠费通知
     */
    @Service
    public class EmailService implements ApplicationListener {
    
        private Logger logger = LoggerFactory.getLogger(getClass());
    
        @Override
        @Async
        public void onApplicationEvent(UserArrearsEvent event) {
            logger.info("邮箱欠费通知,你好 {} ,请尽快缴费啊啊啊啊!", event.getUsername());
        }
    }
    
    1. 实现 ApplicationListener 接口,通过 E 泛型设置感兴趣的事件。

    2. 实现 onApplicationEvent方法,针对监听的 UserRegisterEvent 事件,进行自定义处理。

    3. 设置 @Async 注解,那就代表走异步操作。同时需要在启动类上添加@EnableAsync,这样异步才生效。

    4、观察者 SmsService

    /**
     *  短信欠费通知
     */
    @Service
    public class SmsService {
    
        private Logger logger = LoggerFactory.getLogger(getClass());
    
        @EventListener
        public void smsArrears(UserArrearsEvent event) {
            logger.info("短信欠费通知,你好 {} ,请尽快缴费啊啊啊啊!", event.getUsername());
        }
    }
    

    这里提供另一种方式,就是在方法上,添加 @EventListener 注解,并设置监听的事件为 UserRegisterEvent。

    5、接口测试

    /**
     *  测试 Sping Event观察者模式
     */
    @RestController
    @RequestMapping("/test")
    public class DemoController {
    
        @Autowired
        private UserArrearsService userArrearsService;
    
        @GetMapping("/arrears")
        public String arrears(String username) {
            userArrearsService.arrears(username);
            return "成功";
        }
    }
    

    日志输出

    被观察者 用户欠费,用户名称 = 张老三
    短信欠费通知,你好 张老三 ,请尽快缴费啊啊啊啊!
    邮箱欠费通知,你好 张老三 ,请尽快缴费啊啊啊啊!
    

    成功!

    GitHub地址: https://github.com/yudiandemingzi/spring-boot-study



    声明: 公众号如需转载该篇文章,发表文章的头部一定要 告知是转至公众号: 后端元宇宙。同时也可以问本人要markdown原稿和原图片。其它情况一律禁止转载!

  • 相关阅读:
    C++ 冒泡排序 Bubble Sort
    【前端】常用属性及实例
    大数据:脚本实现WordCount,结果以压缩格式输出到HDFS
    多语言在线客服系统源码-自动识别中英环境-私有化部署完美支持跨境电商网站...
    【Jmeter】安装配置:Jmeter 下载 MySQL JDBC 驱动
    1. CSS 选择器及其优先级?
    Java中的Collections类[80]
    打包Qt程序,自动添加依赖的库和文件(详细步骤)
    php将单引号和双引号替换为空字符串
    接口自动化测试yaml+requests+allure技术,你学会了吗?
  • 原文地址:https://www.cnblogs.com/qdhxhz/p/17687648.html