提升代码可读性与可维护性:利用责任链模式优化你的Spring Boot代码

news2025/1/12 16:11:13

1. 基本介绍

责任链是一种非常常见的设计模式, 具体我就不介绍了, 本文是讲解如何在SpringBoot中优雅的使用责任链模式

1.1. 代码执行流程

image-20230829201630365

基本步骤如下 :

  1. SpringBoot启动时, 需要获取 handler 对应Bean, 不同业务对应着不同的多个处理器, 比如 购票业务, 可能需要检查参数是否为空, 检测参数是否合法, 检测是否重复购票等等, 所以需要一个 mark 用于标记当前业务, 这样才能把相同的handler放到一起
  2. 然后就是通过 mark 将不同的handler 放到一起, 具体查看 3.7 核心加载类
  3. 然后实现一个方法, 通过传入 mark 和 参数去批量执行 对应部分的代码

2. 项目创建

2.1. 项目结构

img

2.2. maven

<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.3.5.RELEASE</version>
    <relativePath/> <!-- lookup parent from repository -->
  </parent>
  <groupId>cn.knightzz</groupId>
  <artifactId>chain-responsibility-pattern-example</artifactId>
  <version>0.0.1-SNAPSHOT</version>
  <name>chain-responsibility-pattern-example</name>
  <description>责任链模式demo</description>
  <properties>
    <java.version>1.8</java.version>
  </properties>
  <dependencies>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-web</artifactId>
    </dependency>

    <dependency>
      <groupId>org.projectlombok</groupId>
      <artifactId>lombok</artifactId>
      <optional>true</optional>
    </dependency>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-test</artifactId>
      <scope>test</scope>
    </dependency>
  </dependencies>

  <build>
    <plugins>
      <plugin>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-maven-plugin</artifactId>
      </plugin>
    </plugins>
  </build>

</project>

3. 代码编写

3.1. 实体类

这个类是用于存储实体类的

package cn.knightzz.pattern.dto.req;

/**
 * @author 王天赐
 * @title: PurchaseTicketReqDTO
 * @description:
 * @create: 2023-08-29 18:09
 */
public class PurchaseTicketReqDTO {

}

3.2. 枚举类

package cn.knightzz.pattern.common.enums;

/**
 * @author 王天赐
 * @title: TicketChainMarkEnum
 * @description: 存储标记责任链的注解
 * @create: 2023-08-29 18:10
 */
public enum TicketChainMarkEnum {

    /**
     * 用于标记购票的责任链过滤器
     */
    TRAIN_PURCHASE_TICKET_FILTER("train_purchase_ticket_filter");

    private String name;

    TicketChainMarkEnum(String name) {
        this.name = name;
    }
}

枚举类主要是用于标记某一类业务的责任链

3.3. 通用类

package cn.knightzz.pattern.context;

import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;

import java.lang.annotation.Annotation;
import java.util.Map;

/**
 * @author 王天赐
 * @title: ApplicationContextHolder
 * @description:
 * @create: 2023-08-29 18:31
 */
@Component
public class ApplicationContextHolder implements ApplicationContextAware {

    private static ApplicationContext CONTEXT;

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        ApplicationContextHolder.CONTEXT = applicationContext;
    }

    /**
     * Get ioc container bean by type.
     *
     * @param clazz
     * @param <T>
     * @return
     */
    public static <T> T getBean(Class<T> clazz) {
        return CONTEXT.getBean(clazz);
    }

    /**
     * Get ioc container bean by name and type.
     *
     * @param name
     * @param clazz
     * @param <T>
     * @return
     */
    public static <T> T getBean(String name, Class<T> clazz) {
        return CONTEXT.getBean(name, clazz);
    }

    /**
     * Get a set of ioc container beans by type.
     *
     * @param clazz
     * @param <T>
     * @return
     */
    public static <T> Map<String, T> getBeansOfType(Class<T> clazz) {
        return CONTEXT.getBeansOfType(clazz);
    }

    /**
     * Find whether the bean has annotations.
     *
     * @param beanName
     * @param annotationType
     * @param <A>
     * @return
     */
    public static <A extends Annotation> A findAnnotationOnBean(String beanName, Class<A> annotationType) {
        return CONTEXT.findAnnotationOnBean(beanName, annotationType);
    }

    /**
     * Get ApplicationContext.
     *
     * @return
     */
    public static ApplicationContext getInstance() {
        return CONTEXT;
    }
}

ApplicationContextHolder 的作用是, 当Bean被创建时, 将Spring中存储Bean的容器注入到CONTEXT中, 这样我们就可以在其他类中使用 Bean

3.4. 通用责任链接口

package cn.knightzz.pattern.chain;

import cn.knightzz.pattern.common.enums.TicketChainMarkEnum;
import org.springframework.core.Ordered;

/**
 * @author 王天赐
 * @title: AbstractChainHandler
 * @description:
 * @create: 2023-08-29 18:15
 */
public interface AbstractChainHandler<T> extends Ordered {

    /**
     * 执行责任链逻辑
     *
     * @param requestParam 责任链执行入参
     */
    void handler(T requestParam);

    /**
     * @return 责任链组件标识
     */
    String mark();
}

3.5. 购票责任链接口

package cn.knightzz.pattern.filter;

import cn.knightzz.pattern.chain.AbstractChainHandler;
import cn.knightzz.pattern.common.enums.TicketChainMarkEnum;
import cn.knightzz.pattern.dto.req.PurchaseTicketReqDTO;

/**
 * @author 王天赐
 * @title: TrainPurchaseTicketChainFilter
 * @description:
 * @create: 2023-08-29 18:10
 */
public interface TrainPurchaseTicketChainFilter<T extends PurchaseTicketReqDTO> extends AbstractChainHandler<PurchaseTicketReqDTO> {

    @Override
    default String mark() {
        return TicketChainMarkEnum.TRAIN_PURCHASE_TICKET_FILTER.name();
    }
}

通过实现通过责任链接口, 编写默认 mark 方法, 用于标记当前责任链处理器集合

3.6. 购票责任链处理器

package cn.knightzz.pattern.filter.handler;

import cn.knightzz.pattern.dto.req.PurchaseTicketReqDTO;
import cn.knightzz.pattern.filter.TrainPurchaseTicketChainFilter;
import org.springframework.stereotype.Component;

/**
 * @author 王天赐
 * @title: TrainPurchaseTicketParamNotNullChainHandler
 * @description:
 * @create: 2023-08-29 18:18
 */
@Component
public class TrainPurchaseTicketParamNotNullChainHandler implements TrainPurchaseTicketChainFilter<PurchaseTicketReqDTO> {

    @Override
    public void handler(PurchaseTicketReqDTO requestParam) {
        System.out.println("参数不能为空 , 过滤器执行成功");
    }

    @Override
    public int getOrder() {
        return 10;
    }
}
package cn.knightzz.pattern.filter.handler;

import cn.knightzz.pattern.dto.req.PurchaseTicketReqDTO;
import cn.knightzz.pattern.filter.TrainPurchaseTicketChainFilter;
import org.springframework.stereotype.Component;

/**
 * @author 王天赐
 * @title: TrainPurchaseTicketParamVerifyChainHandler
 * @description: 购票流程过滤器之验证参数是否有效
 * @create: 2023-08-29 18:23
 */
@Component
public class TrainPurchaseTicketParamVerifyChainHandler implements TrainPurchaseTicketChainFilter<PurchaseTicketReqDTO> {
    @Override
    public void handler(PurchaseTicketReqDTO requestParam) {
        System.out.println("参数合法 , 过滤器执行成功");
    }

    @Override
    public int getOrder() {
        return 20;
    }
}
package cn.knightzz.pattern.filter.handler;

import cn.knightzz.pattern.dto.req.PurchaseTicketReqDTO;
import cn.knightzz.pattern.filter.TrainPurchaseTicketChainFilter;
import org.springframework.stereotype.Component;

/**
 * @author 王天赐
 * @title: TrainPurchaseTicketRepeatChainHandler
 * @description: 购票流程过滤器之验证乘客是否重复购买
 * @create: 2023-08-29 18:24
 */
@Component
public class TrainPurchaseTicketRepeatChainHandler implements TrainPurchaseTicketChainFilter<PurchaseTicketReqDTO> {
    @Override
    public void handler(PurchaseTicketReqDTO requestParam) {
        System.out.println("未重复购票 , 过滤器执行成功");
    }

    @Override
    public int getOrder() {
        return 30;
    }
}

3.7. 核心加载类

package cn.knightzz.pattern.context;

import cn.knightzz.pattern.chain.AbstractChainHandler;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.CommandLineRunner;
import org.springframework.core.Ordered;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;

import java.util.*;
import java.util.stream.Collectors;

/**
 * @author 王天赐
 * @title: AbstractChainContext
 * @description: CommandLineRunner:SpringBoot 启动完成后执行的回调函数
 * @create: 2023-08-29 18:27
 */
@Component
@Slf4j
public final class AbstractChainContext<T> implements CommandLineRunner {

    // CommandLineRunner:SpringBoot 启动完成后执行的回调函数


    // 存储责任链组件实现和责任链业务标识的容器
    // 比如:Key:购票验证过滤器 Val:HanlderA、HanlderB、HanlderC、......
    private final Map<String, List<AbstractChainHandler>> abstractChainHandlerContainer = new HashMap<>();


    public void handler(String mark, T requestParam) {
        List<AbstractChainHandler> abstractChainHandlers = abstractChainHandlerContainer.get(mark);
        if (CollectionUtils.isEmpty(abstractChainHandlers)) {
            throw new RuntimeException(String.format("[%s] Chain of Responsibility ID is undefined.", mark));
        }
        abstractChainHandlers.forEach(each -> each.handler(requestParam));
    }

    @Override
    public void run(String... args) throws Exception {
        // 通过ApplicationContextHolder获取所有的Bean
        Map<String, AbstractChainHandler> chainFilterMap =
                ApplicationContextHolder.getBeansOfType(AbstractChainHandler.class);

        chainFilterMap.forEach((beanName, bean) -> {

            // 获取指定类型的责任链集合, 如果没有就创建
            // 需要将同一个mark的放到一起
            List<AbstractChainHandler> abstractChainHandlers = abstractChainHandlerContainer.get(bean.mark());
            if (CollectionUtils.isEmpty(abstractChainHandlers)) {
                abstractChainHandlers = new ArrayList<>();
            }
            // 添加到处理器集合中
            abstractChainHandlers.add(bean);
            // 对处理器集合顺序进行排序
            List<AbstractChainHandler> actualAbstractChainHandlers = abstractChainHandlers
                    .stream()
                    .sorted(Comparator.comparing(Ordered::getOrder))
                    .collect(Collectors.toList());

            log.info("mark {} , bean : {} add container", bean.mark(), bean);

            //将排好序的Bean存入到容器等待运行时被调用
            abstractChainHandlerContainer.put(bean.mark(), actualAbstractChainHandlers);
        });

    }
}

这个类主要是需要实现 CommandLineRunner 接口, 这个接口提供一个run方法, 会在SpringBoot启动后执行

handler 方法

3.8. 基本使用

package cn.knightzz.pattern.service;

import cn.knightzz.pattern.common.enums.TicketChainMarkEnum;
import cn.knightzz.pattern.context.AbstractChainContext;
import cn.knightzz.pattern.dto.req.PurchaseTicketReqDTO;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;

/**
 * @author 王天赐
 * @title: TicketService
 * @description:
 * @create: 2023-08-29 19:04
 */
@Service
@RequiredArgsConstructor
public class TicketService {

    private final AbstractChainContext<PurchaseTicketReqDTO> purchaseTicketAbstractChainContext;

    public void purchase(PurchaseTicketReqDTO requestParam) {
        purchaseTicketAbstractChainContext.handler(TicketChainMarkEnum.TRAIN_PURCHASE_TICKET_FILTER.name(), requestParam);
    }
}

如上面代码所示 , 使用时 直接调用 AbstractChainContext 提供的handler方法既可

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/945238.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

LeetCode第21~25题解

CONTENTS LeetCode 21. 合并两个有序链表&#xff08;简单&#xff09;LeetCode 22. 括号生成&#xff08;中等&#xff09; LeetCode 21. 合并两个有序链表&#xff08;简单&#xff09; 【题目描述】 将两个升序链表合并为一个新的升序链表并返回。新链表是通过拼接给定的两…

使用awvs进行web安全扫描

1、安装 docker pull secfa/docker-awvs docker run -it -d -name awvs -p 13443:3443 --cap-add LINUX_IMMUTABLE secfa/docker-awvs2、账号密码 # https://ip:13443/ # 用户名:adminadmin.com # 密码:Admin1233、使用 ps:需要征得甲方的同意

重磅!OpenAI突然发布企业版ChatGPT:没有限制、更快、更强、更安全的GPT-4

这是由【小瑶智能体】 AI创作的第 4 篇科技文章 大模型研究测试传送门 GPT-4传送门&#xff08;免墙&#xff0c;可直接测试&#xff0c;遇浏览器警告点高级/继续访问即可&#xff09;&#xff1a;Hello, GPT4! 大家好&#xff0c;我是小瑶智能体&#xff0c;一个喜欢分享人…

VR全景展示:打造三维、立体化的VR房产参观方式

我们从近期的新闻中可以了解到&#xff0c;房地产行业正在经历挑战和压力&#xff0c;因为房地产销售市场的持续低迷&#xff0c;导致很多公司出现了债务危机。线下销售模式效果不佳&#xff0c;很多房企开始转战线上销售&#xff0c;VR全景展示方案为房地产销售带来了全新的体…

open cv快速入门系列---数字图像基础

目录 一、数字图像基础 1.1 数字图像和图像单位 1.2 区分图片分辨率与屏幕分辨率 1.3 图像的灰度与灰度级 1.4 图像的深度 1.5 二值图像、灰度图像与彩色图像 1.6 通道数 二、数字图像处理 2.1 图像噪声及其消除 2.2 数字图像处理技术 2.2.1 图像变换 2.2.2 图像增强…

汇编代码:在代码段中使用栈完成数据的倒序

前言 在代码段中使用栈完成数据的倒序 1、代码如下 assume cs:codesgcodesg segmentdw 0123H,0456H,0789H,0abcH,0defH,0fedH,0cbaH,0987Hdw 0,0,0,0,0,0,0,0 ;用dw定义8个字型数据&#xff0c;在程序加载后&#xff0c;将取得8个字的;内存空间&#xff0c;存放这8个数据。我…

Linux通信--构建进程通信的 方案之管道(下)|使用匿名管道实现功能解耦|命名管道实现serveclient通信

文章目录 一、管道的应用实例-父进程唤醒子进程&#xff0c;子进程执行某种任务 二、命名管道 1.创建一个命名管道 2.匿名管道与命名管道的区别 3.命名管道的打开规则 4.用命名管道实现server&client通信 一、匿名管道的应用实例-父进程唤醒子进程&#xff0c;子进程执…

Verilog开源项目——百兆以太网交换机(一)架构设计与Feature定义

Verilog开源项目——百兆以太网交换机&#xff08;一&#xff09;架构设计与Feature定义 &#x1f508;声明&#xff1a;未经作者允许&#xff0c;禁止转载 &#x1f603;博主主页&#xff1a;王_嘻嘻的CSDN主页 &#x1f511;全新原创以太网交换机项目&#xff0c;Blog内容将聚…

vue使用命令npm install 报错 cb() never called!

一.错误说明,npm本身下载就慢&#xff0c;有可能是网络的问题。 二.解决方案,把npm设置成淘宝镜像后,再重新npm install npm config set registry https://registry.npm.taobao.org 三.还是不行&#xff0c;还会出现同样的问题&#xff0c;那接下来先清理一下npm缓存 npm cache…

微信网站应用申请

网站应用是基于微信开放平台的。它的特点是&#xff1a; 1、用户可使用微信帐号快速登录你的网站&#xff0c;降低注册门槛&#xff0c;提高用户留存 2、同一用户使用微信登录你的不同应用和公众帐号&#xff0c;会对应同一个UnionID&#xff0c;以便进行不同业务间的帐号统一…

【力扣每日一题】2023.8.28 插入区间

目录 题目&#xff1a; 示例&#xff1a; 分析&#xff1a; 代码&#xff1a; 题目&#xff1a; 示例&#xff1a; 分析&#xff1a; 和昨天的题大差不差&#xff0c;我们仍然是有一堆区间&#xff0c;题目给我们一个新的区间&#xff0c;要我们把新区间插入到原本的区间数…

ES 7.6 - API高阶操作篇

ES 7.6 - API高阶操作篇 分片和副本索引别名添加别名查询所有别名删除别名使用别名代替索引操作代替插入代替查询 场景实操 滚动索引索引模板创建索引模板查看模板删除模板 场景实操一把索引的生命周期数据迁移APIGEO(地理)API索引准备矩形查询圆形查询多边形查询 自定义分词器…

范式 事务 多表查询

范式 概念&#xff1a;设计数据库时&#xff0c;需要遵循的一些规范。要遵循后边的范式要求&#xff0c;必须遵循前边的所有范式要求 第一范式&#xff1a; 数据库表的每一列都是不可分割的基本数据项 这样子就不满足第一范式 这样子就满足第一范式 存在问题&#xff1a; 数…

【真题解析】系统集成项目管理工程师 2022 年下半年真题卷(案例分析)

本文为系统集成项目管理工程师考试(软考) 2022 年下半年真题&#xff08;全国卷&#xff09;&#xff0c;包含答案与详细解析。考试共分为两科&#xff0c;成绩均 ≥45 即可通过考试&#xff1a; 综合知识&#xff08;选择题 75 道&#xff0c;75分&#xff09;案例分析&#x…

搭建微服务架构、实现服务调用

OpenFeign&#xff1a;Spring Cloud声明式服务调用组件 OpenFeign 全称 Spring Cloud OpenFeign&#xff0c;它是 Spring 官方推出的一种声明式服务调用与负载均衡组件&#xff0c;它的出现就是为了替代进入停更维护状态的 Feign。 OpenFeign 常用注解 使用 OpenFegin 进行远…

Java中线程共享变量的可见性问题

在Java中&#xff0c;线程是一种重要的并发编程机制。线程允许程序同时执行多个任务&#xff0c;提高了程序的效率和性能。然而&#xff0c;线程的并发执行也带来了一些问题&#xff0c;其中之一就是线程共享变量的可见性问题。 什么是线程共享变量的可见性问题呢&#xff1f;…

Confluence使用教程(用户篇)

1、如何创建空间 可以把空间理解成一个gitlab仓库&#xff0c;空间之间相互独立&#xff0c;一般建议按照部门&#xff08;小组的人太少&#xff0c;没必要创建空间&#xff09;或者按照项目分别创建空间 2、confluence可以创建两种类型的文档&#xff1a;页面和博文 从内容上来…

Linux通过libudev获取挂载路径、监控U盘热拔插事件、U盘文件系统类型

文章目录 获取挂载路径监控U盘热拔插事件libusb 文件系统类型通过挂载点获取挂载路径添libudev加库 获取挂载路径 #include <stdio.h> #include <libudev.h> #include <string.h>int main() {struct udev *udev;struct udev_enumerate *enumerate;struct ud…

C++--动态规划背包问题(1)

1. 【模板】01背包_牛客题霸_牛客网 你有一个背包&#xff0c;最多能容纳的体积是V。 现在有n个物品&#xff0c;第i个物品的体积为vivi​ ,价值为wiwi​。 &#xff08;1&#xff09;求这个背包至多能装多大价值的物品&#xff1f; &#xff08;2&#xff09;若背包恰好装满&a…

AMD即将上市大量中端显卡,为新显卡支付过高价格的日子可能结束

​AMD在本周末&#xff08;8月25日&#xff09;的德国Gamescom活动中展示了两款新显卡和一些新的升级技术&#xff0c;这些新GPU的定价将与英伟达的GeForce RTX 4000卡竞争。 这是一件大事&#xff0c;因为新的Radeon RX 7700 XT和7800 XT卡占据了AMD Radeon RX 7000系列产品线…