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

一、概述

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

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

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

最近在使用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 {
    @Override
    public void handleRequest(String request) {
        //TODO 1的处理逻辑
        
        //向下个节点传递,也可以在这个节点直接断掉,return
        if (nextHandler != null) {
            nextHandler.handleRequest(request);
        } 
    }
}

public class ConcreteHandler2 extends Handler {
    @Override
    public void handleRequest(String request) {
        //TODO 2的处理逻辑
        
        //向下个节点传递,也可以在这个节点直接断掉return
        if (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 {

    @Override
    public 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);
    }

    @Override
    public 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 {
    @Override
    public 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);
    }

    @Override
    public 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 {
    @Override
    public 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);
    }

    @Override
    public int order() {
        return 2;
    }
}

异常响应结果回写组件

public class ExceptionWritebackPlugin implements ExceptionPlugin {
    @Override
    public Mono<Void> handle(ServerWebExchange exchange, ExceptionChain pluginChain, Throwable ex) {
        // 设置 header
        ServerHttpResponse response = exchange.getResponse();
        Result<?> result = exchange.getAttribute("EXCEPTION_CHAIN_RESULT");

        // 必须使用 APPLICATION_JSON_UTF8_VALUE,否则会乱码
        response.getHeaders().setContentType(MediaType.APPLICATION_JSON_UTF8);
        // 设置 body
        return response.writeWith(Mono.fromSupplier(() -> {
            DataBufferFactory bufferFactory = response.bufferFactory();
            return bufferFactory.wrap(JsonConverter.jsonToByte(result));
        }));
    }

    @Override
    public 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 {

    @Override
    public 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);
    }

}

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

单链表相关面试题--4.输入一个链表,输出该链表中倒数第k个结点

/* 解题思路&#xff1a; 快慢指针法 fast, slow, 首先让fast先走k步&#xff0c;然后fast,slow同时走&#xff0c;fast走到末尾时&#xff0c;slow走到倒数第k个节点。 */ class Solution { public:ListNode* FindKthToTail(ListNode* pListHead, unsigned int k) {struct Lis…

AppLink定时调度操作

RestCloud AppLink定时调度操作 定时调度可以让我们更加快速了解到数据的变动以作出更好的决策&#xff0c;接下来通过AppLink平台配置定时调度的操作。 1.登录RestCloud AppLink 2.点击授权管理 3.点击应用认证菜单 4.新建拼多多授权认证 基础定时调度配置 1.拉取一个定时器…

Linux内核调试篇——获取内核函数地址的四种方法(一文解决)

在内核调试中&#xff0c;经常需要知道某个函数的地址&#xff0c;或者根据函数地址找到对应的函数&#xff0c;从而进行更深一步的debug。 下面介绍四种获取内核函数地址的方法&#xff1a; 1、System.map 在编译Linux内核时&#xff0c;会产生一个内核映像文件System.map&…

英飞凌TC3xx的LMU SRAM保护机制(一)

目录 1.基本概述 2.理解Master Tag ID 3.LMU memory保护使能 4.测试结果分析 5.小结 1.基本概述 在英飞凌TC3xx中&#xff0c;每个CPU除了有自己的DLMU外&#xff0c;在SRI总线还挂着几块SRAM&#xff0c;这几块SRAM由LMU(Local Memory Unit)进行权限控制。…

微服务学习|Gateway网关:网关作用、快速入门、路由断言工厂、路由过滤器配置、全局过滤器、过滤器执行顺序、跨域问题处理

为什么需要网关 网关功能: 1.身份认证和权限校验 2.服务路由、负载均衡 3.请求限流 网关的技术实现 在SpringCloud中网关的实现包括两种:gateway、zuul Zuul是基于Servlet的实现&#xff0c;属于阻塞式编程。而SprinaCloudGateway则是基于Spring5中提供的WebFlux&#xf…

RHCE之WEB服务器作业

目录 一、实验要求 1.1 网站需求&#xff1a; 1.2 要求&#xff1a; 二、实验步骤 2.1 下载mod_ssl模块&#xff0c;关闭firewall 2.2 链接本机IP与域名 2.3 创建网页目录 2.4 建立该网站 ​编辑 2.5 创建两位学生信息 2.6 实施数据加密 三、实验结果 一、实验要求…

Leetcode刷题详解——打家劫舍 II

1. 题目链接&#xff1a;213. 打家劫舍 II 2. 题目描述&#xff1a; 你是一个专业的小偷&#xff0c;计划偷窃沿街的房屋&#xff0c;每间房内都藏有一定的现金。这个地方所有的房屋都 围成一圈 &#xff0c;这意味着第一个房屋和最后一个房屋是紧挨着的。同时&#xff0c;相邻…

火电安全事故vr模拟仿真培训强交互更真实

VR消防&#xff0c;利用VR虚拟现实技术&#xff0c;将VR和消防教育融合在一起达到寓教于乐的效果&#xff0c; VR消防教育是对于家中、校园内、大型商场、公司办公室等情景产品研发的消防安全培训类VR系统软件&#xff0c;根据互动体验、互动、视角实际操作、视听觉系统多度自然…

什么是域欺骗?域欺骗的主要类型有哪些?

域欺骗是指网络犯罪分子假冒网站名称或电子邮件域来欺骗用户。域欺骗的目的是将恶意电子邮件或网络钓鱼网站伪装成合法电子邮件或网站&#xff0c;诱使用户与之交互。域欺骗就像骗子一样&#xff0c;向人们展示伪造的凭据以获得信任&#xff0c;然后再利用其获得好处。 域欺骗…

photoshop插件开发入门(java,android,ios)

photoshop给我们提供了一个服务&#xff0c;让我们可以通过java或者c#等语言发送JavaScript等脚本给photoshop&#xff0c;让photoshop自动帮我们处理图片。这里的资料主要是如何连接photoshop&#xff0c;如果开发和调试JavaScript脚本。 photoshop 学习资料和sdk&#xff08…

网络工程师网络配置经典例题(一)附有详细注释

一、配置管理IP和Telnet 配置设备管理IP地址后&#xff0c;可以通过管理IP远程登录设备。 &#xff08;1&#xff09;配置管理IP地址 <HUAWEI> system-view [HUAWEI] vlan 5 //创建交换机管理VLAN 5 [HUAWEI-VLAN5] management-vlan [HUAWEI-VLAN5…

一篇博客读懂双向链表

目录 一、双向带头循环链表的格式 二、链表的初始化和销毁 2.1链表的初始化 2.2链表的销毁 三、链表的检查与准备 3.1链表的打印 3.2创建新结点 四、链表增删查改 4.1尾插 4.2尾删 4.3头插 4.4头删 4.5查找 4.6任意位置前插入 4.7删除任意位置 一、双向带…

emq Neuron工业协议采集使用

emq Neuron工业协议采集使用 Neuron 简介 EMQ X Neuron 是运行在各类物联网边缘网关硬件上的工业协议商业化网关软件&#xff0c;支持一站式接入和解析数十种工业协议&#xff0c;并转换成 MQTT 协议接入工业物联网平台。用户可以通过基于 Web 的管理控制台可以实现在线的网关…

leetcode刷题日记:202. Happy Number( 快乐数)和203. Remove Linked List Elements(移除链表元素)

202. Happy Number( 快乐数) 这一题的解决与之前的循环链表比较类似&#xff0c;因为如果不是快乐数的话&#xff0c;在数字变化的过程中必然遇到了数字变换的循环&#xff0c;所以我们需要在变换的过程中判断是否遇到了循环&#xff0c;判断是否在一个序列中存在循环&#xf…

[uni-app] uni.showToast 一闪而过问题/设定时间无效/1秒即逝

toast一闪就消失 1.猜测频繁点击导致 – 排除 2.猜测再定时器内导致-- 排除 3.和封装的接口调用一起导致 - 是改原因 深挖发现: axios封装中, 对loading/hindloading进行了配置, 看来是 showToast 与 loading等冲突导致的 wx.hideLoading(Object object) 解决办法 再封装的…

前端CSS实现响应式TimeLine效果(附源码)

文章目录 纯CSS搭建,先上效果图(附有源码)视图层 index.htmlindex.css 公用样式文件Main.css 主要的样式文件纯CSS搭建,先上效果图(附有源码) 本效果为纯CSS搭建,适配移动端和PC端! 视图层 index.html <body class="light"><header class="he…

Scrum敏捷开发培训团队和组织来说的重要性

Scrum敏捷开发培训对于团队和组织来说是至关重要的&#xff0c;有以下几点&#xff0c;大家可以参考下&#xff1a; 理解敏捷价值观和原则&#xff1a; 培训有助于团队理解敏捷方法背后的核心理念和价值观&#xff0c;包括个体和互动、工作软件、客户合作和响应变化。这有助于建…

智慧商场数字孪生三维可视化大屏管理系统提升品牌形象

聚焦国家战略需求和先进制造业发展方向&#xff0c;加快数字化发展战略部署&#xff0c;数字孪生、工业互联网、工业物联网已被广泛认为是工业革命的新引擎。数字孪生公司正在推动工业制造从制造转向智造。通过数字化建模和仿真的方式&#xff0c;优化设计、生产、质量管理、供…

《opencv实用探索·一》QT+opencv实现图片拼接和Mat转QImage

本文利用opencv实现了几个好用的功能&#xff0c;包含两个文件&#xff0c;如下&#xff1a; 源码放在文章末尾 imageProcessing类包含三个功能&#xff1a; 1、图像拼接 cv::Mat imageMosaic(cv::Mat mat1, cv::Mat mat2, MosaicMode mosaicMode);mat1和mat2为两个待拼接的…