一、模式简介
责任链模式是一种行为设计模式, 允许你将请求沿着处理者链进行发送。 收到请求后, 每个处理者均可对请求进行处理, 或将其传递给链上的下个处理者。责任链模式通过建立一条链来组织请求的处理者,请求将沿着链进行传递,请求发送者无须知道请求在何时、何处以及如何被处理,实现了请求发送者与处理者的解耦。 在软件开发中责任链有着广泛的应用,如果遇到有多个对象可以处理同一请求时可以应用职责链模式,例如在Web应用开发中创建一个过滤器(Filter)链来对请求数据进行过滤, 在工作流系统中实现公文的分级审批等等,使用职责链模式可以较好地解决此类问题。
二、模式详解
在web项目中我们通常会遇到这样的场景,对一次请求进行多次处理。比如我们的系统现在有这样一个需求:需要对一次数据文件进行校验处理,校验的步骤包括:数据类型校验、数据源校验、正文内容校验等。如果我们将这些校验步骤都写在一处代码时可能就会造成校验代码本来就已经混乱不堪, 而每次新增功能都会使其更加臃肿。 修改某个检查步骤有时会影响其他的检查步骤。而且当有其他文件需要校验当你希望复用这些原来数据文件的校验逻辑时, 你只能复制部分代码。因此我们引入了责任链设计模式,下面我们来看下责任链模式的类图。
Hander: 声明了所有具体处理者的通用接口。 该接口通常仅包含单个方法用于请求处理, 但有时其还会包含一个设置链上下个处理者的方法。
BaseHander: 是一个可选的类, 你可以将所有处理者共用的样本代码放置在其中。通常情况下, 该类中定义了一个保存对于下个处理者引用的成员变量。 客户端可通过将处理者传递给上个处理者的构造函数或设定方法来创建链。 该类还可以实现默认的处理行为: 确定下个处理者存在后再将请求传递给它。
contreteHanders:包含处理请求的实际代码。 每个处理者接收到请求后, 都必须决定是否进行处理, 以及是否沿着链传递请求。处理者通常是独立且不可变的, 需要通过构造函数一次性地获得所有必要地数据。
client:可根据程序逻辑一次性或者动态地生成链。 值得注意的是, 请求可发送给链上的任意一个处理者, 而非必须是第一个处理者。
下面我们来自己实现一下上述的业务处理逻辑来方便大家感受下责任链模式。
- Handler处理者接口
/**
* 处理者接口
*
*/
public interface Handler {
/**
* 设置下一个
*/
void setNext(Handler handler);
/**
* 处理逻辑
*/
String handler(DataFile dataFile);
}
- BaseHandler抽象处理者
/**
* 抽象处理者
*
*/
public abstract class BaseHandler implements Handler{
private Handler nextHandler;
/**
* 设置下一个
*/
public void setNext(Handler handler){
this.nextHandler=handler;
}
public Handler getNextHandler(){
return this.nextHandler;
}
}
- ConcreteHandler具体处理器
/**
* 校验数据源
*
*/
public class ConcreteHandlerA extends BaseHandler{
@Override
public String handler(DataFile dataFile) {
System.out.println("进行数据源处理");
if (!dataFile.getDataSource().equals("zq")){
return "false";
}
return super.getNextHandler().handler(dataFile);
}
}
/**
* 校验数据类型
*/
public class ConcreteHandlerB extends BaseHandler{
@Override
public String handler(DataFile dataFile) {
System.out.println("进行数据类型处理");
if (!dataFile.getDataType().equals("新闻")){
return "false";
}
return super.getNextHandler().handler(dataFile);
}
}
/**
* 校验正文
*
*/
public class ConcreteHandlerC extends BaseHandler{
@Override
public String handler(DataFile dataFile) {
System.out.println("进行内容处理");
//校验正文内容长度大小
if (dataFile.getContext().length()>100){
return "success";
}
return "false";
}
}
- client客户端
public class Client {
public static void main(String[] args) {
DataFile dataFile=new DataFile("新闻","zq","9月3日下午,北青报记者在北京图书大厦4楼学习类图书专区看到,“2022年秋季各区版本使用情况”提示牌张贴在书架之间,以方便学生、家长及时购买到准确的教材、教辅用书,细致的服务措施赢得读者点赞啊啊啊啊啊啊");
Handler handlerA=new ConcreteHandlerA();
Handler handlerB=new ConcreteHandlerB();
Handler handlerC=new ConcreteHandlerC();
handlerA.setNext(handlerB);
handlerB.setNext(handlerC);
System.out.println("处理结果-----"+handlerA.handler(dataFile));
}
}
- 责任链的纯与不纯
- 一个纯的责任链:要求一个具体的处理者对象只能在两个行为中选择一个:一是承担责任,而是把责任推给下家。不允许出现某一个具体处理者对象在承担了一部分责任后又把责任向下传的情况。在一个纯的责任链模式里面,一个请求必须被某一个处理者对象所接收;
- 一个不纯的责任链:一个请求可以最终不被任何接收端对象所接收。纯的责任链模式的实际例子很难找到,一般看到的例子均是不纯的责任链模式的实现。
个人认为不纯的责任链模式在实际的项目中是比较常见的。
三、Spring中的责任链
Spring中运用了许多种设计模式,最近也在空闲的时候阅读了一些Spring的源码,后续也可以整理出一些设计模式在Spring中的具体应用。责任链设计模式在Spring中也有着十分重要的应用场景,我们通常使用Aop对方法的功能进行增强,而通知的种类也有很多种,因此我们可能使用多组通知对同一方法进行功能增强。在Spring中所有的通知最终都会转化为MethodInterceptor方法拦截器,而最终去一个一个调用这些方法拦截器则是通过MethodInvocation对象,假如有两组环绕通知就有如下的处理逻辑。
从 ProxyFactory 获得 Target 和环绕通知链,根据他俩创建 MethodInvocation,简称 mi
首次执行 mi.proceed() 发现有下一个环绕通知,调用它的 invoke(mi)
进入环绕通知1,执行前增强,再次调用 mi.proceed() 发现有下一个环绕通知,调用它的 invoke(mi)
进入环绕通知2,执行前增强,调用 mi.proceed() 发现没有环绕通知,调用 mi.invokeJoinPoint() 执行目标方法
目标方法执行结束,将结果返回给环绕通知2,执行环绕通知2 的后增强
环绕通知2继续将结果返回给环绕通知1,执行环绕通知1 的后增强
环绕通知1返回最终的结果
示例代码:
/**具体通知*/
static class Advice1 implements MethodInterceptor {
public Object invoke(MethodInvocation invocation) throws Throwable {
System.out.println("Advice1.before()");
Object result = invocation.proceed();// 调用下一个通知或目标
System.out.println("Advice1.after()");
return result;
}
}
static class Advice2 implements MethodInterceptor {
public Object invoke(MethodInvocation invocation) throws Throwable {
System.out.println("Advice2.before()");
Object result = invocation.proceed();// 调用下一个通知或目标
System.out.println("Advice2.after()");
return result;
}
}
/**执行器对象**/
static class MyInvocation implements MethodInvocation {
private Object target; // 1
private Method method;
private Object[] args;
List<MethodInterceptor> methodInterceptorList; // 2
private int count = 1; // 调用次数
public MyInvocation(Object target, Method method, Object[] args, List<MethodInterceptor> methodInterceptorList) {
this.target = target;
this.method = method;
this.args = args;
this.methodInterceptorList = methodInterceptorList;
}
@Override
public Method getMethod() {
return method;
}
@Override
public Object[] getArguments() {
return args;
}
@Override
public Object proceed() throws Throwable { // 调用每一个环绕通知, 调用目标
if (count > methodInterceptorList.size()) {
// 调用目标, 返回并结束递归
return method.invoke(target, args);
}
// 逐一调用通知, count + 1
MethodInterceptor methodInterceptor = methodInterceptorList.get(count++ - 1);
return methodInterceptor.invoke(this);
}
@Override
public Object getThis() {
return target;
}
@Override
public AccessibleObject getStaticPart() {
return method;
}
}
经过如上的流程我们发现通过MethodInvocation对象去调用一个一个的方法拦截器,对方法进行增强。那是不是正如我们的责任链模式一级一级的往下处理逻辑,只不过这里将具体责任的处理者和维护责任链的对象进行了拆分,通过组合的方式避免了耦合。MethodInvocation主要就是用来编排整个责任链的对象,而MethodInterceptor就是具体的责任处理者相当于类图的Handler。
四、适用场景
-
当程序需要使用不同方式处理不同种类请求, 而且请求类型和顺序预先未知时,可以使用责任链模式。
-
当必须按顺序执行多个处理者时, 可以使用该模式。
-
如果所需处理者及其顺序必须在运行时进行改变, 可以使用责任链模式。