关于责任链模式,其有两种形式,一种是经过外部调用的方式对链的各个节点调用进行控制,从而进行链的各个节点之间的切换;另外一种是链的每一个节点自由控制是否继续往下传递链的进度,这种比较典型的使用方式就是Netty中的责任链模式。本文主要讲解咱们如何在Spring中使用这两种责任链模式。前端
对于外部控制的方式,这种方式比较简单,链的每一个节点只须要专一于各自的逻辑便可,而当前节点调用完成以后是否继续调用下一个节点,这个则由外部控制逻辑进行。这里咱们以一个过滤器的实现逻辑为例进行讲解,在日常工做中,咱们常常须要根据一系列的条件对某个东西进行过滤,好比任务服务的设计,在执行某个任务时,其须要通过诸如时效性检验,风控拦截,任务完成次数等过滤条件的检验以后才能判断当前任务是否可以执行,只有在全部的过滤条件都完成以后,咱们才能执行该任务。那么这里咱们就能够抽象出一个Filter
接口,其设计以下:java
public interface Filter { /** * 用于对各个任务节点进行过滤 */ boolean filter(Task task); }
这里的Filter.filter()
方法只有一个参数Task
,主要就是控制当前task是否须要被过滤掉,其有一个boolean类型的返回值,经过该返回值以告知外部控制逻辑是否须要将该task过滤掉。对于该接口的子类,咱们只须要将其声明为Spring所管理的一个bean便可:数据库
// 时效性检验 @Component public class DurationFilter implements Filter { @Override public boolean filter(Task task) { System.out.println("时效性检验"); return true; } }
// 风控拦截 @Component public class RiskFilter implements Filter { @Override public boolean filter(Task task) { System.out.println("风控拦截"); return true; } }
// 次数限制校验 @Component public class TimesFilter implements Filter { @Override public boolean filter(Task task) { System.out.println("次数限制检验"); return true; } }
上面咱们模拟声明了三个Filter
的子类,用于设计一系列的控制当前task是否须要被过滤的逻辑,结构上的逻辑其实比较简单,主要就是须要将其声明为Spring所管理的一个bean。下面是咱们的控制逻辑:app
@Service public class ApplicationService { @Autowired private List<Filter> filters; public void mockedClient() { Task task = new Task(); // 这里task通常是经过数据库查询获得的 for (Filter filter : filters) { if (!filter.filter(task)) { return; } } // 过滤完成,后续是执行任务的逻辑 } }
在上述的控制逻辑中,对于过滤器的获取,只须要经过Spring的自动注入便可,这里注入的是一个List<Filter>
,也就是说,若是咱们有新的Filter
实例须要参与责任链的过滤,只须要将其声明为一个Spring容器所管理的bean便可。框架
这种责任链设计方式的优势在于链的控制比较简单,只须要实现一个统一的接口便可,其基本上可以知足大部分的逻辑控制,可是对于某些须要动态调整链的需求其就无能为力了。好比在执行到某个节点以后须要动态的判断是否执行下一个节点,或者说要执行某些分叉的节点等等。这个时候咱们就须要将链节点的传递工做交由各个节点进行。ide
对于节点控制调用的方式,其主要有三个控制点:Handler,HandlerContext和Pipeline。Handler中是用于编写具体的业务代码的;HandlerContext则主要是用于对Handler进行包裹,而且用于控制进行下一个节点的调用的;Pipeline则主要是用于控制总体的流程调用的,好比对于任务的执行,其有任务的查询,任务的过滤和执行任务等等流程,这些流程总体的逻辑控制就是由Pipeline来控制的,在每一个流程中又包含了一系列的子流程,这些子流程则是由一个个的HandlerContext和Handler进行梳理的。这种责任链的控制方式总体逻辑以下图所示:函数
从图中能够看出,咱们将整个流程经过Pipeline
对象进行了抽象,这里主要分为了三个步骤:查询task,过滤task和执行task。在每一个步骤中,咱们都使用了一系列的链式调用。图中须要注意的是,在每次调用链的下一个节点的时候,咱们都是经过具体的Handler进行的,也就是说是否进行链的下一个节点的调用,咱们是经过业务实现方来进行动态控制的。post
关于该模式的设计,咱们首先须要强调的就是Handler
接口的设计,其设计以下所示:this
public interface Handler { /** * 处理接收到前端请求的逻辑 */ default void receiveTask(HandlerContext ctx, Request request) { ctx.fireTaskReceived(request); } /** * 查询到task以后,进行task过滤的逻辑 */ default void filterTask(HandlerContext ctx, Task task) { ctx.fireTaskFiltered(task); } /** * task过滤完成以后,处理执行task的逻辑 */ default void executeTask(HandlerContext ctx, Task task) { ctx.fireTaskExecuted(task); } /** * 当实现的前面的方法抛出异常时,将使用当前方法进行异常处理,这样能够将每一个handler的异常 * 都只在该handler内进行处理,而无需额外进行捕获 */ default void exceptionCaught(HandlerContext ctx, Throwable e) { throw new RuntimeException(e); } /** * 在整个流程中,保证最后必定会执行的代码,主要是用于一些清理工做 */ default void afterCompletion(HandlerContext ctx) { ctx.fireAfterCompletion(ctx); } }
这里的Handler
接口主要是对具体的业务逻辑的一个抽象,对于该Handler
主要有以下几点须要说明:prototype
Pipeline
的每一个层级中对应于该Handler
都有一个方法,在须要进行具体的业务处理的时候,用户只须要声明一个bean,具体实现某个当前业务所须要处理的层级的方法便可,而无需管其余的逻辑;HandlerContext
类型的,该参数主要是用于进行流程控制的,好比是否须要将当前层级的调用链往下继续传递,这里链的传递工做主要是经过ctx.fireXXX()
方法进行的;Handler
中都有一个exceptionCaught()
方法和afterCompletion()
方法,这两个方法分别用于异常控制和全部调用完成后的清理的,这里的异常控制主要是捕获当前Handler
中的异常,而afterCompletion()
方法则会保证在全部步骤以后必定会进行调用的,不管是否抛出异常;Handler
的使用,咱们但愿可以达到的目的是,适用方只须要实现该接口,而且使用某个注解来将其标志为Spring
的bean便可,而无需管整个Pipeline
的组装和流程控制。经过这种方式,咱们即保留了每一个Spring提供给咱们的便利性,也使用了Pipeline
模式的灵活性。 上述流程代码中,咱们注意到,每一个层级的方法中都有一个HandlerContext
用于传递链相关的控制信息,这里咱们来看一下其源码:
@Component @Scope("prototype") public class HandlerContext { HandlerContext prev; HandlerContext next; Handler handler; private Task task; public void fireTaskReceived(Request request) { invokeTaskReceived(next(), request); } /** * 处理接收到任务的事件 */ static void invokeTaskReceived(HandlerContext ctx, Request request) { if (ctx != null) { try { ctx.handler().receiveTask(ctx, request); } catch (Throwable e) { ctx.handler().exceptionCaught(ctx, e); } } } public void fireTaskFiltered(Task task) { invokeTaskFiltered(next(), task); } /** * 处理任务过滤事件 */ static void invokeTaskFiltered(HandlerContext ctx, Task task) { if (null != ctx) { try { ctx.handler().filterTask(ctx, task); } catch (Throwable e) { ctx.handler().exceptionCaught(ctx, e); } } } public void fireTaskExecuted(Task task) { invokeTaskExecuted(next(), task); } /** * 处理执行任务事件 */ static void invokeTaskExecuted(HandlerContext ctx, Task task) { if (null != ctx) { try { ctx.handler().executeTask(ctx, task); } catch (Exception e) { ctx.handler().exceptionCaught(ctx, e); } } } public void fireAfterCompletion(HandlerContext ctx) { invokeAfterCompletion(next()); } static void invokeAfterCompletion(HandlerContext ctx) { if (null != ctx) { ctx.handler().afterCompletion(ctx); } } private HandlerContext next() { return next; } private Handler handler() { return handler; } }
在HandlerContext
中,咱们须要说明以下几点:
Handler
接口默认实现的ctx.fireXXX()
方法,在这里都委托给了对应的invokeXXX()
方法进行调用,并且咱们须要注意到,在传递给invokeXXX()
方法的参数里,传入的HandlerContext
对象都是经过next()
方法获取到的。也就是说咱们在Handler
中调用ctx.fireXXX()
方法时,都是在调用当前handler的下一个handler对应层级的方法,经过这种方式咱们就实现了链的往下传递。Handler
中若是想让链往下传递,只须要调用ctx.fireXXX()
方法便可,也就是说,若是咱们在某个Handler
中,若是根据业务,当前层级已经调用完成,而无需调用后续的Handler
,那么咱们就不须要调用ctx.fireXXX()
方法便可;HandlerContext
中,咱们也实现了invokeXXX()
方法,该方法的主要做用是供给外部的Pipeline
进行调用的,以开启每一个层级的链;invokeXXX()
方法中,咱们都使用try…catch将当前层级的调用抛出的异常给捕获了,而后调用ctx.handler().exceptionCaught()
方法处理该异常,这也就是咱们前面说的,若是想处理当前Handler
中的异常,只须要实现该Handler
中的exceptionCaught()
方法便可,异常捕获流程就是在这里的HandlerContext
中进行处理的;HandlerContext
的声明处,咱们须要注意到,其使用了@Component
和@Scope("prototype")
注解进行标注了,这说明咱们的HandlerContext
是由Spring所管理的一个bean,而且因为咱们每个Handler
实际上都由一个HandlerContext
维护着,因此这里必须声明为prototype
类型。经过这种方式,咱们的HandlerContext
也就具有了诸如Spring相关的bean的功能,也就可以根据业务需求进行一些额外的处理了; 前面咱们讲解了Handler
和HandlerContext
的具体实现,以及实现的过程当中须要注意的问题,下面咱们就来看一下进行流程控制的Pipeline
是如何实现的,以下是Pipeline
接口的定义:
public interface Pipeline { Pipeline fireTaskReceived(); Pipeline fireTaskFiltered(); Pipeline fireTaskExecuted(); Pipeline fireAfterCompletion(); }
这里 主要是定义了一个Pipeline
接口,该接口定义了一系列的层级调用,是每一个层级的入口方法。以下是该接口的一个实现类:
@Component("pipeline") @Scope("prototype") public class DefaultPipeline implements Pipeline, ApplicationContextAware, InitializingBean { // 建立一个默认的handler,将其注入到首尾两个节点的HandlerContext中,其做用只是将链往下传递 private static final Handler DEFAULT_HANDLER = new Handler() {}; // 将ApplicationContext注入进来的主要缘由在于,HandlerContext是prototype类型的,于是须要 // 经过ApplicationContext.getBean()方法来获取其实例 private ApplicationContext context; // 建立一个头结点和尾节点,这两个节点内部没有作任何处理,只是默认的将每一层级的链往下传递, // 这里头结点和尾节点的主要做用就是用于标志整个链的首尾,全部的业务节点都在这两个节点中间 private HandlerContext head; private HandlerContext tail; // 用于业务调用的request对象,其内部封装了业务数据 private Request request; // 用于执行任务的task对象 private Task task; // 最初始的业务数据须要经过构造函数传入,由于这是驱动整个pipeline所须要的数据, // 通常经过外部调用方的参数进行封装便可 public DefaultPipeline(Request request) { this.request = request; } // 这里咱们能够看到,每一层级的调用都是经过HandlerContext.invokeXXX(head)的方式进行的, // 也就是说咱们每一层级链的入口都是从头结点开始的,固然在某些状况下,咱们也须要从尾节点开始链 // 的调用,这个时候传入tail便可。 @Override public Pipeline fireTaskReceived() { HandlerContext.invokeTaskReceived(head, request); return this; } // 触发任务过滤的链调用 @Override public Pipeline fireTaskFiltered() { HandlerContext.invokeTaskFiltered(head, task); return this; } // 触发任务执行的链执行 @Override public Pipeline fireTaskExecuted() { HandlerContext.invokeTaskExecuted(head, task); return this; } // 触发最终完成的链的执行 @Override public Pipeline fireAfterCompletion() { HandlerContext.invokeAfterCompletion(head); return this; } // 用于往Pipeline中添加节点的方法,读者朋友也能够实现其余的方法用于进行链的维护 void addLast(Handler handler) { HandlerContext handlerContext = newContext(handler); tail.prev.next = handlerContext; handlerContext.prev = tail.prev; handlerContext.next = tail; tail.prev = handlerContext; } // 这里经过实现InitializingBean接口来达到初始化Pipeline的目的,能够看到,这里初始的时候 // 咱们经过ApplicationContext实例化了两个HandlerContext对象,而后将head.next指向tail节点, // 将tail.prev指向head节点。也就是说,初始时,整个链只有头结点和尾节点。 @Override public void afterPropertiesSet() throws Exception { head = newContext(DEFAULT_HANDLER); tail = newContext(DEFAULT_HANDLER); head.next = tail; tail.prev = head; } // 使用默认的Handler初始化一个HandlerContext private HandlerContext newContext(Handler handler) { HandlerContext context = this.context.getBean(HandlerContext.class); context.handler = handler; return context; } // 注入ApplicationContext对象 @Override public void setApplicationContext(ApplicationContext applicationContext) { this.context = applicationContext; } }
关于DefaultPipeline
的实现,主要有以下几点须要说明:
DefaultPipeline
使用@Component
和@Scope("prototype")
注解进行了标注,前一个注解用于将其声明为一个Spring容器所管理的bean,然后一个注解则用于表征DefaultPipeline
是一个多例类型的,很明显,这里的Pipeline
是有状态的。这里须要进行说明的是,"有状态"主要是由于咱们可能会根据业务状况动态的调整个链的节点状况,并且这里的Request
和Task
对象都是与具体的业务相关的,于是必须声明为prototype
类型;Request
对象是经过构造Pipeline
对象的时候传进来的,而Task
对象则是在Pipeline
的流转过程当中生成的,这里好比经过完成fireTaskReceived()
链的调用以后,就须要经过外部请求Request
获得一个Task
对象,从而进行整个Pipeline
的后续处理; 这里咱们已经实现了Pipeline
,HandlerContext
和Handler
,知道这些bean都是被Spring所管理的bean,那么咱们接下来的问题主要在于如何进行整个链的组装。这里的组装方式比较简单,其主要须要解决两个问题:
Handler
接口便可,而无需处理与链相关的全部逻辑,于是咱们须要获取到全部实现了Handler
接口的bean;Handler
接口的bean经过HandlerContext
进行封装,而后将其添加到Pipeline
中。这里的第一个问题比较好处理,由于经过ApplicationContext就能够获取实现了某个接口的全部bean,而第二个问题咱们能够经过声明一个实现了BeanPostProcessor接口的类来实现。以下是其实现代码:
@Component public class HandlerBeanProcessor implements BeanPostProcessor, ApplicationContextAware { private ApplicationContext context; // 该方法会在一个bean初始化完成后调用,这里主要是在Pipeline初始化完成以后获取全部实现了 // Handler接口的bean,而后经过调用Pipeline.addLast()方法将其添加到pipeline中 @Override public Object postProcessAfterInitialization(Object bean, String beanName) { if (bean instanceof DefaultPipeline) { DefaultPipeline pipeline = (DefaultPipeline) bean; Map<String, Handler> handlerMap = context.getBeansOfType(Handler.class); handlerMap.forEach((name, handler) -> pipeline.addLast(handler)); } return bean; } @Override public void setApplicationContext(ApplicationContext applicationContext) { this.context = applicationContext; } }
这里咱们整个链的维护工做就已经完成,能够看到,如今基本上已经实现了前面图中整个链式流程的控制。这里须要说明的一点是,上面的HandlerBeanProcessor.postProcessAfterInitialization()
方法的执行是在InitializingBean.afterPropertySet()
方法以后执行的,也就是说这里HandlerBeanProcessor
在执行时,整个Pipeline
是已经初始化完成了的。下面咱们来看一下外部客户端如何进行整个链是流程的控制:
@Service public class ApplicationService { @Autowired private ApplicationContext context; public void mockedClient() { Request request = new Request(); // request通常是经过外部调用获取 Pipeline pipeline = newPipeline(request); try { pipeline.fireTaskReceived(); pipeline.fireTaskFiltered(); pipeline.fireTaskExecuted(); } finally { pipeline.fireAfterCompletion(); } } private Pipeline newPipeline(Request request) { return context.getBean(DefaultPipeline.class, request); } }
这里咱们模拟了一个客户端的调用,首先建立了一个Pipeline
对象,而后依次调用其各个层级的方法,而且这里咱们使用try…finally结构来保证Pipeline.fireAfterCompletion()
方法必定会执行。如此咱们就完成了整个责任链模式的构造。这里咱们使用前面用到的时效性过滤的filter来做为示例来实现一个Handler
:
@Component public class DurationHandler implements Handler { @Override public void filterTask(HandlerContext ctx, Task task) { System.out.println("时效性检验"); ctx.fireTaskFiltered(task); } }
关于这里的具体业务Handler
咱们须要说明的有以下几点:
Handler
必须使用@Conponent
注解来将其声明为Spring容器所管理的一个bean,这样咱们前面实现的HandlerBeanProcessor
才能将其动态的添加到整个Pipeline
中;Handler
中,须要根据当前的业务须要来实现具体的层级方法,好比这里是进行时效性检验,就是"任务过滤"这一层级的逻辑,由于时效性检验经过咱们才能执行这个task,于是这里须要实现的是Handler.filterTask()
方法,若是咱们须要实现的是执行task的逻辑,那么须要实现的就是Handler.executeTask()
方法;ctx.fireTaskFiltered(task);
方法的调用,咱们能够看前面HandlerContext.fireXXX()
方法就是会获取当前节点的下一个节点,而后进行调用。若是根据业务须要,不须要将链往下传递,那么就不须要调用ctx.fireTaskFiltered(task);
;如此,咱们就经过两种方式实现了责任链模式,并且咱们实现的责任链模式都是符合"开-闭"原则的,也就是说后续咱们要为链添加新的节点的时候,只须要根据规范实现相应的接口便可,而无需处理链的维护相关的工做。关于第二种实现方式,这里咱们并无实现链节点的顺序控制功能,以及如何动态的添加或删除链的节点,更有甚者,若是控制每一个Handler是单例的仍是多例的。固然,有了前面的框架,这些点实现起来也比较简单,这里权当起到一个抛砖引玉的做用,读者朋友可根据本身的须要进行实现。