定义
责任链模式是一种设计模式。在责任链模式里,很多对象由每一个对象对其下家的引用而连接起来形成一条链。请求在这个链上传递,直到链上的某一个对象决定处理此请求。
发出这个请求的客户端并不知道链上的哪一个对象最终处理这个请求,这使得系统可以在不影响客户端的情况下动态地重新组织和分配责任。
简而言之,就是将多个对象以链条的形式进行连接。每一个对象都会引用下一个对象。请求在链条上进行传递,直到某个对象处理了请求,传递终止。
责任链类图
责任链模式涉及到的角色如下所示
● 抽象处理者(BaseHandler)角色:定义出一个处理请求的接口。如果需要,接口可以定义出一个方法以设定和返回对下家的引用。
这个角色通常由一个Java抽象类或者Java接口实现。上图中Handler类的聚合关系给出了具体子类对下家的引用,
抽象方法handleRequest()规范了子类处理请求的操作。
● 具体处理者(ConcreteHandler)角色:具体处理者接到请求后,可以选择将请求处理掉,或者将请求传给下家。
由于具体处理者持有对下家的引用,因此,如果需要,具体处理者可以访问下家。
源代码
抽象处理者(Handler)角色
代码语言:javascript代码运行次数:0运行复制
定义了一个对象,四个方法:successor:持有当前责任的对象。getSuccessor():获取下家责任对象的方法。setSuccessor():设置下家责任对象的方法。handlerRequest():处理当前请求的方法。如果当前对象可以处理请求,处理请求。如果当前对象不可以处理请求,传递给下家。
代码语言:javascript代码运行次数:0运行复制
1 public abstract class BaseHandler { 2 3 /**持有后续的责任对象*/ 4 protected BaseHandler successor; 5 6 /** 7 * 示意处理请求的方法,虽然这个示意方法并没有传入参数 8 * 但是实际是可以传入参数的,根据实际情况进行选择 9 */10 public abstract void handleRequest();11 12 /**13 * 获取后续的责任对象14 */15 public BaseHandler getSuccessor() {16 return successor;17 }18 19 /**20 * 设置后续的责任对象21 */22 public void setSuccessor(BaseHandler successor) {23 this.successor = successor;24 }25 }具体处理者(ConcreteHandler)角色
继承BaseHandler类,重写了handleRequest()方法。如果当前的处理对象有下家就传递给下家,没有下家就自行处理请求。通过getSuccessor().handleRequest()可以引用下家。
代码语言:javascript代码运行次数:0运行复制
1 public class ConcreteHandler extends BaseHandler { 2 3 4 /**处理器名称*/ 5 private String handlerName; 6 public ConcreteHandler(String handlerName) { 7 this.handlerName = handlerName; 8 } 9 10 /**11 * 处理请求的方法12 * 如果当前对象有下家,就传递给下家13 * 如果当前对象没有下家,就自行处理14 */15 @Override16 public void handleRequest() {17 18 if(getSuccessor() !=null){19 System.out.println("已经有对象处理此请求!"+this.handlerName);20 getSuccessor().handleRequest();21 }else {22 System.out.println("正在处理请求......."+this.handlerName);23 }24 25 System.out.println(" ======= 请求处理结束 ======= "+new Date().toLocaleString());26 }27 }测试用例
代码语言:javascript代码运行次数:0运行复制
1 public static void main(String[] args) { 2 3 BaseHandler baseHandler1=new ConcreteHandler("baseHandler1"); 4 BaseHandler baseHandler2=new ConcreteHandler("baseHandler2"); 5 BaseHandler baseHandler3=new ConcreteHandler("baseHandler3"); 6 BaseHandler baseHandler4=new ConcreteHandler("baseHandler4"); 7 baseHandler1.setSuccessor(baseHandler2); 8 baseHandler2.setSuccessor(baseHandler3); 9 baseHandler3.setSuccessor(baseHandler4);10 11 //提交请求12 baseHandler1.handleRequest();13 14 }场景一聚餐费用
一般申请聚餐费用的流程为:申请人填写申请表->提交给领导审批->如果领导批准->去财务领钱->如果领导不批准->就没有然后了
不同的级别领导的审批额度不一样比如:项目经理审批最大额度为2000,部门经理审批最大额度为5000,总经理审批最大额度为50000。也就是说如果审批的费用在2000以内,项目经理、部门经理、总经理都可以审批。如果审批的费用在2000以上,只有部门经理、总经理可以审批。如果审批的费用在5000以上,只有总经理可以审批。
但是最终那个领导进行审批,请求人是不清楚的。这个流程就可以使用责任链模式来实现。
用户提交申请,请求会在(项目经理审批-部门经理审批-总经理审批)这样一条责任处理链上传递。直到某个领导处理了这个请求,这个传递才会结束。
思路:因为每个领导的审批额度是不一样的,所以每个领导应该分别建一个单独的对象,处理各自的业务。而每个领导都继承了同一个抽象的父类,拥有共同的行为和方法。方便根据不同的功能选择对应的审批流程。
源代码
审批处理抽象父类(BaseApprovalHandler)
代码语言:javascript代码运行次数:0运行复制
定义了一个对象,四个方法。baseApprovalHandler:持有当前责任的对象transferRequest():负责将请求传递给下家getBaseApprovalHandler():获取当前的责任对象setBaseApprovalHandler():设置当前的责任对象handlerRequest():处理当前请求的方法。如果当前对象可以处理请求,处理请求。如果当前对象不可以处理请求,传递给下家。
代码语言:javascript代码运行次数:0运行复制
1 public abstract class BaseApprovalHandler { 2 3 4 /** 5 * 审批当前请求的责任对象 6 */ 7 protected BaseApprovalHandler baseApprovalHandler; 8 9 /**10 * 处理当前请求的方法11 * 如果当前对象可以处理请求->处理请求12 * 如果当前对象不可以处理请求->传递给下家13 *14 * @param money 审批金额15 */16 public abstract void handlerRequest(int money);17 18 /**19 * 将请求传递给下家20 */21 protected void transferRequest(int money){22 23 //将请求传递给下家24 BaseApprovalHandler baseApprovalHandler = getBaseApprovalHandler();25 if (baseApprovalHandler != null) {26 baseApprovalHandler.handlerRequest(money);27 }28 }29 30 /**31 * 获取当前的责任对象32 */33 public BaseApprovalHandler getBaseApprovalHandler() {34 return baseApprovalHandler;35 }36 37 /**38 * 设置当前的责任对象39 */40 public void setBaseApprovalHandler(BaseApprovalHandler baseApprovalHandler) {41 this.baseApprovalHandler = baseApprovalHandler;42 }43 }项目经理(ProjectManager)
Magento是一套专业开源的PHP电子商务系统。Magento设计得非常灵活,具有模块化架构体系和丰富的功能。易于与第三方应用系统无缝集成。Magento开源网店系统的特点主要分以下几大类,网站管理促销和工具国际化支持SEO搜索引擎优化结账方式运输快递支付方式客户服务用户帐户目录管理目录浏览产品展示分析和报表Magento 1.6 主要包含以下新特性:•持久性购物 - 为不同的
职能:只能审批N元以下的申请,超出职能传递给BranchManager
代码语言:javascript代码运行次数:0运行复制
1 public class ProjectManager extends BaseApprovalHandler { 2 3 4 public ProjectManager(BaseApprovalHandler baseApprovalHandler) { 5 this.baseApprovalHandler=baseApprovalHandler; 6 } 7 8 @Override 9 public void handlerRequest(int money) {10 11 //最大审批额度12 int maxMoney=2000;13 14 if(money <p>部门经理(BranchManager)</p><p>职能:只能审批N元以下的申请,超出职能传递给GeneralManager</p>代码语言:javascript<i class="icon-code"></i>代码运行次数:<!-- -->0<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewbox="0 0 16 16" fill="none"><path d="M6.66666 10.9999L10.6667 7.99992L6.66666 4.99992V10.9999ZM7.99999 1.33325C4.31999 1.33325 1.33333 4.31992 1.33333 7.99992C1.33333 11.6799 4.31999 14.6666 7.99999 14.6666C11.68 14.6666 14.6667 11.6799 14.6667 7.99992C14.6667 4.31992 11.68 1.33325 7.99999 1.33325ZM7.99999 13.3333C5.05999 13.3333 2.66666 10.9399 2.66666 7.99992C2.66666 5.05992 5.05999 2.66659 7.99999 2.66659C10.94 2.66659 13.3333 5.05992 13.3333 7.99992C13.3333 10.9399 10.94 13.3333 7.99999 13.3333Z" fill="currentcolor"></path></svg>运行<svg width="16" height="16" viewbox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" clip-rule="evenodd" d="M4.5 15.5V3.5H14.5V15.5H4.5ZM12.5 5.5H6.5V13.5H12.5V5.5ZM9.5 2.5H3.5V12.5H1.5V0.5H11.5V2.5H9.5Z" fill="currentcolor"></path></svg>复制<pre class="brush:php;toolbar:false;"> 1 public class BranchManager extends BaseApprovalHandler { 2 3 4 public BranchManager(BaseApprovalHandler baseApprovalHandler) { 5 this.baseApprovalHandler = baseApprovalHandler; 6 } 7 8 @Override 9 public void handlerRequest(int money) {10 11 //最大审批额度12 int maxMoney=5000;13 14 if(money <p>总经理(GeneralManager)</p><p>职能:只能审批N元以下的申请,超出职能直接拒绝</p>代码语言:javascript<i class="icon-code"></i>代码运行次数:<!-- -->0<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewbox="0 0 16 16" fill="none"><path d="M6.66666 10.9999L10.6667 7.99992L6.66666 4.99992V10.9999ZM7.99999 1.33325C4.31999 1.33325 1.33333 4.31992 1.33333 7.99992C1.33333 11.6799 4.31999 14.6666 7.99999 14.6666C11.68 14.6666 14.6667 11.6799 14.6667 7.99992C14.6667 4.31992 11.68 1.33325 7.99999 1.33325ZM7.99999 13.3333C5.05999 13.3333 2.66666 10.9399 2.66666 7.99992C2.66666 5.05992 5.05999 2.66659 7.99999 2.66659C10.94 2.66659 13.3333 5.05992 13.3333 7.99992C13.3333 10.9399 10.94 13.3333 7.99999 13.3333Z" fill="currentcolor"></path></svg>运行<svg width="16" height="16" viewbox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" clip-rule="evenodd" d="M4.5 15.5V3.5H14.5V15.5H4.5ZM12.5 5.5H6.5V13.5H12.5V5.5ZM9.5 2.5H3.5V12.5H1.5V0.5H11.5V2.5H9.5Z" fill="currentcolor"></path></svg>复制<pre class="brush:php;toolbar:false;"> 1 public class GeneralManager extends BaseApprovalHandler { 2 3 4 public GeneralManager(BaseApprovalHandler baseApprovalHandler) { 5 this.baseApprovalHandler = baseApprovalHandler; 6 } 7 8 @Override 9 public void handlerRequest(int money) {10 11 //最大审批额度12 int maxMoney=50000;13 14 if(money <p>处理工厂(HandlerFactory)</p><p>作用:规范审批流程。当前情景的审批需要先经过项目经理,项目经理能审批就处理,不能审批传递给部门经理。部门经理能审批就处理不能审批就传递给总经理。以此类推</p>代码语言:javascript<i class="icon-code"></i>代码运行次数:<!-- -->0<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewbox="0 0 16 16" fill="none"><path d="M6.66666 10.9999L10.6667 7.99992L6.66666 4.99992V10.9999ZM7.99999 1.33325C4.31999 1.33325 1.33333 4.31992 1.33333 7.99992C1.33333 11.6799 4.31999 14.6666 7.99999 14.6666C11.68 14.6666 14.6667 11.6799 14.6667 7.99992C14.6667 4.31992 11.68 1.33325 7.99999 1.33325ZM7.99999 13.3333C5.05999 13.3333 2.66666 10.9399 2.66666 7.99992C2.66666 5.05992 5.05999 2.66659 7.99999 2.66659C10.94 2.66659 13.3333 5.05992 13.3333 7.99992C13.3333 10.9399 10.94 13.3333 7.99999 13.3333Z" fill="currentcolor"></path></svg>运行<svg width="16" height="16" viewbox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" clip-rule="evenodd" d="M4.5 15.5V3.5H14.5V15.5H4.5ZM12.5 5.5H6.5V13.5H12.5V5.5ZM9.5 2.5H3.5V12.5H1.5V0.5H11.5V2.5H9.5Z" fill="currentcolor"></path></svg>复制<pre class="brush:php;toolbar:false;"> 1 public class HandlerFactory { 2 3 /** 4 * 审批处理流程 5 * 项目经理->部门经理->总经理 6 */ 7 8 public static BaseApprovalHandler getProjectManagerHandler() { 9 return new ProjectManager(new BranchManager(new GeneralManager(null)));10 }11 12 }测试用例
代码语言:javascript代码运行次数:0运行复制1 public static void main(String[] args) {2 3 //申请金额4 int money = 8000;5 6 System.out.println("\n\n项目经理开始审批");7 HandlerFactory.getProjectManagerHandler().handlerRequest(money);8 }扩展性
如果此时审批流程没有项目经理这个角色了。新增了两个审批流程:(部门经理审批->总经理审批)、(总经理审批)。应该怎么办?这个时候工厂的作用就出现了。我们可以新增两个方法getBranchManagerHandler()、getGeneralManagerHandler()。然后将以前的getProjectManagerHandler()方法暂时废弃掉即可。
代码语言:javascript代码运行次数:0运行复制 1 public class HandlerFactory { 2 3 /** 4 * 审批处理流程 5 * 项目经理->部门经理->总经理 6 */ 7 @Deprecated 8 public static BaseApprovalHandler getProjectManagerHandler() { 9 return new ProjectManager(new BranchManager(new GeneralManager(null)));10 }11 12 /**13 * 审批处理流程14 * 部门经理->总经理15 */16 public static BaseApprovalHandler getBranchManagerHandler() {17 return new BranchManager(new GeneralManager(null));18 }19 20 /**21 * 审批处理流程22 * 总经理23 */24 public static BaseApprovalHandler getGeneralManagerHandler() {25 return new GeneralManager(null);26 }27 28 }场景二拦截器应用假设我需要对外提供API服务。考虑到接口安全性需要做token校验,日志收集需要做日志记录。这个时候怎么做呢?一般的时候我们都会选择加一个拦截器。先做一个token校验,然后再做一个日志收集。或者增加一个日志拦截器、token拦截器。那么我们分析一下这样做好不好。第一种情况将所有的业务冗余一个方法一个类,如果以后增加接口限流,增加其他的功能会继续冗余。第二种情况增加多个拦截器,以后增加功能拦截器会越来越多。能不能做到有效的管理?
思路:我们知道责任链的功能是把多个业务功能进行串联。当请求到达token校验类的时候校验token,检验完token传递给下家。当请求到达日志类的时候做日志记录,做完日志记录传递给下家。那么,如果获取这个请求呢?我们可以增加一个拦截器,拦截器接收到请求之后,将请求传递到责任链。这样一来,只需要一个拦截器我们就完成了token校验、日志收集。如果后期增加功能只需要增加一个类即可。
查看以下类图
首先客户端接收到请求会进入拦截器(BaseFilter),在doFilter()方法中进行接收。doFilter()获取request参数、response参数。并调用BaseFilterFactory工厂将request参数、response参数传到责任链。此时责任链接接收到request参数、response参数。会进行token校验、日志采集,执行完会调用下家继续执行,如果没有下家则停止传递。
源代码审批处理抽象父类(BaseFilterHandler)
代码语言:javascript代码运行次数:0运行复制定义了一个对象,四个方法successor:持有当前责任的对象transferRequest():负责将请求传递给下家getBaseApprovalHandler():获取当前的责任对象setBaseApprovalHandler():设置当前的责任对象handlerRequest():处理当前请求的方法。如果当前对象可以处理请求,处理请求。如果当前对象不可以处理请求,传递给下家。代码语言:javascript代码运行次数:0运行复制
1 public abstract class BaseFilterHandler { 2 3 /** 处理当前请求的责任对象 */ 4 protected BaseFilterHandler successor; 5 6 /** 7 * 处理请求 8 * 9 * @param request10 * 请求对象11 * @param response12 * 请求对象13 * @param chain14 * 决定拦截器是否执行15 *16 * @throws IOException17 * io异常18 * @throws ServletException19 * servlet异常20 */21 public abstract void handlerRequest(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException;22 23 /**24 * 将请求传递给下家25 *26 * @param request27 * 请求对象28 * @param response29 * 请求对象30 * @param chain31 * 决定拦截器是否执行32 *33 * @throws IOException34 * io异常35 * @throws ServletException36 * servlet异常37 */38 protected void transferRequest(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {39 40 //将请求传递给下家41 BaseFilterHandler baseFilter = getSuccessor();42 if (baseFilter != null) {43 baseFilter.handlerRequest(request, response, chain);44 }45 }46 47 48 public BaseFilterHandler getSuccessor() {49 return successor;50 }51 52 public void setSuccessor(BaseFilterHandler successor) {53 this.successor = successor;54 }55 }日志记录(LogRecord)
职能:负责获取接口请求与参数,进行统一日志记录管理
代码语言:javascript代码运行次数:0运行复制 1 public class LogRecord extends BaseFilterHandler { 2 3 public LogRecord(BaseFilterHandler baseFilterHandler) { 4 this.successor = baseFilterHandler; 5 } 6 7 @Override 8 public void handlerRequest(ServletRequest request, ServletResponse response,FilterChain chain) throws IOException, ServletException { 9 10 11 System.out.println("\n 日志记录拦截器..................");12 13 //日志记录->记录完日志调用下家14 HttpServletRequest httpServletRequest = (HttpServletRequest) request;15 16 17 //获取请求URL18 StringBuffer requestURL = httpServletRequest.getRequestURL();19 System.out.println("请求URL: requestURL=" + requestURL);20 21 //获取请求参数22 Map<string string> parameterMap = request.getParameterMap();23 if (parameterMap.size() > 0) {24 25 Map<string string> parameterMap2 = new HashMap(parameterMap.size());26 parameterMap.forEach((key, value) -> parameterMap2.put(key, Arrays.toString(value)));27 28 System.out.println("请求参数:parameterMap=" + parameterMap2);29 }30 31 32 //请求传递给下家33 transferRequest(request, response ,chain);34 35 chain.doFilter(request, response);36 }37 }</string></string>token鉴权(TokenAuth)
职能:负责校验接口token是否合法
代码语言:javascript代码运行次数:0运行复制public class TokenAuth extends BaseFilterHandler { private final String ckToken = "123"; public TokenAuth(BaseFilterHandler baseFilterHandler) { this.successor = baseFilterHandler; } @Override public void handlerRequest(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { //token校验->校验完token调用下家 HttpServletResponse httpServletResponse = (HttpServletResponse) response; System.out.println("\n token校验拦截器.................."); //获取请求参数 boolean isNext = false; Map<string string> parameterMap = request.getParameterMap(); if (parameterMap.size() > 0) { String[] tokens = parameterMap.get("token"); if (tokens.length > 0) { for (String token : tokens) { if (StringUtils.isNotEmpty(token) && ckToken.equals(token)) { isNext = true; } } } } if (isNext) { //请求传递给下家 transferRequest(request, response, chain); chain.doFilter(request, response); } else { //这句话的意思,是让浏览器用utf8来解析返回的数据 httpServletResponse.setHeader("Content-type", "text/html;charset=UTF-8"); //这句话的意思,是告诉servlet用UTF-8转码,而不是用默认的ISO8859 httpServletResponse.setCharacterEncoding("UTF-8"); String printStr="无效的token"; httpServletResponse.getWriter().print(printStr); System.out.println(printStr); } }}</string>处理工厂(BaseFilterFactory)
作用:规范审批流程。当前的执行流程为:token鉴权(TokenAuth)->日志记录(LogRecord)。当然执行顺序可以按照业务场景,自行调整。
代码语言:javascript代码运行次数:0运行复制1 public class BaseFilterFactory {2 3 public static BaseFilterHandler getBaseFilterHandler(){4 return new TokenAuth(new LogRecord(null));5 }6 7 }过滤器(BaseFilter)作用:负责拦截请求,调用处理工厂,并将请求参数传递给责任链
代码语言:javascript代码运行次数:0运行复制 1 @WebFilter(filterName = "baseFilter", urlPatterns = "/*") 2 public class BaseFilter implements Filter { 3 4 private final List<string> passUrlList =new ArrayList(50); 5 6 @Override 7 public void init(FilterConfig filterConfig) throws ServletException { 8 passUrlList.clear(); 9 passUrlList.add(".");10 passUrlList.add("/login");11 passUrlList.add("/jiankong");12 }13 14 @Override15 public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {16 17 System.out.println("\n\n ========================= 执行自定义责任链拦截器 ========================= ");18 19 //获取请求URL20 HttpServletRequest httpServletRequest = (HttpServletRequest) request;21 String requestURI = httpServletRequest.getRequestURI();22 if(passUrlList.size() >0){23 for (String url : passUrlList) {24 if (requestURI.contains(url)) {25 26 System.out.println("url ="+requestURI +"放行!");27 28 chain.doFilter(request, response);29 return;30 }31 }32 }33 34 //执行自定义责任链拦截器35 BaseFilterHandler baseFilterHandler = BaseFilterFactory.getBaseFilterHandler();36 baseFilterHandler.handlerRequest(request,response,chain);37 38 }39 }</string>配置(BaseFilterConfig)
代码语言:javascript代码运行次数:0运行复制 1 @Configuration 2 public class BaseFilterConfig { 3 @Bean 4 public FilterRegistrationBean filterRegistrationBean(){ 5 FilterRegistrationBean bean = new FilterRegistrationBean(); 6 bean.setFilter(new BaseFilter()); 7 bean.addUrlPatterns("/*"); 8 return bean; 9 }10 }总结纯与不纯的责任链模式我们知道责任链中有两个行为:一个是承担责任,一个是把责任推给下家。
纯的责任链模式:要求两个行为中只执行一个,要么承担责任,要么把责任推给下家。不能存在既承担部分责任,又把责任推给下家的情况。
不纯的责任链模式:就是即承担部分责任,又把责任推给下家。当然也有可能出现没有对象承担责任的情况。
在抽象处理者(Handler)角色中,我们采用的是纯的责任链模式,但是这种情况在现实生活中很难找到。像场景一聚餐费用 与场景二拦截器应用现实生活中均是不纯的责任链模式。
责任链模式的应用场景网关:接口鉴权、日志记录、接口限流等等多条件(if else 、switch)流程判断、权限控制ERP系统 流程审批 总经理、人事经理、项目经理Java过滤器的底层实现Filter





