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

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;@Overridepublic 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> {@Overridedefault 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> {@Overridepublic void handler(PurchaseTicketReqDTO requestParam) {System.out.println("参数不能为空 , 过滤器执行成功");}@Overridepublic 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> {@Overridepublic void handler(PurchaseTicketReqDTO requestParam) {System.out.println("参数合法 , 过滤器执行成功");}@Overridepublic 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> {@Overridepublic void handler(PurchaseTicketReqDTO requestParam) {System.out.println("未重复购票 , 过滤器执行成功");}@Overridepublic 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));}@Overridepublic void run(String... args) throws Exception {// 通过ApplicationContextHolder获取所有的BeanMap<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.rhkb.cn/news/110863.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

运算符(个人学习笔记黑马学习)

算数运算符 加减乘除 #include <iostream> using namespace std;int main() {int a1 10;int a2 20;cout << a1 a2 << endl;cout << a1 - a2 << endl;cout << a1 * a2 << endl;cout << a1 / a2 << endl;/*double a3 …

GPU版本pytorch(Cuda12.1)安装教程

我们通过Pytorch官网安装torch的时候&#xff0c;会发现常常由于网速问题安装不成功&#xff0c;下面提供一种简单的方法可以成功安装Cuda12.1&#xff0c;亲测有效。 目录 一、常规方法 二、有效方法 2.1 创建并激活虚拟环境 2.2 添加清华源 2.3 安装torch 一、常规方法…

惠普NS1020激光打印机碳粉警告提示及添加碳粉方法

本文也适用于惠普NS1020、1020c 和 1020w 系列打印机。 通过碳粉量指示灯检查碳粉量。 如果碳粉量是满的或指示器显示 1&#xff0c;可选择添加一个碳粉或者忽略不添加。如果碳粉量指示灯显示 2或 2 和碳粉量警告感叹号图标 &#xff0c;则表示碳粉量不足或严重不足&#xff0…

Mysql--技术文档--MVCC(Multi-Version Concurrency Control | 多版本并发控制)

MVCC到底是什么 MVCC&#xff08;Multi-Version Concurrency Control&#xff09;是一种并发控制机制&#xff0c;用于解决并发访问数据库时的数据一致性和隔离性问题。MVCC允许多个事务同时读取数据库的同一数据&#xff0c;而不会相互干扰或导致冲突。 在传统的并发控制机制中…

远程教育:别催了,在线巡课真爽啊

随着远程教育和在线学习的兴起&#xff0c;教学活动的场景正逐渐从传统的实体教室转向虚拟的线上平台&#xff0c;这也催生了对教学质量监管的新需求。 在线巡课系统在这一背景下应运而生&#xff0c;它不仅能够实时观察教师的教学过程&#xff0c;还能够量化评估教学效果&…

Element——table排序,上移下移功能。及按钮上一条下一条功能

需求&#xff1a;table排序&#xff0c;可操作排序上移下移功能。判断第一行上移禁用和最后一行下移禁用&#xff0c;排序根据后端返回的字段 <el-table:data"tableData"style"width: 100%"><el-table-column type"index" label"序…

先进API生产力工具eqable HTTP,一站式开发调试工具推荐

简介 Reqable是什么? Regable Fiddler/Charles Postman Reqable是HTTP一站式开发调试国产化解决方案&#xff0c;拥有更便捷的体验&#xff0c;更先进的协议&#xff0c;更高效的性能和更精致的界面。 Reqable是一款跨平台的专业HTTP开发和调试工具&#xff0c;在全平台支持…

APSIM模型应用与参数优化、批量模拟

APSIM (Agricultural Production Systems sIMulator)模型是世界知名的作物生长模拟模型之一。APSIM模型有Classic和Next Generation两个系列模型&#xff0c;能模拟几十种农作物、牧草和树木的土壤-植物-大气过程&#xff0c;被广泛应用于精细农业、水肥管理、气候变化、粮食安…

ElasticSearch总结

ES是什么 ES是一个天生支持分布式的搜索、聚合分析的存储引擎 基于Java开发 基于Lucene的开源分布式搜索引擎 ELK &#xff1a; elasticSearch Logstah Kibana 加入 Beats 后 ELK 改为 &#xff1a;Elastic stack ES解决了什么问题 ES解决的核心问题 &#xff1a; 1.海量数…

Vue2向Vue3过度核心技术工程化开发和脚手架

目录 1 工程化开发和脚手架1.1 开发Vue的两种方式1.2.脚手架Vue CLI 2 项目目录介绍和运行流程2.1 项目目录介绍2.2 运行流程 3 组件化开发4 根组件 App.vue4.1 根组件介绍4.2 组件是由三部分构成4.3 总结 5 普通组件的注册使用-局部注册5.1 特点&#xff1a;5.2 步骤&#xff…

爬虫异常处理之如何处理连接丢失和数据存储异常

在爬虫开发过程中&#xff0c;我们可能会遇到各种异常情况&#xff0c;如连接丢失、数据存储异常等。本文将介绍如何处理这些异常&#xff0c;并提供具体的解决代码。我们将以Python语言为例&#xff0c;使用requests库进行网络请求和sqlite3库进行数据存储。 1. 处理连接丢失 …

GD32-舵机的原理

GD32-舵机的原理 舵机的现一脉宽与舵机转动角度 旋转编码器的原理 顺时针&#xff1a;A的下降沿时&#xff0c;B处于高电平&#xff1b; 逆时针&#xff1a;A的下降沿时&#xff0c;B处于低电平&#xff1b; #ifndef _ENCODER_DRIVE_H #define _ENCODER_DRIVE_H#include &quo…

开源软件的崛起:历史与未来

&#x1f337;&#x1f341; 博主猫头虎 带您 Go to New World.✨&#x1f341; &#x1f984; 博客首页——猫头虎的博客&#x1f390; &#x1f433;《面试题大全专栏》 文章图文并茂&#x1f995;生动形象&#x1f996;简单易学&#xff01;欢迎大家来踩踩~&#x1f33a; &a…

5.Redis-string

string 字符串 字符串类型是 Redis 最基础的数据类型&#xff0c;关于字符串需要特别注意&#xff1a; 1.⾸先Redis中所有 key 的类型都是字符串类型&#xff0c;⽽且其他⼏种数据结构也都是在字符串类似基础上构建的&#xff0c;例如 list 和 set 的元素类型是字符串类型。 2…

每日一题 823. 带因子的二叉树

每日一题 823. 带因子的二叉树 难度&#xff1a;中等 思路&#xff1a; 取乘积&#xff0c;那么两个叶子节点相乘一定会得到一个更大的数&#xff0c;所以先排序以父节点为根节点的数的数量 以右节点为根节点的数的数量 * 以左节点为根节点的数的数量初始化列表&#xff0c;…

花5分钟判断,你的Jmeter技能是大佬还是小白!

jmeter 这个工具既可以做接口的功能测试&#xff0c;也可以做自动化测试&#xff0c;还可以做性能测试&#xff0c;其主要用途就是用于性能测试。但是&#xff0c;有些公司和个人&#xff0c;就想用 jmeter 来做接口自动化测试。 你有没有想过呢&#xff1f; 下面我就给大家讲…

多线程(二)

一.关于线程的常用操作 1.启动线程 run(): 对于run方法的覆写只是指定线程要做的任务清单&#xff0c;而不是真正的启动线程 start()&#xff1a; start()方法才是真正的在底层创建出一个线程&#xff0c;并且启动 2.中断线程 1.通过共享的标记来中断 package demo; impor…

unity-AI自动导航

unity-AI自动导航 给人物导航 一.地形创建 1.首先我们在Hierarchy面板中创建一个地形对象terrian&#xff0c;自行设定地形外貌&#xff0c;此时我们设置一个如下的地形外观。 二.创建导航系统 1.在主人公的Inspector、面板中添加Nav Mesh Agent &#xff08;导航网格代理&…

python中的matplotlib画直方图(数据分析与可视化)

python中的matplotlib画直方图&#xff08;数据分析与可视化&#xff09; import numpy as np import pandas as pd import matplotlib.pyplot as pltpd.set_option("max_columns",None) plt.rcParams[font.sans-serif][SimHei] plt.rcParams[axes.unicode_minus]Fa…

【Linux】进程通信 — 信号(下篇)

文章目录 &#x1f4d6; 前言1. 阻塞信号1.1 信号其他相关常见概念&#xff1a;1.2 sigset_t&#xff1a;1.2 - 1 信号集操作函数 1.3 sigprocmask&#xff1a;1.4 sigpending&#xff1a; 2. 进程处理信号2.1 内核页表和用户页表&#xff1a;2.2 内核态和用户态&#xff1a;2.…