责任链模式-Chain of Responsibility Pattern
序言
责任链模式(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
判断,如下
public void handleException(Exception ex, HttpServletRequest request, HttpServletResponse response) {
if (ex instanceof ServiceException) {
// 业务异常处理
} else if (ex instanceof HttpRequestMethodNotSupportedException) {
// 请求方法不支持的处理
} else if (ex instanceof ...) {
// ...
} else {
// 系统异常处理
}
}
如果需要对新的异常进行处理,那就增加新的if
代码块。这样也能解决问题,但是解决问题的方式不够优雅,那有没有更优雅一点的方法呢。其中一种方法就是责任链模式。当然还有其他的方式,其他方式在后续其他设计模式的章节中介绍。
责任链模式的结构
其结构如下:
这里我们需要通过setNext
方法将其组成一个链,类似于数据结构中的单向链表。也即我们需要为每个ExceptionHandler
指定其下一个处理器。
代码实现
这里需要先定义一前置的业务异常类 ServiceException
。
public class ServiceException extends RuntimeException{
public ServiceException(String message) {
super(message);
}
}
然后定义异常处理器的接口。
public interface ExceptionHandler{
void handle(Exception exception, HttpServletRequest request, HttpServletResponse response);
void setNext(ExceptionHandler next);
}
定义异常处理器
@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) {
// 如果是业务异常(ServiceException)直接处理,并且截断后续处理
response.setStatus(HttpServletResponse.SC_OK);
response.setContentType("text/plain");
try {
response.getWriter().print(serviceException.getMessage());
} catch (IOException e) {
// ignore
}
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) {
// 如果异常是HttpRequestMethodNotSupportedException直接处理,并且截断后续处理
response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
response.setContentType("text/plain");
try {
response.getWriter().print("请求方式错误,本接口仅支持[" + String.join(",", ex.getSupportedMethods()) + "]请求方式。");
} catch (IOException e) {
// ignore
}
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) {
// ignore
}
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
。
<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>
使用我们的异常处理链。
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);
}
// mock 一个 HttpServletResponse
private static HttpServletResponse mockResponse() throws IOException {
HttpServletResponse response = Mockito.mock(HttpServletResponse.class);
PrintWriter printWriter = Mockito.mock(PrintWriter.class);
// 使用Answer来自定义输出到System.out
// 将getWriter方法重定向到我们mock的PrintWriter
Mockito.doAnswer(invocation -> printWriter).when(response).getWriter();
// 将print方法重定向到System.out.println
Mockito.doAnswer(invocationOnMock -> {
Object[] args = invocationOnMock.getArguments();
String output = (String) args[0];
System.out.println(output); // 将输出重定向到System.out
return null;
}).when(printWriter).print(Mockito.anyString());
return response;
}
}
输出结果
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)
系统异常
上面我们定义的异常处理器有大量重复的代码,我们可以用抽象类抽象出部分相同的业务,以此来优化代码。如下:
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);
}
}
/**
* 如果返回值为true,则表示继续向下执行,否则终止执行后续链上的处理器
*/
protected abstract boolean doHandle(Exception exception, HttpServletRequest request, HttpServletResponse response);
@Override
public void setNext(ExceptionHandler next) {
this.next = next;
}
}
下面修改我们的ServiceExceptionHandler
。
public class ServiceExceptionHandler extends AbstractExceptionHandler {
@Override
protected boolean doHandle(Exception exception, HttpServletRequest request, HttpServletResponse response) {
if (exception instanceof ServiceException serviceException) {
// 如果是业务异常(ServiceException)直接处理,并且截断后续处理
response.setStatus(HttpServletResponse.SC_OK);
response.setContentType("text/plain");
try {
response.getWriter().print(serviceException.getMessage());
} catch (IOException e) {
// ignore
}
return false;
}
return true;
}
}
可以看出ServiceExceptionHandler
中的代码量少了很多,而且处理器仅需要专注于异 常的处理,而不用关心如何组成链。
其他类的代码此处不再提供,有兴趣的同学可以自己实现一下。
这里我们虽然用抽象类抽象了相同的业务代码,使得我们的具体异常处理类的代码量减少了,但是在使用时还有点怪怪的感觉,我们每创建一个处理类的实例都需要为其指定下一个处理器。难道没有更优雅的方式来使得链上的对象能够有序执行吗?
对于上述问题,就是提供一个链容器,由容器负责管理链和执行链上的处理器方法。此处我们不再具体实现。Spring的拦截器和Tomcat的过滤器为我们提供了两种实现链容器的思路。具体请看下面的源码分析部分。
在开源框架中的应用
Apache Tomcat过滤器
Tomcat中使用FilterChain
来处理过滤器链。其结构如下
当用户的请求提交后,Tomcat会通过ApplicationFilterChain#createFilterChain
创建过滤器链,然后通过addFilter
向链中添加过滤器。这部分不是本章的重点,属于Tomcat源码部分,因此再次不再赘述。有兴趣的同学可以研究下Tomcat源码。
在ApplicationFilterChain
中,链中其实是ApplicationFilterConfig
,目的是为了对过滤器进行初始化,确保过滤器是单例的。如下
Filter getFilter() throws ClassCastException, ReflectiveOperationException, ServletException,
NamingException, IllegalArgumentException, SecurityException {
// Return the existing filter instance, if any
if (this.filter != null) {
return this.filter;
}
// Identify the class loader we will be using
String filterClass = filterDef.getFilterClass();
this.filter = (Filter) context.getInstanceManager().newInstance(filterClass);
initFilter();
return this.filter;
}
当然,这也不是本章的重点。
本章的重点在于 doFilter
和 internalDoFilter
方法。ApplicationFilterChain#doFilter
中又调用了internalDoFilter
方法,这里我们只看internalDoFilter
的源码。
private void internalDoFilter(ServletRequest request,
ServletResponse response)
throws IOException, ServletException {
// Call the next filter if there is one
// pos是当前过滤器的索引位置,n是过滤器数量。pos从0开始
if (pos < n) {
// 注意这里的++在后,也即会先实用pos,然后再+1。从链中获取到要执行的过滤器配置
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};
// 通过反射的方式执行过滤器的`doFilter`方法
SecurityUtil.doAsPrivilege ("doFilter", filter, classType, args, principal);
} else {
// 执行过滤器的`doFilter`方法
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;
}
// 这里我们忽略过滤器执行结束后的后续处理。后续还有请求的后续处理代码,
// 主要是servlet.service(request, response);即执行servlet的 service方法,开始处理请求。
......
}
看到这里,你可会疑惑,这里没有循环啊。怎么做到一个接一个的处理的呢。还记得Filter.doFilter()
方法中的参数吗。
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException;
这里是需要传入一个 FilterChain
的,如果当前Filter执行完毕后,还需要执行后续的Filter,我们一般会在doFilter
方法中加入以下代码。
chain.doFilter(request, response);
这样就形成了一个循环,流程如下图。
Spring中有一个更简洁的实现,在org.springframework.web.filter.CompositeFilter
中,是一个内部类,名为VirtualFilterChain
。具体代码如下,思想雷同。CompositeFilter
使用了组合模式,内部这个过滤器链是为了执行CompositeFilter
中聚合的Filter
。
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
的定义
public interface HandlerInterceptor {
/**
* 请求的前置处理,如果返回false,则终止链的执行,后续的拦截器不再执行。
*/
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 {
}
......
}
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);
}
......
/**
* Apply preHandle methods of registered interceptors.
* @return {@code true} if the execution chain should proceed with the
* next interceptor or the handler itself. Else, DispatcherServlet assumes
* that this interceptor has already dealt with the response itself.
*/
boolean applyPreHandle(HttpServletRequest request, HttpServletResponse response) throws Exception {
// 开始执行拦截器前置处理方法
for (int i = 0; i < this.interceptorList.size(); i++) {
// 获取下一个要执行的拦截器
HandlerInterceptor interceptor = this.interceptorList.get(i);
// 执行拦截器的preHandle方法
// 如果方法返回的是false,则执行拦截器的完成方法,并终止后续拦截器的执行。
if (!interceptor.preHandle(request, response, this.handler)) {
triggerAfterCompletion(request, response, null);
return false;
}
// 如果方法返回的是true,则继续执行下一个拦截器
this.interceptorIndex = i;
}
return true;
}
/**
* Apply postHandle methods of registered interceptors.
*/
void applyPostHandle(HttpServletRequest request, HttpServletResponse response, @Nullable ModelAndView mv)
throws Exception {
// 倒序执行拦截器的后置处理方法
// 也即拦截器的顺序如果为 A-->B-->C,那后置方法执行的顺序为:C-->B-->A
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
。
总结
在实际业务应用中,责任链模式可以用于审批流程、日志处理、权限验证、异常处理等场景,为系统提供灵活的处理流程,使得系统更加容易扩展和维护。
责任链模式适用于需要动态确定处理者并且请求的处理者不固定的场景,例如请求的处理者可能会根据条件动态变化的情况。
责任链模式的优点在于它可以动态地组织和分配责任,使得系统具有更好的扩展性和可维护性。同时,由于请求的发送者和接收者被解耦,系统的复用性也得到了提高。此外,责任链模式还可以简化对象的结构,使得对象不需要知道其他对象的具体信息。
然而,责任链模式也有一些缺点。首先,由于请求可以在链上传递,因此可能会增加系统的复杂性。其次,如果链过长或设计不当,可能会导致性能下降。此外,责任链模式还可能导致代码阅读和理解的困难。