本文内容思维导图如下:
一、责任链模式介绍
责任链模式定义:为请求创建一个处理此请求对象的链。
适用场景(核心):只要把你的请求抛给第一个处理者,不用关心谁处理的,并且最终会返回你一个结果。
优点:请求者和处理者解耦,请求者不用知道谁处理的,处理者可以不用知道请求的全貌。
缺点:每个请求从链头遍历到链尾,影响性能。代码调试时候不方便。
类型:行为型。
类图:
源码中的典型应用:
- Netty 中的 Pipeline和ChannelHandler通过责任链设计模式来组织代码逻辑。
- Spring Security 使用责任链模式,可以动态地添加或删除责任(处理 request 请求)。ref:SPRING与设计模式---责任链模式
- Spring AOP 通过责任链模式来管理 Advisor。
- Dubbo Filter 过滤器链也是用了责任链模式(链表),可以对方法调用做一些过滤处理,譬如超时(TimeoutFilter),异常(ExceptionFilter),Token(TokenFilter)等。
- Mybatis 中的 Plugin 机制使用了责任链模式,配置各种官方或者自定义的 Plugin,与 Filter 类似,可以在执行 Sql 语句的时候做一些操作。
- Tomcat 调用 ApplicationFilterFactory过滤器链。
二、请假示例
员工在OA系统中提交请假申请,首先项目经理处理,他能审批3天以内的假期,如果大于3天,则由项目经理则转交给总经理处理。接下来我们用责任链模式实现这个过程。
1、封装请假信息实体类
public class LeaveRequest {
private String name; // 请假人姓名
private int numOfDays; // 请假天数
private int workingAge; //员工工龄(在公司大于2年则总经理会审批)
//省略get..set..
}
2、抽象处理者类 Handler,维护一个nextHandler属性,该属性为当前处理者的下一个处理者的引用;声明了抽象方法process,其实在这里也用了方法模板模式:
public abstract class ApproveHandler {
protected ApproveHandler nextHandler;//下一个处理者(与类一致,这段代码很重要)
public void setNextHandler(ApproveHandler approveHandler){
this.nextHandler=approveHandler;
}
public abstract void process(LeaveRequest leaveRequest); // 处理请假(这里用了模板方法模式)
}
3、项目经理处理者,能处理小于3天的假期,而请假信息里没有名字时,审批不通过:
public class PMHandler extends ApproveHandler{
@Override
public void process(LeaveRequest leaveRequest) {
//未填写姓名的请假单不通过
if(null != leaveRequest.getName()){
if(leaveRequest.getNumOfDays() <= 3){
System.out.println(leaveRequest.getName()+",你通过项目经理审批!");
}else {
System.out.println("项目经理转交总经理");
if(null != nextHandler){
nextHandler.process(leaveRequest);
}
}
}else {
System.out.println("请假单未填写完整,未通过项目经理审批!");
return;
}
}
}
4、总经理处理者,能处理大于3天的假期,且工龄超过2年才会审批通过:
public class GMHandler extends ApproveHandler{
@Override
public void process(LeaveRequest leaveRequest) {
//员工在公司工龄超过2年,则审批通过
if(leaveRequest.getWorkingAge() >=2 && leaveRequest.getNumOfDays() > 3){
System.out.println(leaveRequest.getName()+",你通过总经理审批!");
if(null != nextHandler){
nextHandler.process(leaveRequest);
}
}else {
System.out.println("在公司年限不够,长假未通过总经理审批!");
return;
}
}
}
实例代码完成,我们测试一下:
public class Test {
public static void main(String[] args) {
PMHandler pm = new PMHandler();
GMHandler gm = new GMHandler();
LeaveRequest leaveRequest = new LeaveRequest();
leaveRequest.setName("张三");
leaveRequest.setNumOfDays(4);//请假4天
leaveRequest.setWorkingAge(3);//工龄3年
pm.setNextHandler(gm);//设置传递顺序
pm.process(leaveRequest);
}
}
运行结果:
------
项目经理转交总经理
张三,你通过总经理审批!
------
三、源码中的责任链模式
Filter接口有非常多的实现类,这里挑选doFilter方法中的FilterChain参数来看,Tomcat和SpringSecurity中都用到责任链模式:
进入第一个,过滤器链 ApplicationFilterChain 的关键代码如下,过滤器链实际是一个 ApplicationFilterConfig 数组:
final class ApplicationFilterChain implements FilterChain, CometFilterChain {
private ApplicationFilterConfig[] filters = new ApplicationFilterConfig[0]; // 过滤器链
private Servlet servlet = null; // 目标
// ...
@Override
public void doFilter(ServletRequest request, ServletResponse response) throws IOException, ServletException {
if( Globals.IS_SECURITY_ENABLED ) {
// ...
} else {
internalDoFilter(request,response); // 调用 internalDoFilter 方法
}
}
private void internalDoFilter(ServletRequest request, ServletResponse response) throws IOException, ServletException {
// Call the next filter if there is one
if (pos < n) {
// 从过滤器数组中取出当前过滤器配置,然后下标自增1
ApplicationFilterConfig filterConfig = filters[pos++];
Filter filter = null;
try {
filter = filterConfig.getFilter(); // 从过滤器配置中取出该 过滤器对象
if( Globals.IS_SECURITY_ENABLED ) {
final ServletRequest req = request;
final ServletResponse res = response;
Principal principal = ((HttpServletRequest) req).getUserPrincipal();
Object[] args = new Object[]{req, res, this};
SecurityUtil.doAsPrivilege("doFilter", filter, classType, args, principal);
} else {
// 调用过滤器的 doFilter,完成一个过滤器的过滤功能
filter.doFilter(request, response, this);
}
return; // 这里很重要,不会重复执行后面的 servlet.service(request, response)
}
// 执行完过滤器链的所有过滤器之后,调用 Servlet 的 service 完成请求的处理
if ((request instanceof HttpServletRequest) && (response instanceof HttpServletResponse)) {
if( Globals.IS_SECURITY_ENABLED ) {
} else {
servlet.service(request, response);
}
} else {
servlet.service(request, response);
}
}
// 省略...
}
这里可以看出ApplicationFilterChain类扮演了抽象处理者角色,doFilter就类似于刚才请假流程里的process方法。
当下标小于过滤器数组长度 n 时,也就是过滤器链未执行完,从数组中取出并调用当前过滤器的 doFilter方法 ,如果下标一直小于n,则循环调用doFilter方法通过嵌套递归的方式来串成一条链。
当最后的过滤器执行完毕,也就是走到最后一个return;时,结束递归调用doFilter。if (pos < n) 为false,调用后面的servlet.service(request, response) 方法。return;这一点在请假流程里也有体现。
参考: