java设计模式责任链模式原理案例详解
作者:大忽悠爱忽悠
引言
以请假流程为例,一般公司普通员工的请假流程简化如下:
普通员工发起一个请假申请,当请假天数小于3天时只需要得到主管批准即可;当请假天数大于3天时,主管批准后还需要提交给经理审批,经理审批通过,若请假天数大于7天还需要进一步提交给总经理审批。
使用 if-else
来实现这个请假流程的简化代码如下:
public class LeaveApproval { public boolean process(String request, int number) { boolean result = handleByDirector(request); // 主管处理 if (result == false) { // 主管不批准 return false; } else if (number < 3) { // 主管批准且天数小于 3 return true; } result = handleByManager(request); // 准管批准且天数大于等于 3,提交给经理处理 if (result == false) { // 经理不批准 return false; } else if (number < 7) { // 经理批准且天数小于 7 return true; } result = handleByTopManager(request); // 经理批准且天数大于等于 7,提交给总经理处理 if (result == false) { // 总经理不批准 return false; } return true; // 总经理最后批准 } private boolean handleByDirector(String request) { // 主管处理该请假申请 if(request.length()>10) return false; return true; } private boolean handleByManager(String request) { // 经理处理该请假申请 if(request.length()>5) return false; return true; } private boolean handleByTopManager(String request) { // 总经理处理该请假申请 if(request.length()>3) return false; return true; } }
问题看起来很简单,三下五除二就搞定,但是该方案存在几个问题:
-
LeaveApproval
类比较庞大,各个上级的审批方法都集中在该类中,违反了 “单一职责原则”,测试和维护难度大 - 当需要修改该请假流程,譬如增加当天数大于30天时还需提交给董事长处理,必须修改该类源代码(并重新进行严格地测试),违反了"开闭原则"
- 该流程缺乏灵活性,流程确定后不可再修改(除非修改源代码),客户端无法定制流程
使用责任链模式可以解决上述问题。
责任链模式定义
避免请求发送者与接收者耦合在一起,让多个对象都有可能接收请求,将这些对象连接成一条链,并且沿着这条链传递请求,直到有对象处理它为止。职责链模式是一种对象行为型模式。
责任链可以是一条直线、一个环或者一个树形结构,最常见的职责链是直线型,即沿着一条单向的链来传递请求,如下图所示。链上的每一个对象都是请求处理者,责任链模式可以将请求的处理者组织成一条链,并让请求沿着链传递,由链上的处理者对请求进行相应的处理。在此过程中,客户端实际上无须关心请求的处理细节以及请求的传递,只需将请求发送到链上即可,从而实现请求发送者和请求处理者解耦。
对责任链的理解,关键在于对链的理解,即包含如下两点:
- 链是一系列节点的集合,在责任链中,节点实质上是指请求的处理者;
- 链的各节点可灵活拆分再重组,在责任链中,实质上就是请求发送者与请求处理者的解耦。
类图
角色
我们可以从责任链模式的结构图中看到,具体的请求处理者可以有多个,并且所有的请求处理者均具有相同的接口(继承于同一抽象类)。 责任链模式主要包含如下两个角色
Handler
(抽象处理者):处理请求的接口,一般设计为具有抽象请求处理方法的抽象类,以便于不同的具体处理者进行继承,从而实现具体的请求处理方法。此外,由于每一个请求处理者的下家还是一个处理者,因此抽象处理者本身还包含了一个本身的引用( successor)作为其对下家的引用,以便将处理者链成一条链;
ConcreteHandler
(具体处理者):它是抽象处理者的子类,可以处理用户请求,在具体处理者类中实现了抽象处理者中定义的抽象请求处理方法,在处理请求之前需要进行判断,看是否有相应的处理权限,如果可以处理请求就处理它,否则将请求转发给后继者;在具体处理者中可以访问链中下一个对象,以便请求的转发。
在责任链模式里,由每一个请求处理者对象对其下家的引用而连接起来形成一条请求处理链。请求将在这条链上一直传递,直到链上的某一个请求处理者能够处理此请求。事实上,发出这个请求的客户端并不知道链上的哪一个请求处理者将处理这个请求,这使得系统可以在不影响客户端的情况下动态地重新组织链和分配责任。
核心
实现责任链模式的关键核心是: 在抽象类 Handler 里面聚合它自己(持有自身类型的引用),并在 handleRequest 方法里判断其是否能够处理请求。若当前处理者无法处理,则设置其后继者并向下传递,直至请求被处理。
示例代码
1、对请求处理者的抽象
责任链模式的核心在于对请求处理者的抽象。在实现过程中,抽象处理者一般会被设定为抽象类
其典型实现代码如下所示:
public abstract class Handler { // protected :维持对下家的引用 protected Handler successor; public void setSuccessor(Handler successor) { this.successor=successor; } public abstract void handleRequest(String request); }
上述代码中,抽象处理者类定义了对下家的引用 (其一般用 protected 进行修饰),以便将请求转发给下家,从而形成一条请求处理链。同时,在抽象处理者类中还声明了抽象的请求处理方法,以便由子类进行具体实现。
2、对请求处理者的抽象
具体处理者是抽象处理者的子类,具体处理者类的典型代码如下:
public class ConcreteHandler extends Handler { public void handleRequest(String request) { if (请求满足条件) { //处理请求 }else { this.successor.handleRequest(request); //转发请求 } } }
在具体处理类中,通过对请求进行判断以便做出相应的处理,因此,其一般具有两大作用:
- 处理请求,不同的具体处理者以不同的形式实现抽象请求处理方法 handleRequest();
- 转发请求,若该请求超出了当前处理者类的权限,可以将该请求转发给下家;
3、责任链的创建
需要注意的是,责任链模式并不创建职责链,职责链的创建工作必须由系统的其他部分来完成,一般由使用该责任链的客户端创建。职责链模式降低了请求的发送者和请求处理者之间的耦合,从而使得多个请求处理者都有机会处理这个请求。
责任链实现请假案例
请假信息类,包含请假人姓名和请假天数
@Data @AllArgsConstructor public class LeaveRequest { String name;//请假人的姓名 Integer num;//请假天数 }
抽象处理者类 Handler
,维护一个 nextHandler
属性,该属性为当前处理者的下一个处理者的引用;声明了抽象方法 process
//抽象处理者 @Data public abstract class Handler { //维护自身引用 protected Handler handler; //当前处理者的姓名 protected String name; //传入当前处理者的姓名 public Handler(String name) { this.name=name; } //抽象方法,用来处理请假的请求 public abstract Boolean process(LeaveRequest leaveRequest); }
三个具体处理类,分别实现了抽象处理类的 process
方法
主管:
public class Director extends Handler{ public Director(String name) { super(name); } //处理请假的请求 @Override public Boolean process(LeaveRequest leaveRequest) { //随机数大于3,就批准请求 boolean result = (new Random().nextInt(10)) > 3; String log = "主管: %s,审批:%s的请假申请,请假天数:%d,审批结果:%s"; System.out.println(String.format(log,name,leaveRequest.getName(),leaveRequest.getNum(),result==true?"通过":"不通过")); if(result)//批准 { //如果请假天数,超过了3天,那么交给上级继续审批 if(leaveRequest.num>3) { return nextHandler.process(leaveRequest); } //请假天数小于3,审批通过 return true; } //没有通过审批 return false; } }
经理
public class Manager extends Handler{ public Manager(String name) { super(name); } //处理请假的请求 @Override public Boolean process(LeaveRequest leaveRequest) { boolean result = (new Random().nextInt(10)) > 3; // 随机数大于3则为批准,否则不批准 String log = "经理: %s,审批:%s的请假申请,请假天数:%d,审批结果:%s"; System.out.println(String.format(log,name,leaveRequest.getName(),leaveRequest.getNum(),result==true?"批准":"不通过")); if(result) { //请假天数过多,还是需要提交到更高的一级去审批 if(leaveRequest.getNum()>7) { return nextHandler.process(leaveRequest); } //否则直接通过 return true; } return false; } }
总经理
public class TopManager extends Handler{ public TopManager(String name) { super(name); } @Override public Boolean process(LeaveRequest leaveRequest) { //随机数大于3,就批准请求 boolean result = (new Random().nextInt(10)) > 3; String log = "总经理: %s,审批:%s的请假申请,请假天数:%d,审批结果:%s"; System.out.println(String.format(log,name,leaveRequest.getName(),leaveRequest.getNum(),result==true?"通过":"不通过")); if(result)//批准 { //默认只有三个处理器,但是如果后续还要加,也需要留个位置 //如果后续继续添加 if(nextHandler!=null) { return nextHandler.process(leaveRequest); } return true; } //没有通过审批 return false; } }
处理器链类:
//处理器链 public class HandlerChain { //维护第一个处理器 private Handler director=new Director("小忽悠"); //默认有三个处理器 public HandlerChain() { //默认有三个处理器链 //并且这三个处理器有先后关系 director.nextHandler=new Manager("小朋友"); director.nextHandler.nextHandler=new TopManager("超级大忽悠"); } //添加一个处理器进集合 public void addHandler(Handler handler) { Handler temp=director; while(temp.nextHandler!=null) { temp=temp.nextHandler; } temp.nextHandler=handler; } //执行处理器链 public void process(LeaveRequest leaveRequest) { //第一个处理器,如果可以处理器就不需要交给下一个处理器处理了 //否则,继续交给下一个处理器处理 director.process(leaveRequest); } }
客户端测试:
public class Client { public static void main(String[] args) { LeaveRequest leaveRequest=new LeaveRequest("大忽悠",10); HandlerChain handlerChain=new HandlerChain(); handlerChain.process(leaveRequest); } }
案例类图
与上面所给出的类图不同的是,我通过一个处理器链类,把调用处理器链处理业务逻辑和客户端分离开来,进一步解耦
可扩展性
如果此时审批流程还需要加上一步,就非常方便
例如,我们需要增加一个上帝,来对请假流程做最终的处理,那么我们只需要创建一个上帝处理器实现处理器抽象类,然后添加进处理器链中即可
public class God extends Handler{ public God(String name) { super(name); } @Override public Boolean process(LeaveRequest leaveRequest) { System.out.println("上帝保佑你,所以你可以放假了"); return true; } }
客户端:
public class Client { public static void main(String[] args) { LeaveRequest leaveRequest=new LeaveRequest("大忽悠",10); HandlerChain handlerChain=new HandlerChain(); handlerChain.addHandler(new God("上帝")); handlerChain.process(leaveRequest); } }
如果还想继续添加处理器,就需要在上帝process方法中预留一个接口
这样很麻烦,我这里没有继续对方法抽取,进行解耦,感兴趣的小伙伴,可以继续尝试解耦
纯与不纯的责任链模式
纯的责任链模式
- 一个具体处理者对象只能在两个行为中选择一个:要么承担全部责任,要么将责任推给下家,不允许出现某一个具体处理者对象在承担了一部分或全部责任后又将责任向下传递的情况
- 一个请求必须被某一个处理者对象所接收,不能出现某个请求未被任何一个处理者对象处理的情况
不纯的责任链模式
- 允许某个请求被一个具体处理者部分处理后再向下传递
- 或者一个具体处理者处理完某请求后其后继处理者可以继续处理该请求
- 而且一个请求可以最终不被任何处理者对象所接收
责任链模式主要优点
- 对象仅需知道该请求会被处理即可,且链中的对象不需要知道链的结构,由客户端负责链的创建,降低了系统的耦合度
- 请求处理对象仅需维持一个指向其后继者的引用,而不需要维持它对所有的候选处理者的引用,可简化对象的相互连接
- 在给对象分派职责时,职责链可以给我们更多的灵活性,可以在运行时对该链进行动态的增删改,改变处理一个请求的职责
- 新增一个新的具体请求处理者时无须修改原有代码,只需要在客户端重新建链即可,符合 “开闭原则”
职责链模式的主要缺点
- 一个请求可能因职责链没有被正确配置而得不到处理
- 对于比较长的职责链,请求的处理可能涉及到多个处理对象,系统性能将受到一定影响,且不方便调试
- 可能因为职责链创建不当,造成循环调用,导致系统陷入死循环
适用场景
- 有多个对象可以处理同一个请求,具体哪个对象处理该请求待运行时刻再确定,客户端只需将请求提交到链上,而无须关心请求的处理对象是谁以及它是如何处理的
- 在不明确指定接收者的情况下,向多个对象中的一个提交一个请求
- 可动态指定一组对象处理请求,客户端可以动态创建职责链来处理请求,还可以改变链中处理者之间的先后次序
模拟实现Tomcat中的过滤器机制
第一步:定义封装请求的类Request和封装处理结果响应的类Response
@Data @AllArgsConstructor @NoArgsConstructor public class Reponse { private List<String> data=new ArrayList<>(); public void addData(String data) { this.data.add(data); } } @Data @NoArgsConstructor @AllArgsConstructor public class Request { private Object data; }
第二步:定义具有过滤功能的接口Filter,具体的过滤规则需要实现该接口
/* * 定义接口Filter,具体的过滤规则需要实现这个接口,最后一个参数添加的意义是我们在Main函数中: * fc.doFilter(request, response,fc);执行这一步的时候可以按照规则链条一次使用三个过滤规则对字符串进行处理 */ public interface Filter { void doFilter(Request request,Reponse reponse,FilterChain filterChain); }
第三步:定义具体的过滤处理规则
public class StuAgeFilter implements Filter { @Override public void doFilter(Request request, Reponse reponse, FilterChain filterChain) { Stu stu = (Stu) request.getData(); if(stu.getName().contains("忽悠")) { //名字不符合要求 reponse.addData("名字不符合要求"); } //名字符合要求 reponse.addData("名字符合要求"); filterChain.doFilter(request,reponse,filterChain); } } //学生过滤器--过滤出18岁以上的 public class StuFilter implements Filter { @Override public void doFilter(Request request, Reponse reponse, FilterChain filterChain) { Stu stu = (Stu)request.getData(); if(stu.getAge()<18) { //不放行 reponse.addData("年龄不符合要求"); } //放行 reponse.addData("年龄满足要求"); filterChain.doFilter(request,reponse,filterChain); } }
第四步:定义责任链FilterChain
//过滤链条 @Data public class FilterChain { //用List集合来存过滤器 private List<Filter> filters = new ArrayList<Filter>(); //用于标记规则的引用顺序 private int index; public FilterChain() { //初始化为0 index=0; } //往过滤器链条中添加新的过滤器 public FilterChain addFilter(Filter f) { filters.add(f); //代码的设计技巧:Chain链添加过滤规则结束后返回添加后的Chain,方便我们下面doFilter函数的操作 return this; } public void doFilter(Request request, Reponse response, FilterChain chain){ //index初始化为0,filters.size()为3,不会执行return操作 //说明所有过滤器都执行完了 if(index==filters.size()){ return; } //获取当前过滤器 Filter f=filters.get(index); //下一次获取的时候,就是下一个过滤器了 index++; //执行当前过滤器的过滤方法 f.doFilter(request, response, chain); } }
第五步:测试
public class Client { public static void main(String[] args) { //创建请求对象 Request request=new Request(); request.setData(new Stu("小朋友",19)); //创建响应对象 Reponse reponse=new Reponse(); //创建一个过滤器链 FilterChain filterChain=new FilterChain(); filterChain.addFilter(new StuAgeFilter()); filterChain.addFilter(new StuFilter()); //执行 filterChain.doFilter(request,reponse,filterChain); reponse.getData().forEach(x->{ System.out.println(x); }); } }
运行过程如下
分析Tomcat 过滤器中的责任链模式
Servlet
过滤器是可用于 Servlet
编程的 Java
类,可以实现以下目的:在客户端的请求访问后端资源之前,拦截这些请求;在服务器的响应发送回客户端之前,处理这些响应。
Servlet
定义了过滤器接口 Filter
和过滤器链接口 FilterChain
的源码如下
public interface Filter { public void init(FilterConfig filterConfig) throws ServletException; public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException; public void destroy(); } public interface FilterChain { void doFilter(ServletRequest var1, ServletResponse var2) throws IOException, ServletException; }
我们自定义一个过滤器的步骤是:
1)写一个过滤器类,实现 javax.servlet.Filter
接口,如下所示
public class MyFilter implements Filter { @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { // 做一些自定义处理.... System.out.println("执行doFilter()方法之前..."); chain.doFilter(request, response); // 传递请求给下一个过滤器 System.out.println("执行doFilter()方法之后..."); } @Override public void destroy() { } @Override public void init(FilterConfig filterConfig) throws ServletException { } }
2)在 web.xml
文件中增加该过滤器的配置,譬如下面是拦截所有请求
<filter> <filter-name>MyFilter</filter-name> <filter-class>com.whirly.filter.MyFilter</filter-class> </filter> <filter-mapping> <filter-name>MyFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping>
当启动 Tomcat 是我们的过滤器就可以发挥作用了。那么过滤器是怎样运行的呢?
Tomcat
有 Pipeline Valve
机制,也是使用了责任链模式
,一个请求会在 Pipeline
中流转,Pipeline
会调用相应的 Valve
完成具体的逻辑处理;
其中的一个基础Valve
为 StandardWrapperValve
,其中的一个作用是调用 ApplicationFilterFactory
生成 Filter
链,具体代码在 invoke
方法中
在运行过滤器之前需要完成过滤器的加载和初始化,以及根据配置信息生成过滤器链:
- 过滤器的加载具体是在
ContextConfig
类的configureContext
方法中,分别加载filter
和filterMap
的相关信息,并保存在上下文环境中 - 过滤器的初始化在
StandardContext
类的startInternal
方法中完成,保存在filterConfigs
中并存到上下文环境中 - 请求流转到
StandardWrapperValve
时,在invoke
方法中,会根据过滤器映射配置信息,为每个请求创建对ApplicationFilterChain
,其中包含了目标Servlet
以及对应的过滤器链,并调用过滤器链的doFilter
方法执行过滤器
StandardWrapperValve
调用 ApplicationFilterFactory
为请求创建过滤器链并调用过滤器链的关键代码如下:
final class StandardWrapperValve extends ValveBase { public final void invoke(Request request, Response response) throws IOException, ServletException { // 省略其他的逻辑处理... // 调用 ApplicationFilterChain.createFilterChain() 创建过滤器链 ApplicationFilterChain filterChain = ApplicationFilterFactory.createFilterChain(request, wrapper, servlet); if (servlet != null && filterChain != null) { // 省略 } else if (request.isAsyncDispatching()) { request.getAsyncContextInternal().doInternalDispatch(); } else if (comet) { filterChain.doFilterEvent(request.getEvent()); } else { // 调用过滤器链的 doFilter 方法开始过滤 filterChain.doFilter(request.getRequest(), response.getResponse()); }
过滤器链 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); } } // 省略... }
过滤器
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { System.out.println("执行doFilter()方法之前..."); chain.doFilter(request, response); // 传递请求给下一个过滤器 System.out.println("执行doFilter()方法之后..."); }
当下标小于过滤器数组长度 n
时,说明过滤器链未执行完,所以从数组中取出当前过滤器,调用过滤器的 doFilter
方法完成过滤处理,在过滤器的 doFilter
中又调用 FilterChain
的 doFilter
,回到 ApplicationFilterChain
,又继续根据下标是否小于数组长度来判断过滤器链是否已执行完,未完则继续从数组取出过滤器并调用 doFilter
方法,所以这里的过滤链是通过嵌套递归的方式来串成一条链。
当全部过滤器都执行完毕,最后一次进入 ApplicationFilterChain.doFilter
方法的时候 pos < n
为false
,不进入 if (pos < n)
中,而是执行后面的代码,判断 (request instanceof HttpServletRequest) && (response instanceof HttpServletResponse)
,若为 http
请求则调用 servlet.service(request, response);
来处理该请求。
处理完毕之后沿着调用过滤器的顺序反向退栈
,分别执行过滤器中 chain.doFilter()
之后的处理逻辑,需要注意的是在 if (pos < n)
方法体的最后有一个 return
;,这样就保证了只有最后一次进入 ApplicationFilterChain.doFilter
方法的调用能够执行后面的 servlet.service(request, response)
方法
画一个简要的调用栈如下所示:
ApplicationFilterChain
类扮演了抽象处理者角色,具体处理者角色由各个 Filter
扮演
以上就是java设计模式责任链模式原理案例详解的详细内容,更多关于java设计模式责任链模式的资料请关注脚本之家其它相关文章!