一、概述
责任链模式是一种行为设计模式,它允许多个对象处理一个请求,从而避免了请求的发送者和接收者之间的耦合关系。
优点是把任务划分为一个一个的节点,然后按照节点之间的业务要求、顺序,把一个个节点串联起来,形成一个执行链路,一个节点一个节点向后执行;
把原来一堆代码按照原子性拆分成责任链,耦合降低,可扩展性增强,责任划分清晰;
最近在使用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);}}