IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> Java知识库 -> 观察者模式在项目中的应用 -> 正文阅读

[Java知识库]观察者模式在项目中的应用

标题观察者模式在项目中的应用

观察者模式在 Java 语言中的地位非常重要。在 JDK 的 java.util 包中,提供了 Observable 类以及 Observer 接口,它们构成了 JDK 对观察者模式的支持。但是,在 Java9 被弃用了。 另外我们在JAVA应用中都会大量使用spring框架,在spring中也提供了观察者模式的实现。Spring 事件驱动模型也是观察者模式很经典的应用。就是我们常见的项目中最常见的事件监听器。

标题理论说明

先描述一下spring中实现观察者模式所定义的几个角色:

ApplicationEvent(事件),这个类是所有事件对象的父类。
ApplicationEvent 继承自 jdk 的 EventObject, 所有的事件都需要继承 ApplicationEvent, 并且通过 source 得到事件源。大家可以深挖一下spring,它给我们提供了很多内置事件供我们去扩展,比如

ContextStartedEvent:Spring Context 启动完成事件。
ContextStoppedEvent:Spring Context 停止完成事件。
ContextClosedEvent:Spring Context 停止开始事件。
ContextRefreshedEvent:Spring Context 初始化或刷新完成事件。

ApplicationListener(监听),也就是观察者,继承自 jdk 的 EventListener,该类中只有一个方法 onApplicationEvent。当监听的事件发生后该方法会被执行。
ApplicationContext(源),ApplicationContext 是 Spring 中的核心容器,在事件监听中 ApplicationContext 可以作为事件的发布者,也就是事件源。因为 ApplicationContext 继承自 ApplicationEventPublisher。在 ApplicationEventPublisher 中定义了事件发布的方法:publishEvent(Object event)
ApplicationEventMulticaster(事件管理者),用于事件监听器的注册和事件的广播。监听器的注册就是通过它来实现的,它的作用是把 Applicationcontext 发布的 Event 广播给它的监听器列表。

场景下对比

我们举个业务场景,推送发布任务的时候到业务层之后需要注入另外3个业务层的控制类,之后进行对应的业务处理,就如下图,可以看出这个业务之间的耦合就很严重。

在这里插入图片描述
基于上边的流程我们使用观察者模式改造一下:松耦合
在这里插入图片描述
观察者模式对我们很大的一个作用,在于实现业务的解耦。来个简单的场景,以用户注册的场景来举例子,假设在用户注册完成时,需要给该用户发送邮件、发送优惠劵等等操作,如下图所示:

在这里插入图片描述

那么我们就基于这个注册发消息这个场景来举个例子,上代码:

  • 我们构建一个springboot项目,引入依赖:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.2.2.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>lab-54-demo</artifactId>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
    </dependencies>
</project>
  • 定义一个事件(此例就是用户注册事件)
package com.springboot.original.service;

import org.springframework.context.ApplicationEvent;

public class UserRegisterEvent extends ApplicationEvent {

    private String username;

    public UserRegisterEvent(Object source) {
        super(source);
    }
    public UserRegisterEvent(Object source, String username) {
        super(source);
        this.username = username;
    }

    public String getUsername() {
        return username;
    }

}
  • 定义一个具体的业务实现类UserService
@Service
public class UserService implements ApplicationEventPublisherAware {
    private Logger log = LoggerFactory.getLogger(this.getClass());
    @Autowired
    private UserMapper userMapper;
    @Resource
    RedisUtil redisUtil;

    private ApplicationEventPublisher applicationEventPublisher;

    public User selectByPrimaryKey(String id){
        return userMapper.selectByPrimaryKey(id);
    }

    public int insertUser(User user){
        return userMapper.insert(user);
    }

    public User selectByNickName(String name){
        redisUtil.set("aaa","bbb");
        return userMapper.selectByNickName(name);
    }

    public String getFromRedis(String key){
        return redisUtil.get(key)+"";
    }

    @Override
    public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
        this.applicationEventPublisher = applicationEventPublisher;
    }

    public void register(String username) {
        //注册逻辑
        System.out.println("[register][执行用户({}) 的注册逻辑] "+ username);

        //发布消息
        applicationEventPublisher.publishEvent(new UserRegisterEvent(this, username));
    }
}

  • 定义EmailService用于监听发布消息后进行发送邮件的业务处理
package com.springboot.original.service;

import org.springframework.context.ApplicationListener;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;

@Service
public class EmailService implements ApplicationListener<UserRegisterEvent> {

    @Override
    @Async
    public void onApplicationEvent(UserRegisterEvent event) { 
        System.out.println("[onApplicationEvent][给用户({}) 发送邮件]"+ event.getUsername());
    }

}

说明一下该类的作用,这个类实现 ApplicationListener 接口,通过泛型设置感兴趣的事件。此处设置为UserRegisterEvent这个事件。之后针对监听的UserRegisterEvent 事件,进行自定义处理。

  • 我们再定义个优惠劵 Service
package com.springboot.original.service;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Service;

@Service
public class CouponService {

    private Logger logger = LoggerFactory.getLogger(getClass());

    @EventListener
    public void addCoupon(UserRegisterEvent event) {
        logger.info("[addCoupon][给用户({}) 发放优惠劵]", event.getUsername());
    }

}

注意添加 该类中方法添加了@EventListener 注解,其实这个是另一种写法而已,其本质是跟发送邮件的service含义是一样的,通过增加注解标识该方法正在监听事件为 UserRegisterEvent的事件。

  • 我们测试一下从controller调用正常的注册方法,会触发对应的邮件监听事件和优惠券监听事件

在这里插入图片描述

各个监听顺序控制

如果有多个业务实现类进行监听同一个事件并且需要在不同的业务实现类进行顺序控制那么我们就有两种情况:

1、采用注解的方式进行监听,那么order注解就要配置在标记监听注解的方法上

在这里插入图片描述
2、采用实现类实现其方法进行监听时,那么order注解就要配置在业务实现类上
在这里插入图片描述
@Order注解值越小越先加载

观察者模式与发布订阅模式

对于观察者模式,我们仅仅维护一个可观察者对象即可,即一个 Observable 实例,当有数据变更时,它只需维护一套观察者(Observer)的集合,这些 Observer 实现相同的接口,Subject 只需要知道,通知 Observer 时,需要调用哪个统一方法就好了。如下图:
在这里插入图片描述

对于发布订阅模式更像一个MQ中间件
在这里插入图片描述
与观察者模式不一样,发布订阅模式里,发布者和订阅者,不是松耦合,而是完全解耦的。观察者模式里,只有两个角色 —— 观察者 + 被观察者,而发布订阅模式里,却不仅仅只有发布者和订阅者两个角色,还有一个管理并执行消息队列的“中间者”。

  Java知识库 最新文章
计算距离春节还有多长时间
系统开发系列 之WebService(spring框架+ma
springBoot+Cache(自定义有效时间配置)
SpringBoot整合mybatis实现增删改查、分页查
spring教程
SpringBoot+Vue实现美食交流网站的设计与实
虚拟机内存结构以及虚拟机中销毁和新建对象
SpringMVC---原理
小李同学: Java如何按多个字段分组
打印票据--java
上一篇文章      下一篇文章      查看所有文章
加:2021-07-23 10:35:34  更:2021-07-23 10:36:40 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/17 20:18:25-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码