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

一、概述

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

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

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

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

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

二、责任链的写法

2.1 常用的责任链写法

在这里插入图片描述

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

java">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方法:

java">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);
        } 
        
    }
}

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

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

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

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

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

在这里插入图片描述

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

java">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:

java">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接口:

异常请求日志打印组件:

java">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;
    }
}

异常分类细化处理组件

java">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信息组件

java">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;
    }
}

异常响应结果回写组件

java">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异常捕获处理,在这里构造最终的责任调用链,代码如下:

java">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.niftyadmin.cn/n/5197521.html

相关文章

腾讯云COS+picgo+typora 图床搭建与自动上传

1、腾讯云 COS 腾讯云活动 COS新用户专享 COS 操作步骤 1、点击 创建桶&#xff0c;完善信息 点击下一步&#xff0c;剩下的配置可自己配置 2、picgo 官网地址 2.3.1版本下载地址 现在稳定版本是2.3.1 相关连接 腾讯云密钥设置地址picgo官网地址2.3.1版本下载地址

Django学习日志08

如何开启事务 事务的目的&#xff1a;为了保证多个SQL语句执行成功&#xff0c;执行失败&#xff0c;前后保持一致&#xff0c;保证数据安全 ACID属性&#xff1a; A&#xff1a;原子性&#xff08;Atomicity&#xff09;&#xff1a;指事务是原子的&#xff0c;对事务中的操…

2023年亚太杯数学建模思路 - 案例:感知机原理剖析及实现

文章目录 1 感知机的直观理解2 感知机的数学角度3 代码实现 4 建模资料 # 0 赛题思路 &#xff08;赛题出来以后第一时间在CSDN分享&#xff09; https://blog.csdn.net/dc_sinor?typeblog 1 感知机的直观理解 感知机应该属于机器学习算法中最简单的一种算法&#xff0c;其…

著名的勃艮第葡萄酒是如何分类的?

勃艮第代表了与他们的地理位置密切相关的所有葡萄酒和葡萄酒风格&#xff0c;1936年法国根据产地对勃艮第葡萄酒进行了分类&#xff0c;勃艮第地区内的100个被批准的葡萄酒种植区被界定&#xff0c;这些地块被分为四个等级&#xff0c;最高等级代表了种植最高品质葡萄酒的最佳土…

PHP 数据类型转换学习资料

PHP 数据类型转换 在 PHP 中&#xff0c;您可以使用内置的类型转换函数来将一个数据类型转换为另一个数据类型。这些函数可以帮助您在程序中进行数据类型的转换和操作。以下是一些常用的 PHP 数据类型转换函数&#xff1a; 1. 转换为字符串类型 (string) $variable&#xff…

第二章:String类

系列文章目录 文章目录 系列文章目录前言一、String类1.1 String 类的理解和创建对象1.2 创建 String 对象的两种方式1.3 两种创建 String 对象的区别 二、字符串的特性三、String 类的常见方法总结 前言 Sting类是常量字符串的包装类。 一、String类 1.1 String 类的理解和创…

JAXB:根据Java文件生成XML schema文件

说明 JAXB有个schemagen脚本&#xff0c;可以根据Java文件生成XML schema。这个工具在JAXB独立发布包中有&#xff0c;可以从官网下载JAXB的独立发布包&#xff1a; https://eclipse-ee4j.github.io/jaxb-ri/ 示例 使用schemagen -d <path> <java files>格式 …

azkaban二次开发

springboot封装azkaban的api&#xff0c;提供可调用azkaban任务流的接口 流程如下&#xff1a; springboot接口->azkaban api->azkaban project(flow tasks)->shell脚本->spark tasks Api测试 curl -k -X POST --data "actionlogin&usernameazkaban&am…