序言
责任链模式(Chain of Responsibility Pattern)是一种行为型设计模式,类似于日常工作中的审批流,一个人审批完之后交给下一个人审批,两个人又相互不知道对方的存在。
责任链模式通过构建一个对象链来处理请求,每个对象都有可能处理这个请求,或者将请求传递给链上的下一个对象。这种模式赋予了对象处理请求的灵活性,同时解耦了发送者和接收者,使得请求的发送者和接收者不必知晓彼此的细节。
定义
Avoid coupling the sender of a request to its receiver by giving more than one object a chance to handle the request.
Chain the receiving objects and pass the request along the chain until an object handles it.
使多个对象都有机会处理请求,从而避免请求的发送者和接收者之间的耦合关系。将这些对象连成一条链,并沿着这条链传递该请求,直到有一个对象处理它为止。
结构
让我们从一个问题开始
现在我们在项目中处理异常都是直接将异常抛出,然后交给Spring的@ExceptionHandler
来处理。试想一下如果没有Spring我们如何处理异常呢?
一种方法就是使用大量的if
判断,如下
1 2 3 4 5 6 7 8 9 10 11 public void handleException (Exception ex, HttpServletRequest request, HttpServletResponse response) { if (ex instanceof ServiceException) { } else if (ex instanceof HttpRequestMethodNotSupportedException) { } else if (ex instanceof ...) { } else { } }
如果需要对新的异常进行处理,那就增加新的if
代码块。这样也能解决问题,但是解决问题的方式不够优雅,那有没有更优雅一点的方法呢。其中一种方法就是责任链模式。当然还有其他的方式,其他方式在后续其他设计模式的章节中介绍。
责任链模式的结构
其结构如下:
$2ExceptionHandler handle(ex:Exception, request:HttpServletRequest, response:HttpServletResponse) setNext(handler: ExceptionHandler) ServiceExceptionHandler next:ExceptionHandler; handle(ex:Exception, request:HttpServletRequest, response:HttpServletResponse) setNext(handler: ExceptionHandler) HttpRequestMethodNotSupportedExceptionHandler next:ExceptionHandler; handle(ex:Exception, request:HttpServletRequest, response:HttpServletResponse) setNext(handler: ExceptionHandler) GlobalExceptionHandler next:ExceptionHandler; handle(ex:Exception, request:HttpServletRequest, response:HttpServletResponse) setNext(handler: ExceptionHandler) ExceptionLoggingHandler next:ExceptionHandler; handle(ex:Exception, request:HttpServletRequest, response:HttpServletResponse) setNext(handler: ExceptionHandler)
这里我们需要通过setNext
方法将其组成一个链,类似于数据结构中的单向链表。也即我们需要为每个ExceptionHandler
指定其下一个处理器。
代码实现
这里需要先定义一前置的业务异常类 ServiceException
。
1 2 3 4 5 public class ServiceException extends RuntimeException { public ServiceException (String message) { super (message); } }
然后定义异常处理器的接口。
1 2 3 4 public interface ExceptionHandler { void handle (Exception exception, HttpServletRequest request, HttpServletResponse response) ; void setNext (ExceptionHandler next) ; }
定义异常处理器
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 @Slf4j public class ExceptionLoggingHandler implements ExceptionHandler { private ExceptionHandler next; @Override public void handle (Exception exception, HttpServletRequest request, HttpServletResponse response) { log.error("异常信息日志" , exception); if (this .next != null ) { this .next.handle(exception, request, response); } } @Override public void setNext (ExceptionHandler next) { this .next = next; } } public class ServiceExceptionHandler implements ExceptionHandler { private ExceptionHandler next; @Override public void handle (Exception exception, HttpServletRequest request, HttpServletResponse response) { if (exception instanceof ServiceException serviceException) { response.setStatus(HttpServletResponse.SC_OK); response.setContentType("text/plain" ); try { response.getWriter().print(serviceException.getMessage()); } catch (IOException e) { } return ; } if (this .next != null ) { this .next.handle(exception, request, response); } } @Override public void setNext (ExceptionHandler next) { this .next = next; } } public class HttpRequestMethodNotSupportedExceptionHandler implements ExceptionHandler { private ExceptionHandler next; @Override public void handle (Exception exception, HttpServletRequest request, HttpServletResponse response) { if (exception instanceof HttpRequestMethodNotSupportedException ex) { response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR); response.setContentType("text/plain" ); try { response.getWriter().print("请求方式错误,本接口仅支持[" + String.join("," , ex.getSupportedMethods()) + "]请求方式。" ); } catch (IOException e) { } return ; } if (this .next != null ) { this .next.handle(exception, request, response); } } @Override public void setNext (ExceptionHandler next) { this .next = next; } } public class GlobalExceptionHandler implements ExceptionHandler { private ExceptionHandler next; @Override public void handle (Exception exception, HttpServletRequest request, HttpServletResponse response) { response.setStatus(HttpServletResponse.SC_OK); response.setContentType("text/plain" ); try { response.getWriter().print("系统异常" ); } catch (IOException e) { } if (this .next != null ) { this .next.handle(exception, request, response); } } @Override public void setNext (ExceptionHandler next) { this .next = next; } }
这里我们用到了HttpServletRequest
和HttpServletResponse
,我们可以借助mockito
在没有web环境的情况下,进行测试,具体如下。
我们需要引入servlet-api
和mockito-core
。
1 2 3 4 5 6 7 8 9 10 11 12 <dependency > <groupId > jakarta.servlet</groupId > <artifactId > jakarta.servlet-api</artifactId > <version > 6.0.0</version > </dependency > <dependency > <groupId > org.mockito</groupId > <artifactId > mockito-core</artifactId > <version > 5.11.0</version > </dependency >
使用我们的异常处理链。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 public class Main { public static void main (String[] args) throws IOException { ExceptionLoggingHandler exceptionLoggingHandler = new ExceptionLoggingHandler (); ServiceExceptionHandler serviceExceptionHandler = new ServiceExceptionHandler (); HttpRequestMethodNotSupportedExceptionHandler httpRequestMethodNotSupportedExceptionHandler = new HttpRequestMethodNotSupportedExceptionHandler (); GlobalExceptionHandler globalExceptionHandler = new GlobalExceptionHandler (); exceptionLoggingHandler.setNext(serviceExceptionHandler); serviceExceptionHandler.setNext(httpRequestMethodNotSupportedExceptionHandler); httpRequestMethodNotSupportedExceptionHandler.setNext(globalExceptionHandler); HttpServletRequest request = Mockito.mock(HttpServletRequest.class); HttpServletResponse response = mockResponse(); ServiceException serviceException = new ServiceException ("这是一个业务异常" ); exceptionLoggingHandler.handle(serviceException, request, response); System.out.println("-------------------------------------------------------" ); HttpRequestMethodNotSupportedException exception = new HttpRequestMethodNotSupportedException ("GET" , Sets.newSet("POST" )); exceptionLoggingHandler.handle(exception, request, response); System.out.println("-------------------------------------------------------" ); Exception ex = new Exception ("一个系统异常" ); exceptionLoggingHandler.handle(ex, request, response); } private static HttpServletResponse mockResponse () throws IOException { HttpServletResponse response = Mockito.mock(HttpServletResponse.class); PrintWriter printWriter = Mockito.mock(PrintWriter.class); Mockito.doAnswer(invocation -> printWriter).when(response).getWriter(); Mockito.doAnswer(invocationOnMock -> { Object[] args = invocationOnMock.getArguments(); String output = (String) args[0 ]; System.out.println(output); return null ; }).when(printWriter).print(Mockito.anyString()); return response; } }
输出结果
1 2 3 4 5 6 7 8 9 10 11 12 13 14 23 :44 :28.525 [main] ERROR org.depsea .designpattern .behavioral .chainofresponsibility .ExceptionLoggingHandler -- 异常信息org.depsea .designpattern .behavioral .chainofresponsibility .ServiceException : 这是一个业务异常 at org.depsea .designpattern .behavioral .chainofresponsibility .Main .main (Main.java :30 ) 这是一个业务异常 ------------------------------------------------------- 23 :44 :28.536 [main] ERROR org.depsea .designpattern .behavioral .chainofresponsibility .ExceptionLoggingHandler -- 异常信息org.springframework .web .HttpRequestMethodNotSupportedException : Request method 'GET' is not supported at org.depsea .designpattern .behavioral .chainofresponsibility .Main .main (Main.java :35 ) 请求方式错误,本接口仅支持[POST] 请求方式。 ------------------------------------------------------- 23 :44 :28.537 [main] ERROR org.depsea .designpattern .behavioral .chainofresponsibility .ExceptionLoggingHandler -- 异常信息java.lang .Exception : 一个系统异常 at org.depsea .designpattern .behavioral .chainofresponsibility .Main .main (Main.java :40 ) 系统异常
上面我们定义的异常处理器有大量重复的代码,我们可以用抽象类抽象出部分相同的业务,以此来优化代码。如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 public abstract class AbstractExceptionHandler implements ExceptionHandler { protected ExceptionHandler next; @Override public void handle (Exception exception, HttpServletRequest request, HttpServletResponse response) { if (this .doHandle(exception, request, response) && next != null ) { this .next.handle(exception, request, response); } } protected abstract boolean doHandle (Exception exception, HttpServletRequest request, HttpServletResponse response) ; @Override public void setNext (ExceptionHandler next) { this .next = next; } }
下面修改我们的ServiceExceptionHandler
。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 public class ServiceExceptionHandler extends AbstractExceptionHandler { @Override protected boolean doHandle (Exception exception, HttpServletRequest request, HttpServletResponse response) { if (exception instanceof ServiceException serviceException) { response.setStatus(HttpServletResponse.SC_OK); response.setContentType("text/plain" ); try { response.getWriter().print(serviceException.getMessage()); } catch (IOException e) { } return false ; } return true ; } }
可以看出ServiceExceptionHandler
中的代码量少了很多,而且处理器仅需要专注于异常的处理,而不用关心如何组成链。
其他类的代码此处不再提供,有兴趣的同学可以自己实现一下。
这里我们虽然用抽象类抽象了相同的业务代码,使得我们的具体异常处理类的代码量减少了,但是在使用时还有点怪怪的感觉,我们每创建一个处理类的实例都需要为其指定下一个处理器。难道没有更优雅的方式来使得链上的对象能够有序执行吗?
对于上述问题,就是提供一个链容器,由容器负责管理链和执行链上的处理器方法。此处我们不再具体实现。Spring的拦截器和Tomcat的过滤器为我们提供了两种实现链容器的思路。具体请看下面的源码分析部分。
在开源框架中的应用
Apache Tomcat过滤器
Tomcat中使用FilterChain
来处理过滤器链。其结构如下
$2FilterChain doFilter(request, response) Filter init(filterConfig:FilterConfig) doFilter(request, response, filterChain) destroy() ApplicationFilterChain filters:ApplicationFilterConfig addFilter(filterConfig:ApplicationFilterConfig) doFilter(request, response) internalDoFilter(request, response) ApplicationFilterFactory createFilterChain(request,wrapper,servlet):ApplicationFilterChain ApplicationFilterConfig filter: Filter getFilter():Filter
当用户的请求提交后,Tomcat会通过ApplicationFilterChain#createFilterChain
创建过滤器链,然后通过addFilter
向链中添加过滤器。这部分不是本章的重点,属于Tomcat源码部分,因此再次不再赘述。有兴趣的同学可以研究下Tomcat源码。
在ApplicationFilterChain
中,链中其实是ApplicationFilterConfig
,目的是为了对过滤器进行初始化,确保过滤器是单例的。如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 Filter getFilter () throws ClassCastException, ReflectiveOperationException, ServletException, NamingException, IllegalArgumentException, SecurityException { if (this .filter != null ) { return this .filter; } String filterClass = filterDef.getFilterClass(); this .filter = (Filter) context.getInstanceManager().newInstance(filterClass); initFilter(); return this .filter; }
当然,这也不是本章的重点。
本章的重点在于 doFilter
和 internalDoFilter
方法。ApplicationFilterChain#doFilter
中又调用了internalDoFilter
方法,这里我们只看internalDoFilter
的源码。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 private void internalDoFilter (ServletRequest request, ServletResponse response) throws IOException, ServletException { if (pos < n) { ApplicationFilterConfig filterConfig = filters[pos++]; try { Filter filter = filterConfig.getFilter(); if (request.isAsyncSupported() && "false" .equalsIgnoreCase( filterConfig.getFilterDef().getAsyncSupported())) { request.setAttribute(Globals.ASYNC_SUPPORTED_ATTR, Boolean.FALSE); } 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 { filter.doFilter(request, response, this ); } } catch (IOException | ServletException | RuntimeException e) { throw e; } catch (Throwable e) { e = ExceptionUtils.unwrapInvocationTargetException(e); ExceptionUtils.handleThrowable(e); throw new ServletException (sm.getString("filterChain.filter" ), e); } return ; } ...... }
看到这里,你可会疑惑,这里没有循环啊。怎么做到一个接一个的处理的呢。还记得Filter.doFilter()
方法中的参数吗。
1 2 public void doFilter (ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException;
这里是需要传入一个 FilterChain
的,如果当前Filter执行完毕后,还需要执行后续的Filter,我们一般会在doFilter
方法中加入以下代码。
1 chain.do Filter (request , response );
这样就形成了一个循环,流程如下图。
Spring中有一个更简洁的实现,在org.springframework.web.filter.CompositeFilter
中,是一个内部类,名为VirtualFilterChain
。具体代码如下,思想雷同。CompositeFilter
使用了组合模式,内部这个过滤器链是为了执行CompositeFilter
中聚合的Filter
。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 public class CompositeFilter implements Filter { private List<? extends Filter > filters = new ArrayList (); ...... public void doFilter (ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { (new VirtualFilterChain (chain, this .filters)).doFilter(request, response); } ...... private static class VirtualFilterChain implements FilterChain { private final FilterChain originalChain; private final List<? extends Filter > additionalFilters; private int currentPosition = 0 ; public VirtualFilterChain (FilterChain chain, List<? extends Filter> additionalFilters) { this .originalChain = chain; this .additionalFilters = additionalFilters; } @Override public void doFilter (final ServletRequest request, final ServletResponse response) throws IOException, ServletException { if (this .currentPosition == this .additionalFilters.size()) { this .originalChain.doFilter(request, response); } else { this .currentPosition++; Filter nextFilter = this .additionalFilters.get(this .currentPosition - 1 ); nextFilter.doFilter(request, response, this ); } } } }
Spring MVC拦截器
Spring拦截器采用了一种更加简洁的设计,链中存储了一个拦截器列表,直接通过遍历来依次执行链中的每个拦截器。核心类有两个,分别是:
rg.springframework.web.servlet.HandlerInterceptor
org.springframework.web.servlet.HandlerExecutionChain
下面是具体代码。我们先看拦截器接口HandlerInterceptor
的定义
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 public interface HandlerInterceptor { default boolean preHandle (HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { return true ; } default void postHandle (HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable ModelAndView modelAndView) throws Exception { } ...... }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 public class HandlerExecutionChain { private final Object handler; private final List<HandlerInterceptor> interceptorList = new ArrayList <>(); private int interceptorIndex = -1 ; ...... public HandlerExecutionChain (Object handler, List<HandlerInterceptor> interceptorList) { if (handler instanceof HandlerExecutionChain) { HandlerExecutionChain originalChain = (HandlerExecutionChain) handler; this .handler = originalChain.getHandler(); this .interceptorList.addAll(originalChain.interceptorList); } else { this .handler = handler; } this .interceptorList.addAll(interceptorList); } ...... boolean applyPreHandle (HttpServletRequest request, HttpServletResponse response) throws Exception { for (int i = 0 ; i < this .interceptorList.size(); i++) { HandlerInterceptor interceptor = this .interceptorList.get(i); if (!interceptor.preHandle(request, response, this .handler)) { triggerAfterCompletion(request, response, null ); return false ; } this .interceptorIndex = i; } return true ; } void applyPostHandle (HttpServletRequest request, HttpServletResponse response, @Nullable ModelAndView mv) throws Exception { for (int i = this .interceptorList.size() - 1 ; i >= 0 ; i--) { HandlerInterceptor interceptor = this .interceptorList.get(i); interceptor.postHandle(request, response, this .handler, mv); } } ...... }
具体执行流程如下
Spring中通过拦截器方法的返回值来决定是否执行下一个拦截器。从而使得链上的所有拦截器的执行只需要通过一次迭代即可完成。比Apache Tomcat中的实现更加的简洁,也更容易理解一些。
这两种责任链的实现方式大家可以参考下,都是很优秀的设计。
适用场景
当程序需要使用不同方式处理不同种类请求,而且请求类型和顺序预先未知时,可以使用责任链模式。该模式能将多个处理者连接成一条链。接收到请求后,它会 “询问”每个处理者是否能够对其进行处理。这样所有处理者都有机会来处理请求。
当必须按顺序执行多个处理者时,可以使用该模式。无论你以何种顺序将处理者连接成一条链,所有请求都会严 格按照顺序通过链上的处理者。
如果所需处理者及其顺序必须在运行时进行改变,可以使用责任链模式。如果在处理者类中有对引用成员变量的设定方法,你将能动 态地插入和移除处理者,或者改变其顺序。
与其他设计模式的关系
责任链通常和组合模式 结合使用。在这种情况下,叶组件接收到请求后,可以将请求沿包含全体父组件的链一直传递至对象树的底部。如Spring中的CompositeFilter
。
总结
在实际业务应用中,责任链模式可以用于审批流程、日志处理、权限验证、异常处理等场景,为系统提供灵活的处理流程,使得系统更加容易扩展和维护。
责任链模式适用于需要动态确定处理者并且请求的处理者不固定的场景,例如请求的处理者可能会根据条件动态变化的情况。
责任链模式的优点在于它可以动态地组织和分配责任,使得系统具有更好的扩展性和可维护性。同时,由于请求的发送者和接收者被解耦,系统的复用性也得到了提高。此外,责任链模式还可以简化对象的结构,使得对象不需要知道其他对象的具体信息。
然而,责任链模式也有一些缺点。首先,由于请求可以在链上传递,因此可能会增加系统的复杂性。其次,如果链过长或设计不当,可能会导致性能下降。此外,责任链模式还可能导致代码阅读和理解的困难。