提升代码可读性与可维护性:利用责任链模式优化你的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.mzph.cn/news/58535.shtml

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

相关文章

docker高级(mysql主从复制)

数据库密码需要设置成自己的&#xff01;&#xff01;&#xff01; 1、创建容器master13307 #docker pulldocker run -p 13307:3306 --name mysql-master \ --privilegedtrue \ -v /mysql/mysql-master/log:/var/log/mysql \ -v /mysql/mysql-master/data:/var/lib/mysql \ -…

【Unity】【Amplify Shader Editor】ASE入门系列教程第二课 硬边溶解

黑色为0,白色为1 新建材质&#xff08;不受光照影响&#xff09; 拖入图片 设置 添加节点&#xff1a; 快捷键&#xff1a;K 组合通道&#xff1a;快捷键 V 完成图

【Sublime Text 】Sublime Text 设置中文 超详细 持续更新中

Sublime Text 设置中文 超详细 持续更新中 概述开发环境一、Sublime Text 设置中文 概述 一个好的安装教程能够帮助开发者完成更便捷、更快速的开发。书山有路勤为径&#xff0c;学海无涯苦作舟。我是秋知叶i、期望每一个阅读了我的文章的开发者都能够有所成长。 开发环境 开…

问道管理:逾4600股飘红!汽车板块爆了,多股冲击涨停!

A股商场今天上午全体低开&#xff0c;但其后逐级上行&#xff0c;创业板指数上午收盘大涨超越3%&#xff0c;北向资金也完成净买入38亿元。 别的&#xff0c;A股商场半年报成绩发表如火如荼进行中&#xff0c;多家公司发表半年报后股价应声大涨&#xff0c;部分公司股价冲击涨停…

【爬虫GUI】YouTube评论采集软件,突破反爬,可无限爬取!

文章目录 一、背景介绍1.1 软件说明1.2 效果演示 二、科普知识2.1 关于视频id2.2 关于评论时间 三、爬虫代码3.1 界面模块3.2 爬虫模块3.3 日志模块 四、获取源码及软件 一、背景介绍 你好&#xff0c;我是马哥python说 &#xff0c;一名10年程序猿。 最近我用python开发了一…

nginx-concat

为了减少tcp请求数量&#xff0c;nginx从上有服务器获取多个静态资源&#xff08;css&#xff0c;js&#xff09;的时候&#xff0c;将多个静态资源合并成一个返回给客户端。 这种前面有两个问号的请求都是用了cancat合并功能。 先到官网下载安装包&#xff0c;拷贝到服务器编译…

Android 绘制之文字测量

drawText() 绘制文字 绘制进度条:paint.strokeCap Paint.CAP.RONUD 线条两边样式 设置文字字体:paint.typeFace Resources.Compat.getFont(context,font) 设置加粗 paint.isFakeBoldText 设置居中: paint.setTextAlign Paint.Align.CENTER //居中, 并不是真正的居中 往…

Transformer (Attention Is All You Need) 论文精读笔记

Transformer(Attention Is All You Need) Attention Is All You Need 参考&#xff1a;跟李沐学AI-Transformer论文逐段精读【论文精读】 摘要&#xff08;Abstract&#xff09; 首先摘要说明&#xff1a;目前&#xff0c;主流的序列转录&#xff08;序列转录&#xff1a;给…

腾讯云学生服务器申请、学生认证入口及学生机价格表

腾讯云学生服务器申请、学生认证入口及学生机价格表&#xff0c;学生机申请流程&#xff0c;腾讯云学生服务器优惠活动&#xff1a;轻量应用服务器2核2G学生价30元3个月、58元6个月、112元一年&#xff0c;轻量应用服务器4核8G配置191.1元3个月、352.8元6个月、646.8元一年&…

【超简单】远程服务器使用 plt.show() 和 cv2.imshow() 可视化图像

远程服务器可视化图像 我的配置MobaXterm 远程显示VSCode 远程显示 我的配置 服务器 Ubuntu 20.04.3 LTSAnaconda 本地电脑 Win11MobaXtermVSCode MobaXterm 远程显示 配置好服务器连接&#xff08;此处略&#xff09;&#xff1b; 连接服务器&#xff0c;并激活使用的 A…

zookeeper启动失败(Error contacting service. It is probably not running.)

问题描述 启动zk时报如下错误&#xff1a; 解决办法 先查日志找找报错原因&#xff1a; 找到zk安装目录下的logs文件夹下的日志文件&#xff0c;查看连接失败原因&#xff1a; 如果是端口问题&#xff0c;修改conf文件&#xff0c;指定端口重新启动即可&#xff1a; 注&a…

JUC并发编程--------基础篇

一、多线程的相关知识 栈与栈帧 我们都知道 JVM 中由堆、栈、方法区所组成&#xff0c;其中栈内存是给谁用的呢&#xff1f;其实就是线程&#xff0c;每个线程启动后&#xff0c;虚拟 机就会为其分配一块栈内存。 每个栈由多个栈帧&#xff08;Frame&#xff09;组成&#xf…

算法通过村第四关-栈白银笔记|括号问题

文章目录 前言1. 括号匹配问题2. 最小栈问题3. 最大栈 总结 前言 提示&#xff1a;如果让我送给年轻人四个字&#xff0c;就是&#xff1a;量力而行。 量力而行不会失眠&#xff0c;不会啃老&#xff0c;不会为各种考试焦虑。顺其自然活得轻松。其实&#xff0c;量力而行最易大…

小米面试题——不用加减乘除计算两数之和

前言 &#xff08;1&#xff09;刷B站看到一个面试题&#xff0c;不用加减乘除计算两数之和。 &#xff08;2&#xff09;当时我看到这个题目&#xff0c;第一反应就是感觉这是一个数电题目。不过需要采用C语言的方式编写出来。 &#xff08;3&#xff09;不过看到大佬的代码之…

算法与数据结构(九)--并查集

并查集是一种树型的数据结构&#xff0c;并查集可以高校地进行如下操作&#xff1a; *查询元素p和元素q是否在同一组 *合并元素p和元素q所在的组 一.并查集结构 并查集也是一种树型结构&#xff0c;这种树的要求比较简单&#xff1a;1.每个元素都唯一的对应一个结点&#xff…

优美而高效:解决服务器通信问题

题目背景 在这个问题中&#xff0c;我们面临着一幅服务器分布图。图中的每个单元格可能有服务器&#xff08;标记为1&#xff09;或者没有&#xff08;标记为0&#xff09;。我们的任务是找出能够与至少一台其他服务器进行通信的服务器数量。 算法思路 为了解决这个问题&…

无涯教程-Python机器学习 - Stochastic Gradient Boosting函数

它也称为梯度提升机。在下面的Python食谱中,我们将通过使用pima Indians糖尿病数据集上的 sklearn 的 GradientBoostingClassifier 类来创建随机梯度Boostingensemble模型进行分类。 首先,导入所需的软件包,如下所示: from pandas import read_csv from sklearn.model_select…

国民八路参考文献:[8]许少辉.乡村振兴战略下传统村落文化旅游设计[M]北京:中国建筑工业出版社,2022.

国民八路参考文献&#xff1a;&#xff3b;&#xff18;&#xff3d;许少辉&#xff0e;乡村振兴战略下传统村落文化旅游设计&#xff3b;&#xff2d;&#xff3d;北京&#xff1a;中国建筑工业出版社&#xff0c;&#xff12;&#xff10;&#xff12;&#xff12;&#xff0…

SpringCloud教程 | 第二篇: 服务消费者(rest+ribbon)

在上一篇文章&#xff0c;讲了服务的注册和发现。在微服务架构中&#xff0c;业务都会被拆分成一个独立的服务&#xff0c;服务与服务的通讯是基于http restful的。Spring cloud有两种服务调用方式&#xff0c;一种是ribbonrestTemplate&#xff0c;另一种是feign。在这一篇文章…

Python requests实现图片上传接口自动化测试

最近帮别人写个小需求&#xff0c;需要本地自动化截图&#xff0c;然后图片自动化上传到又拍云&#xff0c;实现自动截图非常简单&#xff0c;在这里就不详细介绍了&#xff0c;主要和大家写下&#xff0c;如何通过Pythonrequests实现上传本地图片到又拍云服务器。 话不多说&a…