走进 Gang of Four 设计模式:责任链模式
说明:不仅告诉你"怎么用",更告诉你"为什么这样设计"
前置知识:Java 面向对象基础(抽象类、多态、链表结构)
📊 设计模式分类总览
GoF 23 种设计模式可按两个维度交叉分类:类/对象(处理方式) ×创建型/结构型/行为型(目的)。
| 维度 | 创建型(Creational) | 结构型(Structural) | 行为型(Behavioral) |
|---|---|---|---|
| 类(Class) (通过继承复用) | Factory Method 工厂方法 | Adapter(类适配器) | Interpreter(解释器) Template Method(模板方法) |
| 对象(Object) (通过组合/聚合复用) | Abstract Factory(抽象工厂) Builder(建造者) Prototype(原型) Singleton(单例) | Adapter(对象适配器) Bridge(桥接) Composite(组合) Decorator(装饰) Facade(外观) Flyweight(享元) Proxy(代理) | Chain of Resp.(责任链) Command(命令) Iterator(迭代器) Mediator(中介者) Memento(备忘录) Observer(观察者) State(状态) Strategy(策略) Visitor(访问者) |
📑 目录
- 模式概述 · 15 问深度分析
- 框架源码实战分析
- Servlet Filter 责任链源码拆解
- MyBatis InterceptorChain 源码拆解
- Spring Security FilterChain 源码拆解
- 深度追问:Why / How / Trade-off / Evolution / Modern Practice
- 总结
1. 模式概述 · 15 问深度分析
Q1: 为什么需要这个模式?它解决了什么问题?
在软件开发中,经常会出现一个请求需要经过多个环节处理的场景(例如:权限校验 → 数据脱敏 → 日志记录 → 业务核心处理)。
如果让调用者直接知道所有处理节点,调用者就必须耦合所有的处理逻辑和流转规则。责任链模式正是为了解决请求发送者与多个请求处理者之间的紧耦合问题。它将处理者连成一条链,让请求沿着这条链传递,直到有对象处理它为止,或者每个节点都参与处理。
核心矛盾:"确定性的调用流"与"不确定的处理者"之间的矛盾。
Q2: 如果不用这个模式,会有什么缺陷?
不用责任链模式时,通常会采用硬编码的if-else或switch-case逻辑。
// ❌ 硬编码——新增处理者需要改源码publicvoidhandle(Requestrequest){if(request.hasPermission()){// 权限校验通过if(request.needsLogging()){log.info("处理请求: "+request);}// 核心业务处理process(request);// 发送通知notifyUser(request);}}缺陷:
- 违反开闭原则:新增或删除一个处理环节时,必须修改核心类的代码。
- 硬编码流程:处理顺序和逻辑在编译期就固定了,无法在运行时动态调整。
- 代码臃肿:一个方法中充斥着多个处理阶段,难以维护和测试。
Q3: 核心思想是什么?一句话如何概括?
- 核心思想:将请求沿着处理者链传递,每个处理者决定是否处理或传递给下一个。
- 一句话概括:为请求创建一条接收者对象的链,沿着这条链传递请求,直到有一个处理者处理它。
Q4: 包含哪些角色?每个角色的职责是什么?
责任链模式包含以下 3 个核心角色:
- Handler(抽象处理者):定义处理请求的接口,通常包含一个处理方法和一个指向下一个处理者的引用。
- ConcreteHandler(具体处理者):实现 Handler 接口,判断是否能处理当前请求。如果能则自己处理,如果不能则调用
nextHandler.handle()传递给下一个。 - Client(客户端):组装责任链,向链头发送请求。
Q5: 它们之间如何协作?调用流程是怎样的?
标准调用流程:
- 组装阶段:客户端将处理者 A、B、C 串联成链:A → B → C。
- 请求发起:客户端向链头(A)发送请求。
- 流转处理:
- A 处理或传递给 B
- B 处理或传递给 C
- C 处理或终止
- 结果返回:请求被某节点处理后返回结果,或到达链尾未被处理。
Q6: 为什么要这样设计?每个角色存在的意义是什么?
- Handler 的意义:定义了统一的处理接口,让客户端无需关心链中的具体处理者。
- ConcreteHandler 的意义:每个处理者只关心自己能处理的那部分逻辑,实现了关注点分离。
- Client 的意义:负责组装链——可以在运行时动态决定链的组成和顺序。
Q7: 为什么使用接口、抽象类、组合,而不是其他方式?
- 为什么用抽象类?因为 Handler 需要持有下一个处理者的引用,并且通常有一个默认的转发逻辑(
if (next != null) next.handle()),抽象类可以统一实现这个逻辑。 - 为什么用组合(链表结构)?链表可以在运行时动态添加、删除或调整处理者的顺序。如果用数组或列表硬编码,灵活性大打折扣。
Q8: 这种设计符合哪些面向对象原则?
- 开闭原则(OCP):极佳。新增处理者只需新建一个 ConcreteHandler 并接入链中,无需修改任何现有代码。
- 单一职责原则(SRP):每个具体处理者只负责一种处理逻辑(如权限校验、日志记录),职责非常单一。
- 依赖倒置原则(DIP):客户端和具体处理者都依赖抽象的 Handler 接口。
Q9: 它有哪些优点和局限性?会带来哪些额外成本?
优点:
- 降低耦合:请求发送者和接收者之间完全解耦。
- 动态灵活:可以在运行时动态新增、删除或调整处理者顺序。
- 符合单一职责:每个处理者只关心自己的逻辑。
局限性/额外成本:
- 请求可能不被处理:如果链的末端没有兜底处理者,请求可能被丢弃。
- 调试困难:运行时调用链不直观,需要跟踪链的流转。
- 性能影响:请求可能经过多个处理者才被处理,链太长影响性能。
Q10: 它最适合哪些场景?哪些场景不应该使用?
最适合的场景:
- 有多个对象可以处理请求,具体由谁处理由运行时决定。
- 需要动态指定或调整处理者链的顺序(如按配置决定 Filter 顺序)。
- 向多个对象中的一个提交请求,不想明确指定接收者。
不应该使用的场景:
- 处理者只有 1-2 个且固定不变时,用简单的 if-else 更轻量。
- 请求的处理路径需要在编译期就严格确定时。
Q11: 它与容易混淆的其他设计模式有什么区别和联系?
与装饰器模式的区别:
| 对比维度 | 责任链模式 | 装饰器模式 |
|---|---|---|
| 请求流向 | 链式传递,可能由链中某个节点终止 | 层层包装,每个装饰器都执行 |
| 处理方式 | 请求最多被一个处理者处理(或全部处理) | 每个装饰器都增强功能 |
| 结构 | 链表结构 | 嵌套结构 |
| 典型应用 | FilterChain、MyBatis Plugin | Java IO Stream |
与观察者模式的区别:
- 观察者模式是 Subject 主动广播给所有 Observer(一对多)。
- 责任链模式是请求沿着链传递,直到有 Handler 处理它(一对一)。
Q12: 框架中有哪些典型应用?
- Tomcat / Servlet 中的
FilterChain:在请求到达 Servlet 之前,经过一系列 Filter,层层过滤 Request/Response。 - MyBatis 插件机制(InterceptorChain):多个 MyBatis 插件构成责任链,依次拦截 SQL 执行。
- Spring Security 中的
SecurityFilterChain:一组安全过滤器链,按顺序对 HTTP 请求进行安全处理。 - Netty 的
ChannelPipeline:出站/入站处理器的责任链。
Q13: 如何从零实现一个最小可运行版本?
以日志级别过滤器为例:
// 1. Handler 抽象处理者publicabstractclassAbstractLogger{publicstaticintINFO=1;publicstaticintDEBUG=2;publicstaticintERROR=3;protectedintlevel;protectedAbstractLoggernextLogger;// 指向下一个处理者publicvoidsetNextLogger(AbstractLoggernextLogger){this.nextLogger=nextLogger;}publicvoidlogMessage(intlevel,Stringmessage){if(this.level<=level){write(message);}if(nextLogger!=null){nextLogger.logMessage(level,message);// 传递给下一个}}abstractprotectedvoidwrite(Stringmessage);}// 2. ConcreteHandler 具体处理者publicclassConsoleLoggerextendsAbstractLogger{publicConsoleLogger(intlevel){this.level=level;}@Overrideprotectedvoidwrite(Stringmessage){System.out.println("Standard Console::Logger: "+message);}}publicclassFileLoggerextendsAbstractLogger{publicFileLogger(intlevel){this.level=level;}@Overrideprotectedvoidwrite(Stringmessage){System.out.println("File::Logger: "+message);}}publicclassErrorLoggerextendsAbstractLogger{publicErrorLogger(intlevel){this.level=level;}@Overrideprotectedvoidwrite(Stringmessage){System.out.println("Error Console::Logger: "+message);}}// 3. Client 组装链publicclassChainPatternDemo{privatestaticAbstractLoggergetChainOfLoggers(){AbstractLoggererrorLogger=newErrorLogger(AbstractLogger.ERROR);AbstractLoggerfileLogger=newFileLogger(AbstractLogger.DEBUG);AbstractLoggerconsoleLogger=newConsoleLogger(AbstractLogger.INFO);errorLogger.setNextLogger(fileLogger);fileLogger.setNextLogger(consoleLogger);returnerrorLogger;// 返回链头}publicstaticvoidmain(String[]args){AbstractLoggerloggerChain=getChainOfLoggers();loggerChain.logMessage(AbstractLogger.INFO,"This is an information.");loggerChain.logMessage(AbstractLogger.DEBUG,"This is a debug level information.");loggerChain.logMessage(AbstractLogger.ERROR,"This is an error information.");}}Q14: 如何在现有项目中识别可以使用该模式的地方?
如果出现以下信号,可以考虑重构为责任链模式:
- 代码中频繁出现链式的
if-else if-else if或switch,且每个分支的处理逻辑不同。 - 一个方法中包含了多个阶段的处理(权限校验 → 日志 → 核心业务 → 通知),且阶段可能经常增减。
- 需要根据配置或运行时条件动态决定请求的处理路径。
Q15: 如果重新设计这个模式,在今天会有哪些改进?
在现代 Java(Java 8+)中,函数式接口(Function、Consumer)可以替代部分责任链节点,消除处理者类的数量:
// 函数式责任链publicclassFunctionalChain{privatefinalList<Consumer<Request>>handlers=newArrayList<>();publicFunctionalChainaddHandler(Consumer<Request>handler){handlers.add(handler);returnthis;}publicvoidexecute(Requestrequest){for(Consumer<Request>handler:handlers){handler.accept(request);}}}// 使用时无需创建 Handler 子类newFunctionalChain().addHandler(req->System.out.println("权限校验")).addHandler(req->System.out.println("日志记录")).addHandler(req->System.out.println("核心业务")).execute(request);2. 框架源码实战分析
2.1 Servlet FilterChain 源码拆解
Servlet 的 FilterChain 是责任链模式最典型的工业级应用(结合了模板方法模式)。
场景背景:在 HTTP 请求到达 Servlet 之前,需要经过一系列 Filter(如编码过滤、登录校验、XSS 防护)。这些 Filter 的顺序通常由web.xml或注解配置决定,可以在运行时动态调整。
核心源码:
// Tomcat 中 FilterChain 的核心实现publicclassApplicationFilterChainimplementsFilterChain{// 内部维护过滤器数组privateApplicationFilterConfig[]filters=newApplicationFilterConfig[0];privateintpos=0;// 当前执行位置(指针)privateServletservlet;// 目标 Servlet@OverridepublicvoiddoFilter(ServletRequestrequest,ServletResponseresponse){if(pos<filters.length){// 取出当前过滤器(按顺序)ApplicationFilterConfigfilterConfig=filters[pos++];Filterfilter=filterConfig.getFilter();// 将自身(this)作为 FilterChain 传入// 这样在 Filter 中可以调用 chain.doFilter() 继续传递filter.doFilter(request,response,this);}else{// 所有过滤器执行完毕,调用目标 Servletservlet.service(request,response);}}}// 开发者自定义 Filter@WebFilter("/*")publicclassLoggingFilterimplementsFilter{publicvoiddoFilter(ServletRequestrequest,ServletResponseresponse,FilterChainchain)throwsIOException,ServletException{// 前置处理System.out.println("请求前 - "+System.currentTimeMillis());// 调用下一个 Filter(关键!)chain.doFilter(request,response);// 后置处理System.out.println("请求后 - "+System.currentTimeMillis());}}为什么这样设计?每个 Filter 通过chain.doFilter()将控制权交回给ApplicationFilterChain,chain 再调用下一个 Filter 或最终的 Servlet。这种设计实现了"过滤器链"的职责链模式,同时每个 Filter 可以在调用前后添加逻辑,又体现了模板方法模式。
2.2 MyBatis InterceptorChain 源码拆解
MyBatis 的插件机制本质上是责任链模式的应用。
场景背景:MyBatis 允许开发者编写插件来拦截四大核心对象的方法调用(Executor、StatementHandler、ParameterHandler、ResultSetHandler)。多个插件构成一条责任链,依次执行拦截逻辑。
核心源码:
// MyBatis 中的拦截器链publicclassInterceptorChain{privatefinalList<Interceptor>interceptors=newArrayList<>();// 注册拦截器publicObjectpluginAll(Objecttarget){// 遍历所有拦截器,依次包装目标对象// 注意:这是责任链模式的一种变体——先注册的在内层for(Interceptorinterceptor:interceptors){target=interceptor.plugin(target);}returntarget;}publicvoidaddInterceptor(Interceptorinterceptor){interceptors.add(interceptor);}}// 自定义 MyBatis 插件@Intercepts({@Signature(type=Executor.class,method="query",args={MappedStatement.class,Object.class,RowBounds.class,ResultHandler.class})})publicclassExamplePluginimplementsInterceptor{@OverridepublicObjectintercept(Invocationinvocation)throwsThrowable{// 前置处理System.out.println("SQL 执行前");// 调用下一个拦截器或目标方法Objectresult=invocation.proceed();// 后置处理System.out.println("SQL 执行后");returnresult;}@OverridepublicObjectplugin(Objecttarget){// 用 Plugin.wrap 来生成代理returnPlugin.wrap(target,this);}}2.3 Spring Security FilterChain 源码拆解
Spring Security 的安全过滤机制是责任链模式在企业级安全框架中的顶级应用。
场景背景:Spring Security 通过一组SecurityFilterChain来保护 Web 应用。每个 FilterChain 包含多个安全过滤器(如UsernamePasswordAuthenticationFilter、ExceptionTranslationFilter、FilterSecurityInterceptor),按照特定顺序执行。
核心源码:
// Spring Security 的过滤器链管理器publicclassFilterChainProxyextendsGenericFilterBean{// 持有多个 SecurityFilterChain(责任链数组)privateList<SecurityFilterChain>filterChains;@OverridepublicvoiddoFilter(ServletRequestrequest,ServletResponseresponse,FilterChainchain)throwsIOException,ServletException{// 遍历所有 SecurityFilterChain,找到匹配当前请求的第一个for(SecurityFilterChainfilterChain:filterChains){if(filterChain.matches(request)){// 执行匹配的 SecurityFilterChain 中的所有 FilterfilterChain.getFilters().doFilter(request,response,chain);return;}}// 没有匹配的 chain,直接放行chain.doFilter(request,response);}}// 配置多个安全过滤链@Configuration@EnableWebSecuritypublicclassSecurityConfig{@BeanpublicSecurityFilterChainapiFilterChain(HttpSecurityhttp)throwsException{http.securityMatcher("/api/**")// 只匹配 /api/ 路径.authorizeHttpRequests(auth->auth.anyRequest().authenticated()).httpBasic(Customizer.withDefaults());returnhttp.build();}@BeanpublicSecurityFilterChainwebFilterChain(HttpSecurityhttp)throwsException{http.securityMatcher("/web/**")// 匹配 /web/ 路径.authorizeHttpRequests(auth->auth.requestMatchers("/login").permitAll().anyRequest().authenticated()).formLogin(Customizer.withDefaults());returnhttp.build();}}三个框架对比:
| 框架 | 组装方式 | 传递机制 | 终止条件 |
|---|---|---|---|
| Servlet Filter | web.xml/注解顺序 | chain.doFilter()手动传递 | 全部执行完 → Servlet |
| MyBatis Plugin | interceptors.add()注册 | invocation.proceed()内层外层的包装 | 全部执行完 → 目标方法 |
| Spring Security | 代码配置 chain 顺序 | 自动匹配第一个 chain 后执行 | 匹配到的 chain 执行完或放行 |
3. 深度追问
3.1 Why(为什么):解决哪个根本矛盾?
责任链模式解决的根本矛盾是:"确定性的调用流"与"不确定的处理者"之间的矛盾。
在复杂的业务系统中,请求需要经过多个处理环节,但哪些处理环节是必需的、它们的顺序如何,这些信息在编译期往往无法确定。如果使用硬编码的 if-else,每次调整处理环节都需要修改核心代码。责任链模式通过将处理者串联成链,将"谁处理"的决定权从编译期延后到运行期。
3.2 How(怎么做):对象关系与交互机制
责任链模式的核心机制可以总结为:隐式配置、多态传递、控制权让渡。
- 隐式配置:链的组装在客户端完成,Handler 不知道链的全貌。
- 多态传递:每个 Handler 持有抽象 Handler 的引用,调用
next.handle()时利用多态。 - 控制权让渡:每个 Handler 执行完毕后,可以决定是终止处理还是继续传递。
典型的链式调用伪代码:
abstractclassHandler{protectedHandlernext;publicvoidhandle(Requestreq){if(canHandle(req)){doHandle(req);}elseif(next!=null){next.handle(req);// 控制权让渡}}}3.3 Trade-off(代价):牺牲了什么换取什么?
它牺牲了(付出代价):
- 请求处理的确定性:请求可能经过整条链都没有处理者能处理(如果没有兜底处理者)。
- 调试的直观性:运行时调用链不直观,需要跟踪
next.handle()的传递过程。 - 性能损耗:链越长,请求经过的处理者越多,性能损耗越大。
它换取了(获得收益):
- 处理者之间的完全解耦:每个处理者只关心自己的逻辑,不知道其他处理者的存在。
- 运行时动态调整:可以随意新增、删除或重排处理者。
- 单一职责:每个处理者的职责非常单一清晰。
3.4 Evolution(演化):它是如何从简单设计逐步演化而来的?
- 硬编码 if-else(起点):所有处理逻辑写在一个方法里,最直接但也最混乱。
- 策略模式:动态选择单个处理者,但只能选一个,不能链式传递。
- 责任链模式(经典):链式传递,多个处理者依次尝试直到有人处理。
- 责任链 + 模板方法(现代变体):如 Servlet Filter,在链式传递的基础上,每个 Filter 可以通过
chain.doFilter()在前后添加逻辑,结合了模板方法模式。
3.5 Modern Practice(现代实践):它在今天被取代了吗?
结论:责任链模式不仅没有被取代,反而在现代框架中成为了核心基础设施。
- Servlet FilterChain仍然是 Java Web 应用的基础。
- Spring Security的
SecurityFilterChain是安全框架的基石。 - MyBatis Plugin基于责任链实现插件机制。
- Netty的
ChannelPipeline是高性能网络框架的核心。
在函数式编程中,可以使用Function链式组合实现轻量级责任链:
Function<Request,Response>handler=request.andThen(this::validate).andThen(this::process).andThen(this::respond);但这种方式的灵活性不如经典责任链模式(不能中断传递),说明经典责任链在处理"有条件的传递"时仍有不可替代的优势。
4. 总结
核心要点
- 责任链模式将请求沿着处理者链传递,直到有处理者处理它
- 核心结构是链表:每个处理者持有下一个处理者的引用
- 与装饰器的区别:责任链是链式传递(可能终止),装饰器是层层嵌套(全部执行)
- 典型工业应用:Servlet Filter、MyBatis Interceptor、Spring Security FilterChain、Netty ChannelPipeline
一句话
责任链模式就是"击鼓传花"——请求沿着链传递,直到有人能处理它。
5. 参考文献
[1] GAMMA E, HELM R, JOHNSON R, et al. Design Patterns: Elements of Reusable Object-Oriented Software[M]. Boston: Addison-Wesley, 1994.
[2] Apache Tomcat. Tomcat FilterChain Source[EB/OL]. https://tomcat.apache.org/, 2024.
[3] MyBatis. MyBatis 3 Plugin[EB/OL]. https://mybatis.org/mybatis-3/configuration.html#plugins, 2024.
[4] SPRING. Spring Security Architecture[EB/OL]. https://docs.spring.io/spring-security/, 2024.