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

一、概述

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

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

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

最近在使用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中已经固化,

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

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

}
相关推荐
Q_1928499906几秒前
基于Spring Boot的建材租赁系统
java·spring boot·后端
鲨鱼辣椒不吃辣c2 分钟前
拦截器魔法:Spring MVC中的防重放守护者
java·spring·mvc
winks38 分钟前
Spring Task的使用
java·后端·spring
秋意钟19 分钟前
Spring新版本
java·后端·spring
椰椰椰耶21 分钟前
【文档搜索引擎】缓冲区优化和索引模块小结
java·spring·搜索引擎
mubeibeinv22 分钟前
项目搭建+图片(添加+图片)
java·服务器·前端
青莳吖23 分钟前
Java通过Map实现与SQL中的group by相同的逻辑
java·开发语言·sql
Buleall31 分钟前
期末考学C
java·开发语言
重生之绝世牛码33 分钟前
Java设计模式 —— 【结构型模式】外观模式详解
java·大数据·开发语言·设计模式·设计原则·外观模式
小蜗牛慢慢爬行39 分钟前
有关异步场景的 10 大 Spring Boot 面试问题
java·开发语言·网络·spring boot·后端·spring·面试