典型的使用场景
责任链模式在现代架构中无处不在,主要用于将请求的发送者与多个处理者解耦。
1)Servlet Filter(Java Web)
这是你代码的直接原型。Tomcat 启动时将 web.xml 或注解配置的过滤器组装成 ApplicationFilterChain,用于处理编码转换、权限校验、日志记录等。
2)Spring MVC Interceptor(拦截器)
正如我们之前讨论的,Spring MVC 的拦截器也是一种责任链。
- preHandle:正序校验。
- postHandle & afterCompletion:逆序收尾。
3)Spring Security 过滤器链
Spring Security 的本质就是一个巨大的 FilterChainProxy。
- 它包含十几个内置过滤器(UsernamePasswordAuthenticationFilter、CsrfFilter、ExceptionTranslationFilter 等)。
- 应用逻辑:如果 AnonymousAuthenticationFilter 发现你没登录,它会直接打断链路并重定向到登录页。
4)MyBatis 插件机制 (Interceptor)
MyBatis 允许你拦截 SQL 执行的过程(如 Executor、StatementHandler)。
- 它是通过 JDK 动态代理将多个拦截器包装成一个 “洋葱圈”。
- 典型应用:分页插件(自动在 SQL 后加 LIMIT)、数据脱敏、性能监控。
5)Netty 管道 (ChannelPipeline)
在高性能网络编程中,Netty 使用 ChannelHandler 链处理入站和出站数据。
- InboundHandler(入站):处理接收到的字节流。
- OutboundHandler(出站):处理发出的数据。
6)现实生活中的审批流
- 请假 1 天:组长审批即可。
- 请假 3 天:需要部长审批。
- 请假 10 天:需要总经理审批。 每个环节只关心自己权限内的逻辑,处理不了就扔给下一级。
手搓一个出来
我们使用 Servlet 规范的方式,实现一个自己的 FilterChain。下面代码 globalFilters 是共享的常量列表,而执行进度 pos 封装在 ApplicationFilterChain 实例中。
- 由于每个请求都 new 一个链实例,彻底解决了并发错乱问题。
- 我们的设计中不需要复杂的 Lambda 或内部类,执行逻辑非常直观:A 调 Chain -> Chain 调 B -> B 调 Chain..
- 虽然每次请求 new 了一个对象,但这个对象非常轻量(只持有一个引用和一个 int),在 Java 现代 JVM 的逃逸分析下,这种短命小对象通常在栈上分配或被快速回收,性能极高。
以下是完整的代码实现:
定义请求和响应:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
|
@Data public class Request { private String content;
public Request(String content) { this.content = content; } }
@Data public class Response { private String content;
public Response(String content) { super(); this.content = content; } }
|
定义过滤器和过滤器链接口:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
|
public interface Filter { void doFilter(Request request, Response response, FilterChain chain); }
public interface FilterChain { void doFilter(Request request, Response response); }
|
过滤器链的实现:ApplicationFilterChain(运行态)
这是非线程安全的,因为它包含了状态变量 pos。关键在于它只为单次请求服务,随请求创建,随请求销毁。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
|
public class ApplicationFilterChain implements FilterChain {
private final List<Filter> filters;
private int pos = 0;
public ApplicationFilterChain(List<Filter> filters) { this.filters = filters; }
@Override public void doFilter(Request request, Response response) { if (pos < filters.size()) { Filter filter = filters.get(pos++); filter.doFilter(request, response, this); } else { service(request, response); } }
private void service(Request request, Response response) { System.out.println(">>> [Business Logic] 执行目标业务方法..."); } }
|
过滤器实现:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| public class Filter1 implements Filter { @Override public void doFilter(Request request, Response response, FilterChain chain) { request.setContent(request.getContent() + " -> F1_In"); chain.doFilter(request, response); response.setContent(response.getContent() + " -> F1_Out"); } }
public class Filter2 implements Filter { @Override public void doFilter(Request request, Response response, FilterChain chain) { request.setContent(request.getContent() + " -> F2_In"); chain.doFilter(request, response); response.setContent(response.getContent() + " -> F2_Out"); } }
|
模拟 Tomcat 容器(配置态)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37
|
public class MyTomcatContainer {
private final List<Filter> globalFilters = new ArrayList<>();
public void addFilter(Filter filter) { globalFilters.add(filter); }
public void processRequest(Request req, Response resp) { ApplicationFilterChain chain = new ApplicationFilterChain(globalFilters); chain.doFilter(req, resp); }
public static void main(String[] args) { MyTomcatContainer tomcat = new MyTomcatContainer(); tomcat.addFilter(new Filter1()); tomcat.addFilter(new Filter2());
Request request = new Request("ReqData"); Response response = new Response("RespData");
tomcat.processRequest(request, response);
System.out.println("Final Request: " + request.getContent()); System.out.println("Final Response: " + response.getContent()); } }
|
测试结果:
1 2 3
| >>> [Business Logic] 执行目标业务方法... Final Request: ReqData -> F1_In -> F2_In Final Response: RespData -> F2_Out -> F1_Out
|