文章目录
1 基本介绍
职责链模式(Chain of Responsibility Pattern)是一种 行为型 设计模式,用于 处理 请求发送者 和 处理者 之间的解耦问题,允许一个请求沿着职责链进行传递,直到链上的某个处理者能够处理它为止。这种模式 将多个处理者组织成一条链,每个处理者都包含对另一个处理者的引用,从而实现了请求的 链式传递,有些类似 链表 的结构。
2 案例
本案例实现了 让初学者、专家、高手分别解决问题 的“职责链”,分别让它们解决自己力所能及的问题。
2.1 Problem 类
public class Problem { // 问题
private int no; // 问题的编号
public Problem(int no) {
this.no = no;
}
public int getNo() {
return no;
}
}
2.2 Solver 类
public abstract class Solver { // 问题的解决者
private String name; // 问题解决者的名称
private Solver next; // 下一个问题解决者
public Solver(String name) {
this.name = name;
}
// 设置下一个问题解决者,返回下一个问题解决者,用于支持链式编程
public Solver setNext(Solver next) {
this.next = next;
return next;
}
// 解决问题的步骤,作为「模版方法」被外部调用
public final void solve(Problem problem) {
Solver curr = this; // 当前正在解决问题的解决者
while (curr != null) {
if (curr.resolve(problem)) { // 如果能解决问题
done(problem, curr); // 则解决问题
return; // 并直接返回
}
curr = curr.next; // 否则就将问题推卸给下一个解决者
}
fail(problem); // 没有人能解决这个问题,解决失败
}
// 解决问题的方法,作为「抽象方法」被子类实现
protected abstract boolean resolve(Problem problem);
// 问题被解决
private void done(Problem problem, Solver solver) {
System.out.println("问题[" + problem.getNo() + "] 已被「" + solver.name + "」解决");
}
// 问题无法解决
private void fail(Problem problem) {
System.out.println("问题[" + problem.getNo() + "] 无法被解决");
}
}
2.3 Beginner 类
public class Beginner extends Solver { // 初学者
public Beginner(String name) {
super(name);
}
@Override
protected boolean resolve(Problem problem) {
return problem.getNo() < 100; // 初学者能解决编号小于 100 的问题
}
}
2.4 Expert 类
public class Expert extends Solver { // 专家
private int resolveNo; // 专家解决的问题编号
public Expert(String name, int resolveNo) {
super(name);
this.resolveNo = resolveNo;
}
@Override
protected boolean resolve(Problem problem) {
return problem.getNo() == resolveNo; // 专家只解决其负责的问题
}
}
2.5 Hotshot 类
public class Hotshot extends Solver { // 高手
public Hotshot(String name) {
super(name);
}
@Override
protected boolean resolve(Problem problem) {
return problem.getNo() < 1000; // 高手能解决编号小于 1000 的问题
}
}
2.6 Client 类
public class Client { // 客户端,测试了 初学者、高手和专家的解决问题,了解它们是如何推卸责任的
public static void main(String[] args) {
Solver beginner = new Beginner("初学者");
Solver hotshot = new Hotshot("高手");
Solver expert = new Expert("专家", 500);
// 有问题先找 初学者,然后再找 专家,最后找 高手,形成这样的职责链
beginner.setNext(expert).setNext(hotshot);
// 给出一些问题,让这些解决者来解决
for (int i = 0; i <= 500; i += 10) {
beginner.solve(new Problem(i));
}
}
}
2.7 Client 类的运行结果
问题[0] 已被「初学者」解决
问题[10] 已被「初学者」解决
// 省略了部分「初学者」解决问题的输出
问题[80] 已被「初学者」解决
问题[90] 已被「初学者」解决
问题[100] 已被「高手」解决
问题[110] 已被「高手」解决
// 省略了部分「高手」解决问题的输出
问题[480] 已被「高手」解决
问题[490] 已被「高手」解决
问题[500] 已被「专家」解决
2.8 总结
本案例形成了一条 初学者 -> 专家 -> 高手
的“职责链”,从而能够让每个人都解决力所能及的问题。不过需要注意的是,如果职责链是 高手 -> 专家 -> 初学者
,则 高手(解决编号小于 1000 的问题)会解决 专家(解决编号为 500 的问题)和 初学者(解决编号小于 100 的问题)能解决的所有问题。这就要求 在形成职责链时明确各个角色的能力大小,形成合理的处理顺序。
可以发现,如果这时想再添加一个专家,也十分方便,只需要将其放到职责链中即可;如果这时想再添加一种新的问题处理者,只需要让其继承 Solver
抽象类并实现 resolve()
方法,然后就可以将其对象实例放到职责链中进行处理了。
在本案例中,除了使用 职责链模式 之外,还使用了 模版方法模式:将 solve()
方法作为模版方法,放到抽象父类中;将 resolve()
方法作为抽象方法,让其子类实现。通常情况下,外部应该调用 solve()
方法处理问题。
3 各角色之间的关系
3.1 角色
3.1.1 Request ( 需求 )
该角色负责 定义具体的需求,会被 ConcreteHandler 角色处理。本案例中,Problem
类扮演了该角色。
3.1.2 Handler ( 处理者 )
该角色负责 定义 处理 Request 的 接口,并 保存下一个处理者。当自己处理不了 Request 时,将其推卸给下一个处理者。本案例中,Solver
抽象类扮演了该角色。
3.1.3 ConcreteHandler ( 具体处理者 )
该角色负责 实现 Handler 角色中定义的 接口,处理具体的 Request。本案例中,Beginner, Expert, Hotshot
类都在扮演该角色。
3.1.4 Client ( 客户端 )
该角色负责 向职责链的第一个 ConcreteHandler 发送 Request,然后这个 Request 就会沿着职责链移动,直到被解决或没有一个 ConcreteHandler 能解决。本案例中,Client
类扮演了该角色。
3.2 类图
说明:Handler 角色自己聚合自己,这就很像 链表 这种数据结构,所以在处理 Request 的方法中,可以发现类似于 遍历链表 的代码。
4 注意事项
- 请求与处理的解耦:将 请求的处理 与 请求的发送者 解耦,以提高系统的灵活性和可扩展性。这意味着客户端不需要知道哪个具体的处理者将处理其请求,只需将请求发送到链上即可。
- 链的结构透明性:每个处理者只需知道如何将其无法处理的请求传递给链中的下一个处理者,而无需了解整个链的详细情况。这种透明性有助于降低系统的复杂性,使得每个处理者可以更加专注于其自身的职责。
- 性能考虑:在链比较长的情况下,性能可能会受到影响。因为 每个请求都可能需要遍历整个链才能找到能够处理它的处理者。为了避免性能问题,需要 控制链中的最大节点数量。通常可以在处理者中设置一个 最大节点数量 的限制,并在设置下一个处理者时 检查 是否已经超过这个限制。如果已经超过,则不允许继续建立链,以避免出现超长链无意识地破坏系统性能的情况。
5 在源码中的使用
在 SpringMVC 中,在 请求的分发和拦截 上使用了职责链模式,将多个拦截器连接成一条链,沿着这条链传递请求,直到某个处理者能够处理该请求位置。以下是其对应的角色:
- Request 角色:
HttpServletRequest
类表示从前端发送过来的请求,需要被后端的程序处理。 - Handler 角色:
HandlerInterceptor
接口扮演 Handler 角色,实现它的类可以当作拦截器处理前端的请求。其代码如下:public interface HandlerInterceptor { // 在请求处理之前进行调用(Controller 方法调用之前) default boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { return true; } // 请求处理之后进行调用,但是在视图被渲染之前(Controller 方法调用之后) default void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable ModelAndView modelAndView) throws Exception { } // 在整个请求结束之后被调用,也就是在 DispatcherServlet 渲染了对应的视图之后执行 default void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable Exception ex) throws Exception { } }
- ConcreteHandler 角色:自定义的拦截器,以下是一个例子:
// 请注意:以下两个类的包名与 JDK 的版本有关 import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import org.springframework.web.servlet.HandlerInterceptor; import org.springframework.web.servlet.ModelAndView; public class MyInterceptor implements HandlerInterceptor { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { System.out.println("Pre-handle: Request is being processed by MyCustomInterceptor"); // 决定是否继续传递该请求,返回 true 表示继续传递,返回 false 表示中断传递 return true; } @Override public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { if (modelAndView != null) { // 对 modelAndView 进行操作,比如添加公共的模型数据等 System.out.println("Post-handle: Request has been processed by the handler"); } } @Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { // 主要用于进行资源清理工作,注意:这个方法即使在 Controller 层抛出异常时也会被调用 System.out.println("After-completion: Request has been completed"); } }
此外,在处理请求的过程中还有两个很重要的类:
DispatcherServlet
类:这个类中有一个很关键的方法doDispatch()
,这个方法完成了多个需求,本文只讲 按照顺序调用各个拦截器的方法(其调用的HandlerExecutionChain
的方法在下面展示):protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception { HttpServletRequest processedRequest = request; HandlerExecutionChain mappedHandler = null; // ... try { // ... try { // ... // 获取 mappedHandler mappedHandler = getHandler(processedRequest); if (mappedHandler == null) { noHandlerFound(processedRequest, response); return; } // ... // 调用所有拦截器的 preHandle() 方法 if (!mappedHandler.applyPreHandle(processedRequest, response)) { return; } // ... // 调用所有拦截器的 postHandle() 方法 mappedHandler.applyPostHandle(processedRequest, response, mv); } catch (Exception ex) { dispatchException = ex; } catch (Throwable err) { dispatchException = new ServletException("Handler dispatch failed: " + err, err); } // 在 processDispatchResult(), triggerAfterCompletion() 方法中调用了 // mappedHandler.triggerAfterCompletion(request, response, null); processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException); } catch (Exception ex) { triggerAfterCompletion(processedRequest, response, mappedHandler, ex); } catch (Throwable err) { triggerAfterCompletion(processedRequest, response, mappedHandler, new ServletException("Handler processing failed: " + err, err)); } finally { if (asyncManager.isConcurrentHandlingStarted()) { if (mappedHandler != null) { // 调用所有异步拦截器的 afterConcurrentHandlingStarted() 方法 mappedHandler.applyAfterConcurrentHandlingStarted( processedRequest, response); } } else { // 清理资源 if (multipartRequestParsed) { cleanupMultipart(processedRequest); } } } }
HandlerExecutionChain
类:内部有一个链表来维护 ConcreteHandler 角色形成的职责链,在调用 ConcreteHandler 角色的方法时沿着职责链来调用方法。public class HandlerExecutionChain { private static final Log logger = LogFactory.getLog(HandlerExecutionChain.class); private final Object handler; // 储存 拦截器 的链表 private final List<HandlerInterceptor> interceptorList = new ArrayList<>(); // 拦截器的 private int interceptorIndex = -1; // 省略了多个构造器 // 返回处理请求的 handler public Object getHandler() { return this.handler; } // 将拦截器添加到拦截器链的尾部 public void addInterceptor(HandlerInterceptor interceptor) { this.interceptorList.add(interceptor); } // 省略了两个重载的 addInterceptor() 方法 // 省略了 getInterceptors() 和 getInterceptorList() 方法 // 沿拦截器链,调用所有拦截器的 preHandle() 方法,直到其返回 false 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)) { triggerAfterCompletion(request, response, null); return false; } this.interceptorIndex = i; } return true; } // 沿拦截器链的反方向,调用所有拦截器的 postHandle() 方法 void applyPostHandle(HttpServletRequest request, HttpServletResponse response, @Nullable ModelAndView mv) throws Exception { for (int i = this.interceptorList.size() - 1; i >= 0; i--) { HandlerInterceptor interceptor = this.interceptorList.get(i); interceptor.postHandle(request, response, this.handler, mv); } } // 沿拦截器链的反方向,调用所有拦截器的 afterCompletion() 方法,有异常就打印 void triggerAfterCompletion(HttpServletRequest request, HttpServletResponse response, @Nullable Exception ex) { for (int i = this.interceptorIndex; i >= 0; i--) { HandlerInterceptor interceptor = this.interceptorList.get(i); try { interceptor.afterCompletion(request, response, this.handler, ex); } catch (Throwable ex2) { logger.error("HandlerInterceptor.afterCompletion threw exception", ex2); } } } // 沿拦截器链的反方向,调用所有异步拦截器的 afterConcurrentHandlingStarted() 方法,有异常就打印 void applyAfterConcurrentHandlingStarted(HttpServletRequest request, HttpServletResponse response) { for (int i = this.interceptorList.size() - 1; i >= 0; i--) { HandlerInterceptor interceptor = this.interceptorList.get(i); if (interceptor instanceof AsyncHandlerInterceptor asyncInterceptor) { try { asyncInterceptor.afterConcurrentHandlingStarted(request, response, this.handler); } catch (Throwable ex) { if (logger.isErrorEnabled()) { logger.error("Interceptor [" + interceptor + "] failed in afterConcurrentHandlingStarted", ex); } } } } } // 省略了 toString() 方法 }
6 优缺点
优点:
- 降低耦合度:客户端(即 请求的发送者)不需要知道链上的具体处理者是谁,也不需要知道链的结构。客户端 只需要将请求发送到链上 即可,这大大降低了 客户端 与 具体处理者 之间的 耦合度。
- 增强灵活性:可以 在运行时动态地 增加 或 修改 职责链,以适应不同的业务需求,这种灵活性使得系统更加 易于扩展和维护。
- 符合开闭原则:职责链模式允许在不修改现有代码的情况下,通过增加新的处理者类来扩展系统的功能,这符合 开闭原则(对扩展开放,对修改关闭)。
- 请求处理明确:每个处理者都清晰地知道自己的职责范围,即哪些请求是自己能够处理的,这有助于避免请求的误处理或遗漏。
- 提高代码的可重用性:处理者可以独立存在,并且可以被多个不同的职责链所共享,这提高了代码的 重用性。
缺点:
- 可能导致请求不被处理:如果链上没有处理者能够处理请求,或者链没有被正确配置(例如,形成了循环引用),那么请求可能无法被正确处理。这可能导致系统出现错误或异常。
- 性能问题:对于较长的职责链,每个请求都可能需要遍历整个链才能找到能够处理它的处理者。这可能会增加系统的响应时间,影响性能。
- 增加系统复杂性:使用职责链模式可能会增加系统的复杂性,因为需要管理多个处理者以及它们之间的链接关系。这可能会使系统的理解和维护变得更加困难。
- 调试困难:当请求在链中传递时,可能会经过多个处理者,这可能会使得调试过程变得复杂。需要跟踪请求在链中的传递路径,以确定是哪个处理者出现了问题。
- 过度设计:在某些情况下,如果系统并不需要 高度的灵活性和可扩展性,使用职责链模式可能会导致过度设计。这可能会增加系统的复杂性和开发成本,而不会带来实际的好处。
7 适用场景
- 多级审批流程:在企业管理中,如请假、报销、加薪等申请往往 需要经过多级审批。使用职责链模式,可以将各级审批人员组织成一条链,请求沿着这条链传递,直到被某个审批人员处理或拒绝。
- 日志处理:在软件开发中,日志记录是一个重要的功能。使用职责链模式,可以 将不同类型的日志处理器(如控制台输出、文件写入、远程发送等)组织成一条链,根据日志的级别或类型选择相应的处理器进行处理。
- 事件处理:在图形用户界面(GUI)编程中,事件处理是一个常见的任务。使用职责链模式,可以 将不同的事件处理器(如按钮点击、键盘输入、鼠标移动等)组织成一条链,根据事件的类型和优先级选择相应的处理器进行处理。
- 异常处理:在异常处理中,有时需要根据异常的类型和严重程度将异常传递给不同的处理器。使用职责链模式,可以 将异常处理器组织成一条链,根据异常的类型和条件选择相应的处理器进行处理。
- 中间件处理:在分布式系统或网络应用中,中间件(如消息队列、网关等)经常需要处理来自不同源的数据或请求。使用职责链模式,可以 将不同的中间件处理器组织成一条链,根据数据的类型、来源或目标选择相应的处理器进行处理。
- 责任划分:在某些情况下,系统需要明确划分不同组件或模块的责任范围。使用职责链模式,可以 将这些组件或模块组织成一条链,每个组件或模块只处理自己能够处理的请求,将其他请求传递给链中的下一个组件或模块。
- 插件化架构:在插件化架构中,系统允许在运行时动态地加载和卸载插件。使用职责链模式,可以 将插件组织成一条链,根据插件的优先级或条件选择相应的插件进行处理。
8 总结
职责链模式 是一种 行为型 设计模式,将 请求发送者 和 处理者 解耦,让一个请求沿着职责链传递,直到链上的某个处理者能够处理它为止,类似于 链表。这样可以灵活地更换职责链上的处理者,并且也方便添加一种新的处理者。但是,如果职责链上的处理者太多,就会影响性能,一般要控制处理者的数量。此外,如果能确保职责链不会变化,尽量不要使用 职责链模式,而是采用 硬编码 的形式将处理者编排到一起,这样有利于提高系统的性能。