设计模式系列:三、责任链设计模式

一、概述

责任链模式是一种行为设计模式,它允许多个对象处理一个请求,从而避免了请求的发送者和接收者之间的耦合关系。

优点是把任务划分为一个一个的节点,然后按照节点之间的业务要求、顺序,把一个个节点串联起来,形成一个执行链路,一个节点一个节点向后执行;

把原来一堆代码按照原子性拆分成责任链,耦合降低,可扩展性增强,责任划分清晰;

最近在使用SpringGateway来开发网关功能,对SpringGateway中的FliterChain有了清晰的认知,而且正好在做这个网关时,需要对异常捕获进行处理,在异常捕获后,其实也要做很多增值功能,比如:异常请求日志打印、异常分类处理、异常响应日志打印、异常网关码补充、异常响应结果返回;借此,使用责任链模式,把这些功能实现;

其中也会涉及到SpringGateway异常捕获,所以想了解SpringGateway异常捕获的也可以看这篇文章;

二、责任链的写法

2.1 常用的责任链写法

在这里插入图片描述

首先,我们创建一个抽象的处理类(Handler):

public abstract class Handler {protected Handler nextHandler;public void setNextHandler(Handler nextHandler) {this.nextHandler = nextHandler;}public abstract void handleRequest(String request);
}

然后,我们创建具体的处理类(ConcreteHandler1、ConcreteHandler2等),它们都继承自Handler类,并实现了handleRequest方法:

public class ConcreteHandler1 extends Handler {@Overridepublic void handleRequest(String request) {//TODO 1的处理逻辑//向下个节点传递,也可以在这个节点直接断掉,returnif (nextHandler != null) {nextHandler.handleRequest(request);} }
}public class ConcreteHandler2 extends Handler {@Overridepublic void handleRequest(String request) {//TODO 2的处理逻辑//向下个节点传递,也可以在这个节点直接断掉returnif (nextHandler != null) {nextHandler.handleRequest(request);} }
}

最后,我们在客户端代码中使用责任链模式处理请求:

public class Client {public static void main(String[] args) {Handler handler1 = new ConcreteHandler1();Handler handler2 = new ConcreteHandler2();handler1.setNextHandler(handler2);handler1.handleRequest("request");}
}

这种常用的责任链模式的写法,节点之间的前后关系在Client中已经固化,

下面给出一种通过数组形式存储节点的前后关系;

2.1 节点存到数组的写法(结合SpringGateway异常处理)

在这里插入图片描述

创建一个 异常组件接口,有两个抽象方法:

import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;public interface ExceptionPlugin {/*** 节点的处理方法* @param exchange 节点处理的对象,可以是任何对象,会不断向后面的节点传递,可以是任何形式的对象* @param pluginChain 执行调度者* @param 捕获的异常对象 异常* @return MONO*/Mono<Void> handle(ServerWebExchange exchange, ExceptionChain pluginChain, Throwable ex);/*** 组件的执行顺序* @return 数字*/int order();
}

创建 链执行调度者 ExceptionChain:

import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;/*** 链执行调度者* @author xch* 2023/10/7 14:20*/
public class ExceptionChain{/*** 当前执行的组件的 下标位置*/private int pos;/*** 异常组件 列表*/private List<ExceptionPlugin> plugins;/*** 添加 异常组件*/public void addPlugin(ExceptionPlugin gatePlugin) {if (plugins == null) {plugins = new ArrayList<>();}plugins.add(gatePlugin);// 按照 异常组件的order返回的int排序,越小越先执行plugins.sort(Comparator.comparing(ExceptionPlugin::order));}/*** 责任链的节点 执行器,调用这个方法,会按照组件列表向后执行*/public Mono<Void> execute(ServerWebExchange exchange, ExceptionChain pluginChain, Throwable ex) {if (pos == plugins.size()) {return exchange.getResponse().setComplete();}return pluginChain.plugins.get(pos++).handle(exchange, pluginChain, ex);}}

创建各个异常链节点,实现ExceptionPlugin接口:

异常请求日志打印组件:

import lombok.extern.slf4j.Slf4j;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.cloud.gateway.route.Route;
import org.springframework.cloud.gateway.support.ServerWebExchangeUtils;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.util.StringUtils;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;import java.time.LocalDateTime;/*** 异常责任链组件-请求日志打印组件* @author xch* 2023/11/20 13:56*/
@Slf4j
public class ExceptionRequestLogPlugin implements ExceptionPlugin {@Overridepublic Mono<Void> handle(ServerWebExchange exchange, ExceptionChain pluginChain, Throwable ex) {ServerHttpRequest request = exchange.getRequest();log.info("datetime >>> {},; path >>> {},; method >>> {},; host >>> {},; request_headers >>> {},; query_params >>> {},; request_body >>> {}",request.getPath().value(),request.getMethod(),request.getRemoteAddress() == null ? "" : request.getRemoteAddress().getHostString(),request.getHeaders(),request.getQueryParams(),//获取请求body不在这里展开"请求body");//向下个节点执行return pluginChain.execute(exchange, pluginChain, ex);}@Overridepublic int order() {return 0;}
}

异常分类细化处理组件

package com.winning.gate.common.exception.plugin;import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.gateway.support.NotFoundException;
import org.springframework.cloud.gateway.support.TimeoutException;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.web.server.ResponseStatusException;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;/*** 异常责任链组件-异常分类处理组件* @author xch* 2023/11/20 13:56*/
@Slf4j
public class ExceptionClassifyPlugin implements ExceptionPlugin {@Overridepublic Mono<Void> handle(ServerWebExchange exchange, ExceptionChain pluginChain, Throwable ex) {ServerHttpRequest request = exchange.getRequest();//TODO 精细化处理异常if (ex instanceof ResponseStatusException) {} else if (ex instanceof GatewayException) {} else if (ex instanceof TimeoutException) {} else if (ex instanceof NotFoundException) {} else {}return pluginChain.execute(exchange, pluginChain, ex);}@Overridepublic int order() {return 1;}
}

响应Code信息组件

package com.winning.gate.common.exception.plugin;import com.winning.gate.common.constant.FliterChainContant;
import com.winning.gate.common.constant.RequestHeaderContant;
import com.winning.gate.common.exception.ExceptionChain;
import com.winning.gate.common.exception.ExceptionPlugin;
import com.winning.gate.response.Result;
import org.springframework.http.HttpHeaders;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;/*** 异常责任链组件-网关码组件* @author xch* 2023/11/20 13:56*/
public class ExceptionGatecodePlugin implements ExceptionPlugin {@Overridepublic Mono<Void> handle(ServerWebExchange exchange, ExceptionChain pluginChain, Throwable ex) {ServerHttpResponse response = exchange.getResponse();HttpHeaders headers = response.getHeaders();headers.add("X_CA_REQUESTID", "12121");headers.add("X_CA_ERROR", "false");return pluginChain.execute(exchange, pluginChain, ex);}@Overridepublic int order() {return 2;}
}

异常响应结果回写组件

public class ExceptionWritebackPlugin implements ExceptionPlugin {@Overridepublic Mono<Void> handle(ServerWebExchange exchange, ExceptionChain pluginChain, Throwable ex) {// 设置 headerServerHttpResponse response = exchange.getResponse();Result<?> result = exchange.getAttribute("EXCEPTION_CHAIN_RESULT");// 必须使用 APPLICATION_JSON_UTF8_VALUE,否则会乱码response.getHeaders().setContentType(MediaType.APPLICATION_JSON_UTF8);// 设置 bodyreturn response.writeWith(Mono.fromSupplier(() -> {DataBufferFactory bufferFactory = response.bufferFactory();return bufferFactory.wrap(JsonConverter.jsonToByte(result));}));}@Overridepublic int order() {return 999;}
}

SpringGateway的异常捕获处理,在这里构造最终的责任调用链,代码如下:

import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.web.reactive.error.ErrorWebExceptionHandler;
import org.springframework.core.annotation.Order;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;@Component
@Order(-1)
@Slf4j
public class GatewayExceptionHandler implements ErrorWebExceptionHandler {@Overridepublic Mono<Void> handle(ServerWebExchange exchange, Throwable ex) {ServerHttpResponse response = exchange.getResponse();if (response.isCommitted()) {return Mono.error(ex);}//异常责任链构建ExceptionChain exceptionChain = new ExceptionChain();exceptionChain.addPlugin(new ExceptionRequestLogPlugin());exceptionChain.addPlugin(new ExceptionClassifyPlugin());exceptionChain.addPlugin(new ExceptionGatecodePlugin());exceptionChain.addPlugin(new ExceptionWritebackPlugin());//执行起点return exceptionChain.execute(exchange, exceptionChain, ex);}}

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

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

相关文章

如何使用浏览器自动化框架Playwright开发“万媒易发”实现多平台自动发布文章?

作为一名程序员和开发者&#xff0c;我深知在多个媒体平台手动发布和管理文章的痛苦。因此&#xff0c;我决定使用Playwright浏览器自动化框架&#xff0c;开发一款名为“万媒易发”的工具&#xff0c;实现多平台自动发布文章。下面我将分享这款工具的开发过程和成果&#xff0…

浅析教学型数控车床使用案例

教学型数控车床是一种专为教学和培训设计的机床&#xff0c;它具有小型化、高精度和灵活性的特点&#xff0c;可以作为学校和技术学院的培训机器。下面是一个使用案例&#xff0c;以展示教学型数控车床在教学实训中的应用。 案例背景&#xff1a; 某职业技术学院的机械工程专业…

Socket通信之网络协议基本原理

一台机器将自己想要表达的内容&#xff0c;按照某种约定好的格式发送出去&#xff0c;当另外一台机器收到这些信息后&#xff0c;也能够按照约定好的格式解析出来&#xff0c;从而准确、可靠地获得发送方想要表达的内容。这种约定好的格式就是网络协议&#xff08;Networking P…

VR全景航拍要注意什么,航拍图片如何处理

引言: VR全景航拍技术是当前摄影和航拍领域的新潮流。它采用虚拟现实技术&#xff0c;通过360度全景镜头捕捉画面&#xff0c;可以为观众提供身临其境的视觉体验。在宣传展示中&#xff0c;利用VR全景航拍技术可以为品牌宣传带来更加生动、震撼的视觉效果。 一、航拍注意事项 …

【追求卓越01】数据结构--数组

引导 这一章节开始&#xff0c;正式进入数据结构与算法的学习过程中。由简到难&#xff0c;先开始学习最基础的数据结构--数组。 我相信对于数组&#xff0c;大家肯定是不陌生&#xff0c;因为数组在大多数的语言中都有&#xff0c;也是大家在编程中常常会接触到的。我不会说数…

jQuery实现横版手风琴效果

一、实现效果 当鼠标滑过方块的时候&#xff0c;方块的状态就会发生如下图所示的变化&#xff0c;同理当鼠标滑到其他的方块也会发生同样的效果&#xff0c;不仅大小会改变同时方块的颜色也会跟着发生变化&#xff1a; 二、代码实现 <!DOCTYPE html> <html><h…

笔记58:Encoder-Decoder 架构

本地笔记地址&#xff1a;D:\work_file\&#xff08;4&#xff09;DeepLearning_Learning\03_个人笔记\3.循环神经网络\第9章&#xff1a;动手学深度学习~现代循环神经网络 a a a a a a a a a

SpringBoot 导入其他配置文件

默认情况下&#xff0c;springboot 初始的项目中都有一个 application.yml 或者 application.properties 文件&#xff0c;如果我们希望再定义一个独立的配置文件用来配置特定业务数据&#xff0c;而不希望把这些配置内容都堆积在 application 配置文件中&#xff0c;实现这个需…

python命令行交互 引导用户选择宠物

代码 以下代码将在命令行中&#xff0c;引导用户选择一个或者多个宠物&#xff0c;并反馈用户选择的宠物 # -*- coding:UTF-8 -*- """ author: dyy contact: douyaoyuan126.com time: 2023/11/22 15:19 file: 在命令行中引导用户选择宠物.py desc: xxxxxx &qu…

好题分享(2023.11.12——2023.11.18)

目录 ​ 前情回顾&#xff1a; 前言&#xff1a; 题目一&#xff1a;《有效括号》 思路&#xff1a; 总结&#xff1a; 题目二&#xff1a;《用队列实现栈》 思路&#xff1a; 总结&#xff1a; 题目三&#xff1a;《用栈实现队列》 思路&#xff1a; 总结 &#x…

WPF实战项目十五(客户端):RestSharp的使用

1、在WPF项目中添加Nuget包&#xff0c;搜索RestSharp安装 2、新建Service文件夹&#xff0c;新建基础通用请求类BaseRequest.cs public class BaseRequest{public Method Method { get; set; }public string Route { get; set; }public string ContenType { get; set; } &quo…

Node.js之http模块

http模块是什么&#xff1f; http 模块是 Node,js 官方提供的、用来创建 web 服务器的模块。通过 http 模块提供的 http.createServer() 方法&#xff0c;就能方便的把一台普通的电脑&#xff0c;变成一台Web 服务器&#xff0c;从而对外提供 Web 资源服务。 如果我们想在node…

Request 爬虫的 SSL 连接问题深度解析

SSL 连接简介 SSL&#xff08;Secure Sockets Layer&#xff09;是一种用于确保网络通信安全性的加密协议&#xff0c;广泛应用于互联网上的数据传输。在数据爬取过程中&#xff0c;爬虫需要与使用 HTTPS 协议的网站进行通信&#xff0c;这就牵涉到了 SSL 连接。本文将深入研究…

向日葵x华测导航:远程控制如何助力导航测绘设备运维

导航测绘在各个领域均在发挥积极作用&#xff0c;其中RTK载波相位差分技术是导航测绘领域所常用的主流技术&#xff0c;该技术基于卫星定位系统的基础定位数据&#xff0c;可以实现在野外实时获取厘米级精度的定位数据&#xff0c;一定程度上省去了事后解算的麻烦。相应的&…

(论文阅读46-50)图像描述2

46.文献阅读笔记 简介 题目 Learning a Recurrent Visual Representation for Image Caption Generation 作者 Xinlei Chen, C. Lawrence Zitnick, arXiv:1411.5654. 原文链接 http://www.cs.cmu.edu/~xinleic/papers/cvpr15_rnn.pdf 关键词 2014年rnn图像特征和文本特…

验证码 | 可视化一键管控各场景下的风险数据

目录 查看今日验证数据 查看未来趋势数据 验证码作为人机交互界面经常出现的关键要素&#xff0c;是身份核验、防范风险、数据反爬的重要组成部分&#xff0c;广泛应用网站、App上&#xff0c;在注册、登录、交易、交互等各类场景中发挥着巨大作用&#xff0c;具有真人识别、身…

Leo赠书活动-10期 【AIGC重塑教育 AI大模型驱动的教育变革与实践】文末送书

✅作者简介&#xff1a;大家好&#xff0c;我是Leo&#xff0c;热爱Java后端开发者&#xff0c;一个想要与大家共同进步的男人&#x1f609;&#x1f609; &#x1f34e;个人主页&#xff1a;Leo的博客 &#x1f49e;当前专栏&#xff1a; 赠书活动专栏 ✨特色专栏&#xff1a;…

新手做抖店,这6点建议一定要收好,能让你不亏钱!

我是电商珠珠 我呢&#xff0c;目前身居郑州。 电商这个行业也做了5年多了&#xff0c;抖店从20年开始做&#xff0c;到现在也已经快3年了。 其实&#xff0c;我做抖店期间呢&#xff0c;踩过很多坑&#xff0c;所以今天就把我所踩过的坑&#xff0c;给做抖店的新手总结了6点…

QT mysql 数据库线程池 与数据库操作封装

最近事情比较多很久没有写学习笔记了&#xff0c;数据库线程池&#xff0c; 数据库封装&#xff0c;虽说数据库操作有很多不需要写sql 的&#xff0c;ORM 封装的方式去操作数据库。但是从业这些年一直是自己动手写sql &#xff0c;还是改不了这个习惯。不说了直接上代码。 数据…

【23真题】劝退211!今年突变3门课!

今天分享的是23年云南大学847&#xff08;原827&#xff09;的考研试题及解析。同时考SSDSP的院校做一个少一个&#xff0c;珍惜&#xff01;同时考三门课的院校&#xff0c;复习压力极大&#xff0c;但是也会帮大家劝退很多人&#xff0c;有利有弊&#xff0c;请自行分析~ 本…