• 观察者模式实际应用场景「扩展点实战系列」- 第439篇


    历史文章(累计400+篇文章)

    国内最全的Spring Boot系列之一

    国内最全的Spring Boot系列之二

    国内最全的Spring Boot系列之三

    国内最全的Spring Boot系列之四

    国内最全的Spring Boot系列之五》

    扩展点系列之ApplicationContextAwareProcessor普通类获取Spring Bean - 第433篇

    扩展点系列之初始化之@PostConstruct、init-method、InitializingBean - 第434篇

    SpringBoot/Spring扩展点系列之FactoryBean让你不在懵逼 - 第435篇

    SpringBoot/Spring扩展点系列之SmartInitializingSingleton - 第436篇

    扩展点系列之CommandLineRunner和ApplicationRunner实现缓存预热 - 第437篇

    SpringBoot/Spring扩展点系列之初始化和销毁的3种办法 - 第438篇

    悟纤:师傅,我有一个重大的发现。

    师傅:徒儿,这是发现了什么新大陆。

    悟纤:我发现最近一段时间,特别的专注,一不小心就达到进入了心流的状态了。

    师傅:徒儿,那你觉得,你最近为什么会这么专注呐。

    悟纤:我想想了,那是因为我的工作被限定了时间节点,必须在下周周五之前完成,否则我将面临无法工作无法转正的问题。

    师傅:不错,设置截止时间,是可以让自己很专注的一种方式。

    师傅:还有一种方式,就是威胁:当我们自身面临威胁的时候,我们会瞬间聚集注意力。比如说:你在密室逃脱的时候,你当下注意力是很集中的,否则你会被吓到;所以当你心烦意外,老是想东想西的时候,去刺激一下大脑还是不错的,核心还是要能转移注意力,让你能够专注到当下。

    悟纤:今天真开心,又新学习到了一个技能。

    师傅:心态很重要,当你畏惧失败时,结局就已经注定,所以不管面对什么样的困难,不要害怕,尽管去做,竭尽你的所能,去战胜心里的那个巨人。

    悟纤:听师傅一席话,胜读百年书。

    导读

    这一节主要看下ApplicationListener。

    准确的说,这个应该不算spring&springboot当中的一个扩展点,ApplicationListener可以监听某个事件的event,触发时机可以穿插在业务方法执行过程中,用户可以自定义某个业务事件。

    在写篇文章的时候,发展之前在写ApplicationEvent的时候已经写过了。

    Spring Boot使用ApplicationEvent来实现事件发布订阅功能(美女一个都不能少,都要通知到) - 第420篇

    https://mp.weixin.qq.com/s/YU9McjrV1VS2Kas96s9UCg

    所以这篇文章主要以实战的方式进行讲解,当然在讲解之前基础知识,还是会介绍的。

    一、ApplicationListener概述

    接下来我们首先来看看ApplicationListener的基本概念、一些内置的事件以及使用场景。

    1.1 是什么?

    ApplicationListener可以监听某个事件的event,触发时机可以穿插在业务方法执行过程中,用户可以自定义某个业务事件。但是spring内部也有一些内置事件,这种事件,可以穿插在启动调用中。我们也可以利用这个特性,来自己做一些内置事件的监听器来达到和前面一些触发点大致相同的事情。

    1.2 内置事件

    Spring内部也有一些内置事件,这种事件,可以穿插在启动调用中。我们也可以利用这个特性,来自己做一些内置事件的监听器来达到和前面一些触发点大致相同的事情。

    接下来罗列下spring主要的内置事件,如下图所示:

    (1)ContextRefreshedEvent

    ApplicationContext 被初始化或刷新时,该事件被发布。这也可以在 ConfigurableApplicationContext接口中使用refresh() 方法来发生。此处的初始化是指:所有的Bean被成功装载,后处理Bean被检测并激活,所有Singleton Bean 被预实例化,ApplicationContext容器已就绪可用。

    (2)ContextStartedEvent

    当使用ConfigurableApplicationContext (ApplicationContext子接口)接口中的 start() 方法启动 ApplicationContext 时,该事件被发布。你可以调查你的数据库,或者你可以在接受到这个事件后重启任何停止的应用程序。

    (3)ContextStoppedEvent

    当使用ConfigurableApplicationContext 接口中的 stop() 停止 ApplicationContext 时,发布这个事件。你可以在接受到这个事件后做必要的清理的工作

    (4)ContextClosedEvent

    当使用ConfigurableApplicationContext接口中的 close()方法关闭 ApplicationContext 时,该事件被发布。一个已关闭的上下文到达生命周期末端;它不能被刷新或重启

    (5)RequestHandledEvent

    这是一个web-specific 事件,告诉所有 bean HTTP 请求已经被服务。只能应用于使用DispatcherServlet的Web应用。在使用Spring作为前端的MVC控制器时,当Spring处理用户请求结束后,系统会自动触发该事件

    1.3 使用场景

    发布订阅/观察者模型,实现业务之间的解耦。

    (1)服务信息上报。

    结合BeanPostProcessor收集自定义注解,将收集到的接口信息提交到其他服务。

    (2)业务中需要某个方法仅在启动时执行一次。

    在一些业务场景中,当容器初始化完成之后,需要处理一些操作,比如一些数据的加载、初始化缓存、特定任务的注册等等。这个时候我们就可以使用Spring提供的ApplicationListener来进行操作。

    在Spring中InitializingBean接口也提供了类似的功能,只不过它进行操作的时机是在所有bean都被实例化之后才进行调用。根据不同的业务场景和需求,可选择不同的方案来实现。

    (3)使用ApplicationListener 容器监听器来记录请求信息。

    一般我们记录请求信息,可以用 AOP, SpringMVC 拦截器,过滤器等其他相关的。。。现在也可以使用 监听器的 方式来记录了。

    (4)实现ApplicationListener接口监听项目运行状态

    在使用SpringMV或者SpringBoot框架做项目开发时,有时候需要项目在某个运行状态时,去做某些事情,如:项目启动时初始化一些数据,或者启动一些定时任务;项目关闭时,停止定时任务等等这样的功能,这时候就需要监听项目的运行状态,可以实现ApplicationListener接口实现监听功能

    二、ApplicationListener的使用

    第一种情况就是直接实现接口ApplicationListener,进行事件的监听处理。这种方式使用起来很简单。

    第二种情况就是配合ApplicationEvent进行使用。

    接下来我们主要是来介绍下第二种情况的使用。

    1>  ApplicationEvent是什么?

    ApplicationEvent以及Listener是Spring为我们提供的一个事件监听、订阅的实现,内部实现原理是观察者设计模式,设计初衷也是为了系统业务逻辑之间的解耦,提高可扩展性以及可维护性。

    2>  Spring事件发布监听机制

    流程:当事件源(发布者)发布事件时,相应监听此事件的监听者接收到事件对象并且进行处理Spring的事件发布监听机制本质上就是发布-订阅,即生产者-消费者,也体现了设计模式中的观察者模式。

    3>  ApplicationEvent使用步骤

    (1)事件(ApplicationEvent):创建ApplicationEvent事件

    (2)事件发布者(ApplicationEventPublisher):事件发布

    (3)事件监听者(ApplicationListener):创建ApplicationListener事件监听

    辅助理解:我们有一个事件要发布,那么就要有一个发布者的角色,那么事件发布了,被谁处理或者说被谁监听呐,那么就需要监听者的角色。所以:

    事件发布者(ApplicationEventPublisher)发布了一个事件(ApplicationEvent)被某些事件监听者(ApplicationListener)监听处理。

    (1)如何定义事件:继承(extends)类ApplicationEvent。

    (2)如何发布事件:使用applicationContext的publishEvent进行发布事件。比如:

    applicationContext.publishEvent(newMessageUpdateApplicationEvent(message));

    (3)如何监听事件:实现(implements)接口ApplicationListener的onApplicationEvent()方法。

    三、ApplicationListener实战案例

    业务场景描述:在很多平台订单创建成功之后,会给业务操作者发送一条短信进行提醒。

    针对这样的业务场景,常规下我们会怎么编码以及使用了ApplicationListener会怎么编码。

    3.1 基本业务场景代码搭建

    首先我们根据常规搭建一套基本的代码结构。

    3.1.1 新建一个SpringBoot项目

    使用idea工具新建Spring Boot项目,取名为:spring-boot-applicationlistener-example,选择依赖Spring Web,或者手动在pom.xml文件进行添加:

    spring-boot-starter-web。

    整个pom.xml文件如下:

    "http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">    <modelVersion>4.0.0modelVersion>    <parent>        <groupId>org.springframework.bootgroupId>        <artifactId>spring-boot-starter-parentartifactId>        <version>2.7.0version>        <relativePath/>     parent>    <groupId>com.kfitgroupId>    <artifactId>spring-boot-applicationlistener-exampleartifactId>    <version>0.0.1-SNAPSHOTversion>    <name>spring-boot-applicationlistener-examplename>    <description>Demo project for Spring Bootdescription>    <properties>        <java.version>1.8java.version>    properties>    <dependencies>        <dependency>            <groupId>org.springframework.bootgroupId>            <artifactId>spring-boot-starter-webartifactId>        dependency>        <dependency>            <groupId>org.springframework.bootgroupId>            <artifactId>spring-boot-starter-testartifactId>            <scope>testscope>        dependency>    dependencies>    <build>        <plugins>            <plugin>                <groupId>org.springframework.bootgroupId>                <artifactId>spring-boot-maven-pluginartifactId>            plugin>        plugins>    build>project>

    3.1.2 创建订单服务

    构建一个订单服务,主要功能是创建订单,并且发送短信。

    package com.kfit.order.service;import org.springframework.stereotype.Service;/** * 订单服务 * * @author 悟纤「公众号SpringBoot」 * @date 2022-08-15 * @slogan 大道至简 悟在天成 */@Servicepublic class OrderService {    /**     * 创建订单.     */    public void createOrder(){        //1. 创建订单: 生成订单信息,然后保存到数据库.        System.out.println("创建订单 - 生成订单信息,然后保存到数据库");        //2. 发送短信: 调用短信服务,给手机号发送短信信息.        System.out.println("发送短信 - 调用短信服务,给手机号发送短信信息");    }}

    3.1.3 使用controller进行调用

    编写一个controller调用订单服务,方便测试:

    package com.kfit.order.controller;import com.kfit.order.service.OrderService;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.stereotype.RestController;import org.springframework.web.bind.annotation.RequestMapping;/** * 订单的Controller * * @author 悟纤「公众号SpringBoot」 * @date 2022-08-15 * @slogan 大道至简 悟在天成 */@RestControllerpublic class OrderController {    @Autowired    private OrderService orderService;    @RequestMapping("/createOrder")    public  String createOrder(){        orderService.createOrder();        return "创建成功!";    }}

    3.1.4 测试

    启动应用进行测试,访问地址如下地址:

    http://127.0.0.1:8080/createOrder

    3.2 业务升级迭代

    现有新需求:需要加一个微信通知的功能,常规做法会在OrderService进行添加微信通知的代码逻辑,如下所示:

    package com.kfit.order.service;import org.springframework.stereotype.Service;/** * 订单服务 * * @author 悟纤「公众号SpringBoot」 * @date 2022-08-15 * @slogan 大道至简 悟在天成 */@Servicepublic class OrderService {    /**     * 创建订单.     */    public void createOrder(){        //1. 创建订单: 生成订单信息,然后保存到数据库.        System.out.println("创建订单 - 生成订单信息,然后保存到数据库");        //2. 发送短信: 调用短信服务,给手机号发送短信信息.        System.out.println("发送短信 - 调用短信服务,给手机号发送短信信息");        //3. 发送微信 - 调用微信公众号的通知服务,进行发送。        System.out.println("发送微信 - 调用微信公众号的通知服务,进行发送");    }}

    存在问题:每次创建订单需要加新功能(如新的通知方式),则要修改原有的类,难以维护,违背设计模式的原则:

    (1)单一职责:订单保存功能,夹杂了消息通知这些功能

    (2)开闭原则:对拓展开放,对修改关闭

    优化方案:使用观察者模式,使创建订单和消息通知进行分离,低耦合。可以选择消息队列,Spring事件机制等,本文选择Spring事件机制。

    3.3 优化代码

    使用事件机制的,那么需要先定义订单的事件,然后使用监听器对事件进行监听,最后就是在创建订单的时候进行事件的发布。

    3.3.1 创建事件

    构建OrderCreateEvent事件,继承接口ApplicationEvent:

    package com.kfit.order.order.event;import org.springframework.context.ApplicationEvent;/** * 订单创建事件 * * @author 悟纤「公众号SpringBoot」 * @date 2022-08-15 * @slogan 大道至简 悟在天成 */public class OrderCreateEvent extends ApplicationEvent {    private String orderInfo;//订单信息    public OrderCreateEvent(Object source,String orderInfo){        super(source);        this.orderInfo = orderInfo;    }    public String getOrderInfo() {        return orderInfo;    }    public void setOrderInfo(String orderInfo) {        this.orderInfo = orderInfo;    }}

    3.3.2 事件监听器

    事件监听,主要是短信发送监听和微信发送监听。

    SmsListener:

    package com.kfit.order.listener;import com.kfit.order.order.event.OrderCreateEvent;import org.springframework.context.ApplicationListener;import org.springframework.stereotype.Component;/** *  OrderCreateEvent的短信监听. * * @author 悟纤「公众号SpringBoot」 * @date 2022-08-15 * @slogan 大道至简 悟在天成 */@Componentpublic class SmsListener implements ApplicationListener<OrderCreateEvent> {    @Override    public void onApplicationEvent(OrderCreateEvent event) {        //. 发送短信: 调用短信服务,给手机号发送短信信息.        System.out.println("发送短信 - 调用短信服务,给手机号发送短信信息;订单信息:"+event.getOrderInfo());    }}

    WechatListener:

    package com.kfit.order.listener;import com.kfit.order.order.event.OrderCreateEvent;import org.springframework.context.ApplicationListener;import org.springframework.stereotype.Component;/** *  OrderCreateEvent的短信监听. * * @author 悟纤「公众号SpringBoot」 * @date 2022-08-15 * @slogan 大道至简 悟在天成 */@Componentpublic class WechatListener implements ApplicationListener<OrderCreateEvent> {    @Override    public void onApplicationEvent(OrderCreateEvent event) {        //. 发送微信 - 调用微信公众号的通知服务,进行发送。        System.out.println("发送微信 - 调用微信公众号的通知服务,进行发送;订单信息:"+event.getOrderInfo());    }}

    3.3.3 事件发布

    事件发布可以使用ApplicationContext或者ApplicationEventPublisher的publishEvent方法进行发布,修改一下OrderService的代码:

    package com.kfit.order.service;import com.kfit.order.order.event.OrderCreateEvent;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.context.ApplicationContext;import org.springframework.context.ApplicationEventPublisher;import org.springframework.stereotype.Service;/** * 订单服务 * * @author 悟纤「公众号SpringBoot」 * @date 2022-08-15 * @slogan 大道至简 悟在天成 */@Servicepublic class OrderService {    @Autowired    private ApplicationContext applicationContext;    @Autowired    private ApplicationEventPublisher applicationEventPublisher;    /**     * 创建订单.     */    public void createOrder(){        //1. 创建订单: 生成订单信息,然后保存到数据库.        System.out.println("创建订单 - 生成订单信息,然后保存到数据库");        //2. 发布事件        OrderCreateEvent orderCreateEvent = new OrderCreateEvent(this,"orderNo:20220815");        applicationContext.publishEvent(orderCreateEvent);        //applicationEventPublisher.publishEvent(orderCreateEvent);//也可以    }}

    3.3.4 测试

    重新启动运用进行测试一下,访问地址如下地址:

    http://127.0.0.1:8080/createOrder

    若要先执行发送微信的监听的话,在类上添加@Order注解即可,值越小越执行,也就是@Order(1)会在@Order(2)之前执行。

    1. 我就是我,是颜色不一样的烟火。
    2. 我就是我,是与众不同的小苹果。

    à悟空学院:https://t.cn/Rg3fKJD

    学院中有Spring Boot相关的课程!点击「阅读原文」进行查看!

    SpringBoot视频:http://t.cn/A6ZagYTi

    SpringBoot交流平台:https://t.cn/R3QDhU0

    SpringSecurity5.0视频:http://t.cn/A6ZadMBe

    ShardingJDBC分库分表:http://t.cn/A6ZarrqS

    分布式事务解决方案:http://t.cn/A6ZaBnIr

    JVM内存模型调优实战:http://t.cn/A6wWMVqG

    Spring入门到精通:https://t.cn/A6bFcDh4

    大话设计模式之爱你:https://dwz.cn/wqO0MAy7

  • 相关阅读:
    部署Django报错-requires SQLite 3.8.3 or higher
    复旦-华盛顿大学EMBA 二十年20人丨林劲:对自己多一些“标准”
    1、shell脚本中的命令详解
    删除 “显示不存在的文件夹” 的文件夹
    mybatis-plus使用总结
    全网去水印独立版带解析接口服务器打包带前端2.3版本(美化UI)
    【linux命令讲解大全】033.Linux常用命令之atrm、colrm和hdparm
    关于排序中最少交换次数的证明(置换环)
    【微服务】七. http客户端Feign
    SpringAMQP WorkQueue消息队列模型的理解与使用
  • 原文地址:https://blog.csdn.net/linxingliang/article/details/126350755