设计模式之责任链

典型的使用场景

责任链模式在现代架构中无处不在,主要用于将请求的发送者与多个处理者解耦。


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
/**
* @author KJ
* @description 请求封装
*/
@Data
public class Request {
private String content;

public Request(String content) {
this.content = content;
}
}


/**
* @author KJ
* @description 响应封装
*/
@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
/**
* @author KJ
* @description 过滤器接口
*/
public interface Filter {
void doFilter(Request request, Response response, FilterChain chain);
}


/**
* @author KJ
* @description 过滤器链接口
*/
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
/**
* @author KJ
* @description 具体的过滤器链执行器
*/
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++);
// 🚩 核心:将“当前执行链对象(this)”传入,以便过滤器回调继续执行下一步
filter.doFilter(request, response, this);
} else {
// 🚩 所有过滤器执行完毕,这里可以调用真正的业务逻辑(如 Controller)
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
/**
* @author KJ
* @description 测试方法
*/
public class MyTomcatContainer {

// 全局过滤器池(单例,只读,线程安全)
private final List<Filter> globalFilters = new ArrayList<>();

public void addFilter(Filter filter) {
globalFilters.add(filter);
}

/**
* 模拟处理 HTTP 请求
*/
public void processRequest(Request req, Response resp) {
// 为每个请求 new 一个专属的链对象,这样每个线程都有自己的 pos 指针,互不干扰
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