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