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

news2024/11/17 20:20:09

一、概述

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

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

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

最近在使用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);
    }

}

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

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

相关文章

Vulnhub 解决虚拟机网络问题

前言&#xff1a; 有的时候&#xff0c;我们从vulnhub官网下载ovf文件导入到虚拟机后&#xff0c;使用扫描器扫描存活的时候发现扫不到靶机的IP&#xff0c;这是因为虚拟机的网卡配置有问题。我们需要进安全模式修改一些配置。 1. 在虚拟机开机的时候按一下上下键&#xff0c;让…

全国的科技创新情况数据分享,涵盖2020-2022年三年情况

随着国家对科技创新的重视和大力支持&#xff0c;全国的科技创新情况越来越受到关注。 我们根据中国城市统计年鉴的这方面指标&#xff0c;分析汇总得出全国科技创新情况数据&#xff0c;需要说明的是&#xff0c;由于统计年鉴指标调整&#xff0c;每一年的数据并非字段相同&a…

Ubuntu Server download

前言 Ubuntu——公共云、数据中心和边缘上最受欢迎的 Linux 发行版。自成立以来&#xff0c;Ubuntu 一直在获得市场份额&#xff0c;截至今天已接近 50%。 Ubuntu Server download VersionUbuntu Server 其它主机型号版本Ubuntu AMD历史版下载百度云Ubuntu Server all Ubuntu…

onnx模型转换opset版本和固定动态输入尺寸

背景&#xff1a;之前我想把onnx模型从opset12变成opset12&#xff0c;太慌乱就没找着&#xff0c;最近找到了官网上有示例的&#xff0c;大爱onnx官网&#xff0c;分享给有需求没找着的小伙伴们。 1. onnx模型转换opset版本 官网示例&#xff1a; import onnx from onnx im…

python3函数

1、定义函数 函数代码块以def关键词开头&#xff0c;后接函数标识符名称和圆括号任何传入参数和自变量必须放在圆括号中间&#xff0c;圆括号之间可以用于定义参数函数内容以冒号&#xff1a;起始&#xff0c;并且缩进return【表达式】结束函数&#xff0c;选择性返回一个值调…

【数据结构(三)】双向链表(2)

文章目录 1. 基本概念2. 管理双向链表的思路3. 代码实现 1. 基本概念 管理单向链表的缺点分析: ①单向链表&#xff0c;查找的方向只能是一个方向&#xff0c;而双向链表可以向前或者向后查找。     ②单向链表不能自我删除&#xff0c;需要靠辅助节点 &#xff0c;而双向…

Ubuntu18.04安装LeGO-LOAM保姆级教程

系统环境&#xff1a;Ubuntu18.04.6 LTS 1.LeGO-LOAM的安装前要求&#xff1a; 1.1 ROS安装&#xff1a;参考我的另一篇博客Ubuntu18.04安装ROS-melodic保姆级教程_灬杨三岁灬的博客-CSDN博客文章浏览阅读168次。Ubuntu18.04安装ROS-melodic保姆级教程https://blog.csdn.net/…

拓扑排序-

有向无环图是拓扑排序 拓扑排序将图中所有的顶点排成一个线性序列&#xff0c;使得所有的有向边均从序列的前面指向后面。 拓扑排序使用深度优先搜索来实现&#xff0c;图中有环则无法进行拓扑排序 一个有向图&#xff0c;如果图中有入度为0的点&#xff0c;就把这个点删掉…

最全面的SHEIN开店流程,手把手教你从零起步,轻松开店!

SHEIN作为一家全球性的时尚电商平台&#xff0c;为年轻人提供了更多时尚选择和机会&#xff0c;同时也吸引了众多跨境电商卖家的关注。在5月份&#xff0c;SHEIN推出了第三方卖家平台&#xff0c;为卖家提供了全新的商机和发展赛道。毕竟目前SHEIN平台的流量是非常大的&#xf…

机器学习第11天:降维

文章目录 机器学习专栏 主要思想 主流方法 投影 二维投射到一维 三维投射到二维 流形学习 PCA主成分分析 介绍 代码 内核PCA 具体代码 LLE 结语 机器学习专栏 机器学习_Nowl的博客-CSDN博客 主要思想 介绍&#xff1a;当一个任务有很多特征时&#xff0c;我们…

【ISP】噪声--sensor(2)

1.热噪声 也叫KT/C噪声&#xff0c;或者叫暗电流噪声。电子的热运动的导致&#xff0c;温度上升&#xff0c;噪声增大。 2.FPN固定模式噪声 由于每个像素点的元器件制造的会有偏差&#xff0c;也就是这些器件的工作参数相对理论值的漂移就构成一种固定模式噪声。 3.光子散粒噪…

CHINTERGEO2023中国测绘地理信息技术装备展览会,大势智慧在3010展台期待您的莅临!

11月27日-11月29日 CHINTERGEO2023中国测绘地理信息技术装备展览会 二层-HALL3展厅-3010 大势智慧携符合信创要求的实景三维软硬件全流程解决方案 为您带来一场全国产、真安全的实景三维新型智能测绘装备盛宴 期待您的莅临&#xff01;

Vue3 customRef自定义ref 实现防抖

防抖就是防止在input 框中每输入一个字符就要向服务器请求一次&#xff0c;只要在用户输入完成过一段时间再读取用户输入的内容就能解决这个问题&#xff0c;减小服务器的压力。 1. 自定义ref是一个函数&#xff0c;可以接受参数。 比如我们自定义一个myRef&#xff1a; setu…

LeetCode【45】跳跃游戏2

题目&#xff1a; 思路&#xff1a; 注意和跳跃游戏【55】不同的是&#xff0c;题目保证可以跳到nums[n-1];那么每次跳到最大即可 代码&#xff1a; public class LeetCode45 {public static int jump(int[] nums) {int jumps 0;int currentEnd 0;int farthest 0;for(int…

Postman的各种参数你都用对了吗?

大家好&#xff0c;我是G探险者。 Postman我们都不陌生&#xff0c;作为一个广泛使用的 HTTP 客户端&#xff0c;平时我们使用它来测试接口&#xff0c;无非就是把接口的url放进去&#xff0c;然后根据请求类型get或者post,在不同位置传一下参数&#xff0c;除了常见的 Params…

linux(nginx安装配置,tomcat服务命令操作)

首先进系统文件夹 /usr/lib/systemd/systemLs | grep mysql 查看带有命名有MySQL的文件夹修改tomcat.service文件复制jdk目录替换成我们的路径替换成我们的路径进入这个目录&#xff0c;把修改好的文件拖到我们的工具里面重新刷新系统 systemctl daemon-reload查看tomcat状态…

2022最新版-李宏毅机器学习深度学习课程-P51 BERT的各种变体

之前讲的是如何进行fine-tune&#xff0c;现在讲解如何进行pre-train&#xff0c;如何得到一个pre train好的模型。 CoVe 其实最早的跟预训练有关的模型&#xff0c;应该是CoVe&#xff0c;是一个基于翻译任务的一个模型&#xff0c;其用encoder的模块做预训练。 但是CoVe需要…

解析SOLIDWORKS教育版与企业版:选择合适版本,助力创新设计

SOLIDWORKS作为领先的三维CAD软件&#xff0c;旨在为工程设计、产品开发和创新提供全面支持。在SOLIDWORKS产品线中&#xff0c;教育版和企业版是两种常见的版本。让我们来了解一下它们之间的区别和特点。 SOLIDWORKS教育版&#xff1a;学习、探索、启发创新 面向教育和学术&…

KyLin离线安装OceanBase

去OceanBase下载若干文件 1 首先安装ob-deploy-2.3.1-2.el7.x86_64.rpm rpm -ivh ob-deploy-2.3.1-2.el7.x86_64.rpm# 运行此命令的时候他会报错 RPM should not be used directly install RPM packages, use Alien instead! 这个需要用Alien去转换为deb的包&#xff0c;不…

美国DDoS服务器:如何保护你的网站免遭攻击?

​  在当今数字化时代&#xff0c;互联网已经成为人们生活中不可或缺的一部分。随着互联网的普及和发展&#xff0c;网络安全问题也日益严重。其中&#xff0c;DDoS攻击是目前最常见和具有破坏性的网络攻击之一。那么&#xff0c;如何保护你的网站免遭DDoS攻击呢?下面将介绍…