GoF责任链
使多个对象都有机会处理请求,从而避免请求的发送者和接收者耦合。将这些对象连成一条链,沿着链传递请求,直到有一个对象处理它。
每个节点都有一个相同的结构:决定返回还是继续传递。可以交由一个统一的责任链基类处理,如下
public abstract class ChainHandler<C, R> {
private ChainHandler<C, R> next;
/** 拼链:a.andThen(b).andThen(c);返回 next 支持连续拼接 */
public ChainHandler<C, R> andThen(ChainHandler<C, R> next) {
this.next = Objects.requireNonNull(next);
return next;
}
/** 模板:触发方法 */
public final R handle(C ctx) {
R result = doHandle(ctx);
if (result != null) {
return result;
}
if (next != null) {
return next.handle(ctx);
}
return defaultResult(ctx); // 整条链无人认领
}
// 节点业务语义钩子,节点唯一要实现的方法
protected abstract R doHandle(C ctx);
// 链尾兜底,默认 null 空对象
protected R defaultResult(C ctx) {
return null;
}
} 节点自己决定业务结束还是去下一个节点,但节点间顺序和驱动一般被解耦出去。
节点负责实现doHandle(),还需要一个配置类利用 andThen()控制顺序。
@Bean
ChainHandler<ApprovalRequest, ApprovalResult> approvalChain(A a,B b,C c) {
a.andThen(b).andThen(c); // 拼链
return a; // 返回链头,表达式结果是return next 也就是c,我们需要链头。
}最简实现甚至不用责任链基类,定义一个接口,然后在入口注入List<责任接口>,然后for循环触发即可,可用@Order去控制顺序,可以简单理解为策略+foreach。
责任链节点独立驱动
上面是经典版,节点间顺序是交由编排中心处理的。但是有些情况下节点数量不多,但节点业务复杂,顺序有点难以维护,甚至节点间顺序并不固定,节点并不明确。
比如说agent驱动的情况,规划-执行-反馈。 其中反馈有可能需要返回重新规划,也可能直接输出答案,也可能抵达最大轮数返回答案。将所有节点都交由编排中心不合适。那么节点自己指定下一个节点就很方便。
可以看作一种责任链的变种/演化,本质上类似状态机。
以下是责任链基类:
public abstract class AbstractStrategyRouter<T, D, R> implements StrategyMapper<T, D, R>, StrategyHandler<T, D, R> {
protected StrategyHandler<T, D, R> defaultStrategyHandler;
public AbstractStrategyRouter() {
this.defaultStrategyHandler = StrategyHandler.DEFAULT;
}
public R router(T requestParameter, D dynamicContext) throws Exception {
StrategyHandler<T, D, R> strategyHandler = this.get(requestParameter, dynamicContext);
return (R)(null != strategyHandler ? strategyHandler.doApply(requestParameter, dynamicContext) : this.defaultStrategyHandler.doApply(requestParameter, dynamicContext));
}
/** 节点实现该方法,实现节点任务,并且在最后要去调用router(),router()会去调get获得下一个bean,并调用下一个bean的doApply()。*/
public abstract R doApply(T var1, D var2) throws Exception;
}
public interface StrategyMapper<T, D, R> {
/** 需实现该方法,返回下一个bean */
StrategyHandler<T, D, R> get(T var1, D var2) throws Exception;
}
public interface StrategyHandler<T, D, R> {
StrategyHandler DEFAULT = (T, D) -> null;
R doApply(T var1, D var2) throws Exception;
} 责任链around版 - Filter
jakarta.servlet 包下的 Filter和 FilterChain。都有个doFilter方法。 Filter的doFilter是指执行节点自身的过滤业务。FilterChain的doFilter是指责任链 流程的编排。
看起来是非常直接的责任链,但是和八股直接描述的,还有普通使用的责任链还是很不同的。
FilterChain 的 doFilter伪代码
void doFilter(A req,B res){
// i 节点游标,控制节点的位置。filters,节点数组,类似于andThen链表。
Filter filter = filters[i++];
// 关键点:this,让 filter持有chain,chain持有下一节点,简单责任链 里 下一个节点是 自己持有的
filter.doFilter(req,res,this);
} Filter的doFilter伪代码
void doFilter(A req,B res,C chain){
try{
dosthOrReturn(req,res);
chain.doFilter(req,res); // 回调chain,让链去到下一个节点。
}
finally {
dosth(req,res);
}
} 也就是说他不是那种一个Chain里面直接foreach驱动去每一个节点,而是链去调节点,然后将链自己给节点,节点再调用链。
其实这就是利用栈的特点,使得节点可以同时过滤前后。这个思路以一种非常灵活的方式实现了around式的执行:
- 节点1 执行完过滤后,chain.doFilter(); finally并没被执行 入栈
- chain发现节点没了,进入主业务
- 节点1 出栈,继续执行后续 finally 过滤逻辑
当然很容易让人想到,这个全部由chain来人为编排也是可以的,但是那样同一个业务语义的过滤被拆分为了两个,维护成本较高。Spring的拦截器就是这种思路
拓展 Spring的拦截器
spring 拦截器需实现HandlerInterceptor 接口,就三个方法,方法前;方法执行成功后;不管如何都会最后被执行。
而 DispatcherServlet 使用 HandlerExecutionChain 来编排这三个方法。
以下是 DispatcherServlet#doDispatch 经过我删改整理的和责任链有关的源码
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
// 拿到拦截器链。HandlerExecutionChain是个普通对象,不是bean。
HandlerExecutionChain mappedHandler = getHandler();
try {
try {
// 前置拦截器,代码在下面
if (!mappedHandler.applyPreHandle(processedRequest, response)) {
return;
}
// Actually invoke the handler.
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
mappedHandler.applyPostHandle(processedRequest, response, mv);
} catch (Exception ex) {
dispatchException = ex;
}
// 处理结果最后会调一下triggerAfterCompletion,结合catch保证无论成不成都要调一下triggerAfterCompletion
processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
} catch (Exception ex) {
mappedHandler.triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
}
} 而 HandlerExecutionChain 则是需要维护游标和拦截器list。以下是applyPreHandle 方法的源码
boolean applyPreHandle(HttpServletRequest request, HttpServletResponse response) throws Exception {
for (int i = 0; i < this.interceptorList.size(); i++) {
HandlerInterceptor interceptor = this.interceptorList.get(i);
if (!interceptor.preHandle(request, response, this.handler)) {
// 被拦截也会去调afterCompletion,且该方法的for循环为(int i = this.interceptorIndex; i >= 0; i--)
// 也就是说只去执行已经遍历到过的拦截器
triggerAfterCompletion(request, response, null);
return false;
}
this.interceptorIndex = i;
}
return true;
}