前言
责任链模式是一种行为设计模式,执行上它允许请求沿着一条处理链路依次向下传递,每个处理节点都能对当前状态的请求进行处理,满足一定条件后传递给下一个处理节点,亦或者直接结束这一次处理流程。
在现实生产环境中,需要用到责任链模式的场景还是很多的,比如:
- 多层条件准入控制,如人事审批流程、权限检验、游戏通关判断等
- 多环节拦截处理,如 Java 过滤器 Filter、组装生产链路等
在这些场景里,使用责任链模式的优势在于,当其中的某个环节需要进行新增、移除、修改时,可以只对单个节点进行操作,不会影响其他节点的执行过程,保证了整个责任链的稳定,代码更加容易维护和迭代。
模型结构设计
责任链模式初步设计 - 基础属性定义
在上述提到的应用场景中,多层条件准入控制涉及到条件判断,满足某些条件后可以直接中断链路流程,跳出服务;多环节拦截处理通常是一个全链路处理,历经所有环节后到达终点才算结束这一流程的操作。在模型设计上,前者也只是增加了一个对中间结果的判断操作,在整体结构上基本是一致的。基于这样的思路,可以给出责任链模式的初步设计。
这里为了结构的通用化,定义了一个上下文实体 ContextDO
,里面可以根据实际的业务需求在内部定义成员变量,在整理责任链处理流程中,相关的上下文数据则保留在这个实体中并传递下去。
Interface
接口Handler
定义了两个基本方法handle
:执行当前节点的行为setNextHandler
:指定当前节点的下一个责任节点
AbstractHandler
: 为抽象类,内部定义了一个可继承的成员变量next
,用来记录当前节点的下一个责任节点。在该抽象类中,可以实现通用的处理流程。HandlerOne
、HandlerTwo
:具体的实现类,继承了抽象类AbstractHandler
,可根据具体需求重写handler
方法。Client
:业务执行客户端,提供入口获取可执行的责任链并启动其执行流程。
责任链模式进一步设计 - 抽象通用方法
在上面责任链设计模式模型的基础上,各个具体实现类一般会有相同的 handle
动作,这个时候可以将这个执行的内容统一封装到抽象类 AbstractHandler
里面。同时,还可以新增一个方法 isComplete
,判断执行动作后的状态是否已满足条件,从而决定从此结束责任链流程或者决定进入下一个处理节点。
如图,相同的执行动作被统一抽象封装到抽象类 AbstractHandler
的handle 方法中,另外内部单独封装出来一个 action
方法,供实现类对具体执行动作进行重写。同样新增的方法还有 preAction
、postACtion
、isComplete
等。如果某个具体的 Handler 实现类有特殊的处理动作,则可以在实现类中再重写这些方法。
preAction
:预处理(可选)。每个处理节点在执行处理流程前,可以对上下文数据进行一些预处理,如对前面已处理的对象进行去重、过滤等。postAction
:后置处理(可选)。执行处理流程后,对结果进行后置处理,如将当前流程处理的对象加入去重表,用以后续过滤等操作。isComplete
:完成条件判断。判断当前节点执行完成后,是否完成全链路目标,是则结束流程,否则继续下一个节点的动作。亦可以调换判断逻辑。
以下是一个简单的抽象类实现 demo:
@Data
public class ContextDO {
/** 执行条件,可在 preAction 里更新 */
private List<Object> conditions;
/** 返回结果,每次执行 action 后新增 */
private List<Object> resultList;
/** 已处理对象,可在 postAction 里更新 */
private Set<Object> existedObjSet;
/** 目标请求数量 */
private Integer querySize;
}
public abstract class AbstractHandler implements Handler {
/** 下一个处理器节点 */
protected Handler next;
@Override
public void handle(ContextDO context) {
try {
preAction(context);
action(context);
postAction(context);
} catch (Exception e) {
e.printStackTrace();
}
/** 未达到完成目标,且还有下一个处理节点,则继续推进责任链任务 */
if (!isComplete(context) && this.next != null) {
this.next.handle(context);
}
}
@Override
public abstract void preAction(ContextDO context);
@Override
public void postAction(ContextDO context) {
context.getExistedObjSet().addAll(context.getConditions);
}
@Override
public void action(ContextDO context) {
// do something and add new result into #context.resultList.
}
@Override
public boolean isComplete(ContextDO context) {
return context.getResultList() != null && context.getResultList().size() >= context.getQuerySize();
}
@Override
public void setNextHandler(Handler next) {
this.next = next;
}
}
在 demo 中定义了一个示例的上下文实体 ContextDO
,其字段分别可在 preAction
、action
、postACtion
、isComplete
中用到。根据业务的具体情况,可调整上下文需要的字段,或者重写抽象类中封装的各个方法。
责任链模式服务的调用
前面已经完成了责任链模式的设计,从代码中可以看出,责任链任务执行的入口就是首个处理节点的 handle
方法,调用该方法后,整个流程就能结合 isComplete
方法和指向下个处理节点的 next
推动任务的进行。下面介绍几种启动责任链服务的方式,也就是业务客户端的实现方式。
简单客户端 - 按需组合
正如上面 UML 设计图所示,我们可以定义一个 Client 类,直接手动拼接需要组成的责任链节点,直接执行即可。
public class Client {
public Object process(Object input) {
ContextDO context = new ContextDO();
// do something. 将 input 的内容封装到 context 中
AbstractHandler handler1 = new HandlerOne();
AbstractHandler handler2 = new HandlerTwo();
AbstractHandler handler3 = new HandlerThree();
AbstractHandler handler4 = new HandlerFour();
handler1.setNextHandler(handler2);
handler2.setNextHandler(handler3);
handler3.setNextHandler(handler4);
return handler1.hander(context);
}
}
这种方式的优点在于代码可读性强,责任链的组合比较灵活,能否满足不同业务的定制化需求;而缺点在于,每一次调用服务的时候,都需要重新创建对象,完成后则立即释放资源,如果请求较多,则会一直重复这个操作,白白损耗了性能。因此,当责任链的组成相对固定时,我们完全可以定义一个稳定的、全局可引用的责任链,这里就可以结合工厂模式的思想了。
责任链工厂模式 - 统一装配
责任链工厂模式本质上与上面按需组合的简单客户端实现是相同的,只不过更多的结合 Spring
的一些特性,如自动装配、服务代理等,使代码更加整洁,更具拓展性和可维护性。
这里采用自动装配加上 @Order
注解来组装责任链,通过责任链工厂提供统一的责任链服务。
@Order(1)
@Service
public class HandlerOne extends AbstractHandler {
// ...
}
@Order(2)
@Service
public class HandlerTwo extends AbstractHandler {
// ...
}
责任链工厂中实现对责任链各节点的组装,并提供一个统一的管理器,供业务方直接启用服务。
@Service
public class ChainFactory {
private AbstractHandler headHandler;
@Resource
private List<AbstractHandler> handlerList;
@PostConstruct
public void init() {
handlerList.sort(AnnotationAwareOrderComparator.INSTANCE);
int size = handlerList.size();
for (int i = 0; i < size - 1; i++) {
handlerList.get(i).setNextHandler(handlerList.get(i + 1));
}
headHandler = handlerList.get(0);
}
public AbstractHandler getHeadHandler() {
return this.handHandler;
}
}
客户端可以通过以上工厂直接调用方法 ChainFactory.getHeadHandler()
获取到责任链头节点,随后执行 handle
方法即可启用相关服务了。
责任链模式更多拓展 - 工厂策略模式
上述的 demo 代码将继承了抽象类 AbstractHandler
的所有子类都加载到列表后,并根据每个实现类 @Order
注解的值进行排序,组装成责任链,将头节点通过 getter 方法对外开放。如果业务出现更多不同业务场景的责任链,或者同样的业务场景里,需要提供不同的组合方案,则可以在上述基础上做进一步拓展。
假如需要支持更多不一样的场景,可以粗暴地去实现新的抽象类,在新的抽象类中支持新的业务场景,对应的也需要有新的责任链工厂对外开放接口。
假如是同样的业务场景需要支持不同的节点组合方案,即在同一个工厂内需要提供不同的责任链,此时可以在工厂中使用策略模式。每一种策略对应一种责任链组合方案,在责任链工厂中通过一个映射表来维护所有的责任链方案。而不同责任链方案的初始化,可以通过配置文件进行定义,这样的可读性、可维护性最高,灵活度也最高。这里就不再提供具体的实现 demo 了。