定义
使多个对象都有机会处理请求,从而避免了请求的发送者和接收者之间的耦合关系。将这些对象连成一条链,并沿着这条链传递该请求,直到有对象处理它为止
使用场景
如果一个请求可能会出现多个或未知个数处理器实例,或者请求处理器可动态配置的情况下,这时候便可使用责任链模式。
几乎所有的开源框架中都使用到该模式,如 Spring 中的拦截器、过滤器,通过 ant 表达式风格的 url 参数来判断是否对请求进行拦截,Netty 中 ChannelPipeLine 通过链表的形式添加 ChannelHandler 处理节点。
在SpringBoot中使用
结构图
责任链的处理核心在"链"(Chain)上面。"链"是由多个处理者 ConcreateX 组成的,我们先来看抽象 “Handler” 接口:
/**
* @author tianp
**/
public interface Handler {
/**
* 是否支持处理
*
* @param uri 匹配uri
* @return true 成功 false 失败
*/
boolean support(String uri);
/**
* 处理
*
* @param requestBody 处理参数封装
* @return true 成功 false 失败
*/
boolean handle(RequestBody requestBody);
/**
* 获取处理器名字
*
* @return 处理器名字
*/
String getName();
}
RequestBody 对请求的参数进行封装
/**
* 请求处理封装类
*
* @author tianp
**/
public class RequestBody {
private String uri;
//根据业务定义
private Object body;
public String getUri() {
return uri;
}
public void setUri(String uri) {
this.uri = uri;
}
public Object getBody() {
return body;
}
public void setBody(Object body) {
this.body = body;
}
}
处理器接口 “Hanlder” 定义了三个标准:
- 是否支持处理。通过参数 uri 判断当前 “Handler” 是否支持处理, 如果当前不能处理就传递给下一个 “Handler” 处理
- 真正进行处理的方法。传递 RequestBody给 handle 方法,处理成功则传递下一个,不成功则返回false,结束。
- 获取当前处理器名字。便于打印日志排查问题
/**
* @author tianp
**/
public abstract class AbstractHandler implements Handler {
/**
* 处理器名称
*/
private String name;
/**
* 拦截uri
*/
private String[] includePatterns;
/**
* 放行uri
*/
private String[] excludePatterns;
/**
* ant 匹配
*/
private PathMatcher pathMatcher = new AntPathMatcher();
/**
* 下一个处理器
*/
private AbstractHandler next = null;
public AbstractHandler(String name) {
this.name = name;
}
public boolean support(String uri) {
if (excludePatterns != null) {
for (String exclude : excludePatterns) {
if (pathMatcher.match(exclude, uri)) {
return false;
}
}
}
if (includePatterns != null) {
for (String include : includePatterns) {
if (pathMatcher.match(include, uri)) {
return true;
}
}
}
return false;
}
/**
* 给子类实现的真正处理方法
*
* @param requestBody 请求参数
* @return true 成功 false 失败
*/
public abstract boolean process(RequestBody requestBody);
public boolean handle(RequestBody requestBody) {
if (next != null) {
if (next.support(requestBody.getUri())) {
System.out.println(next.getName() + "开始处理");
return next.process(requestBody);
} else {
next.handle(requestBody);
}
}
return true;
}
}
//.... 省略 get/set 方法
抽象处理器 AbstractHandler 实现了 Handler 接口,实现了对应的
- support() 通过 AntPathMatcher 使用 ant 风格的表达式来匹配当前类是否支持拦截
- handle() 通过持有一个 next 指针来进行请求传递
- getName()
- process() 给子类实现的真正处理的方法
/**
* @author tianp
**/
public class LinkedHandlerChain {
/**
* 头节点
*/
private static final AbstractHandler HEAD = new AbstractHandler("head"){
@Override
public boolean process(RequestBody requestBody) {
return true;
}
};
private AbstractHandler TAIL = HEAD;
public void addLast(AbstractHandler handler){
TAIL.setNext(handler);
TAIL = handler;
}
public boolean handle(RequestBody requestBody){
return HEAD.handle(requestBody);
}
}
最后通过一个链 LinkedhandlerChain 串起来。通过持有链表的指针 HEAD、TAIL 来处理和添加节点。
优点
非常显著的优点就是将请求和处理分开。请求者不用知道是谁处理的,处理者不用知道请求的全貌。两者解耦,提高系统灵活性
缺点
- 性能问题。每个请求都是从链头遍历到链尾,特别是在链比较长的时候,性能是一个非常大的问题。
- 调试不方便。当链比较长、环节比较多的时候,由于采取了类似递归的方式,调试的时候逻辑可能比较复杂
最佳实践
责任链在实际项目中,曾经在项目中使用它来对一部分功能进行鉴权,因为当时只有一部分接口需要鉴权,如果引入第三方框架如:Spring Security 和 Shiro 不仅增加项目的复杂度,还让项目变 “重”,因此通过引入 责任链模式,我可以很好的解决这个问题
更多参考
代码仓库地址:https://github.com/To-echo/chain-design/tree/master
参考书籍:《设计模式之禅》