责任链模式
模板模式、策略模式和责任链模式,这三种模式具有相同的作用:复用和扩展,在实际的项目开发中比较常用,特别是框架开发中,我们可以利用它们来提供框架的扩展点,能够让框架的使用者在不修改框架源码的情况下,基于扩展点定制化框架的功能。
这篇文章主要讲解责任链模式的原理和实现。除此之外还会贴合实战,通过剖析Servlet Filter、Spring Interceptor来看,如利用责任链模式实现框架中常用的过滤器、拦截器。
原理
职责链模式的英文翻译是Chain Of Responsibility Design Pattern。在GoF的《设计模式》中,它是这么定义的:
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.
翻译成中文就是:将请求的发送和接收解耦,让多个接收对象都有机会处理这个请求。将这些接收对象串成一条链,并沿着这条链传递这个请求,直到链上的某个接收对象能够处理它为止。
这么说比较抽象,我用更加容易理解的话来进一步解读一下。
在职责链模式中,多个处理器(也就是刚刚定义中说的“接收对象”)依次处理同一个请求。一个请求先经过A处理器处理,然后再把请求传递给B处理器,B处理器处理完后再传递给C处理器,以此类推,形成一个链条。链条上的每个处理器各自承担各自的处理职责,所以叫作职责链模式。
实现
关于职责链模式,我们先来看看它的代码实现。结合代码实现,你会更容易理解它的定义。职责链模式有多种实现方式,我们这里介绍两种比较常用的。
第一种实现方式如下所示。其中,Handler是所有处理器类的抽象父类,handle()是抽象方法。每个具体的处理器类(HandlerA、HandlerB)的handle()函数的代码结构类似,如果它能处理该请求,就不继续往下传递;如果不能处理,则交由后面的处理器来处理(也就是调用successor.handle())。HandlerChain是处理器链,从数据结构的角度来看,它就是一个记录了链头、链尾的链表。其中,记录链尾是为了方便添加处理器。
public abstract class Handler {protected Handler successor = null;public void setSuccessor(Handler successor) {this.successor = successor;}public abstract void handle();
}public class Handler extends Handler {@Overridepublic void handle() {boolean handled = false;//...if (!handled && successor != null) {successor.handle();}}
}public class HandlerB extends Handler {@Overridepublic void handle() {boolean handled = false;//...if (!handled && successor != null) {successor.handle();} }
}public class HandlerChain {private Handler head = null;private Handler tail = null;public void addHandler(Handler handler) {handler.setSuccessor(null);if (head == null) {head = handler;tail = handler;return;}tail.setSuccessor(handler);tail = handler;}public void handle() {if (head != null) {head.handle();}}
}// 使用举例
public class Application {public static void main(String[] args) {HandlerChain chain = new HandlerChain();chain.addHandler(new HandlerA());chain.addHandler(new HandlerB());chain.handle();}
}
实际上,上面的代码实现不够优雅。处理器类的handle()函数,不仅包含自己的业务逻辑,还包含对下一个处理器的调用,也就是代码中的successor.handle()。一个不熟悉这种代码结构的程序员,在添加新的处理器类的时候,很有可能忘记在handle()函数中调用successor.handle(),这就会导致代码出现bug。
针对这个问题,我们对代码进行重构,利用模板模式,将调用successor.handle()的逻辑从具体的处理器类中剥离出来,放到抽象父类中。这样具体的处理器类只需要实现自己的业务逻辑就可以了。重构之后的代码如下所示:
public abstract class Handler {protected Handler successor = null;public void setSuccessor(Handler successor) {this.successor = successor;}public final void handle() {boolean handled = doHandle();if (successor != null && !handled) {successor.handle();}}protected abstract boolean doHandle();
}public class HandlerA extends Handler {@Overrideprotected boolean doHandle() {boolean handled = false;//...return handled;}
}public class HandlerB extends Handler {@Overrideprotected boolean doHandle() {boolean handled = false;//...return handled;}
}// HandlerChain和Application代码不变
框架中的责任链模式
职责链模式常用在框架的开发中,为框架提供扩展点,让框架的使用者在不修改框架源码的情况下,基于扩展点添加新的功能。实际上,更具体点来说,职责链模式最常用来开发框架的过滤器和拦截器。今天,我们就通过Servlet Filter、Spring Interceptor这两个Java开发中常用的组件,来具体讲讲它在框架开发中的应用。
Servlet责任链:
/** Licensed to the Apache Software Foundation (ASF) under one or more* contributor license agreements. See the NOTICE file distributed with* this work for additional information regarding copyright ownership.* The ASF licenses this file to You under the Apache License, Version 2.0* (the "License"); you may not use this file except in compliance with* the License. You may obtain a copy of the License at** http://www.apache.org/licenses/LICENSE-2.0** Unless required by applicable law or agreed to in writing, software* distributed under the License is distributed on an "AS IS" BASIS,* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.* See the License for the specific language governing permissions and* limitations under the License.*/
package org.apache.catalina.core;import java.io.IOException;
import java.security.Principal;
import java.security.PrivilegedActionException;
import java.util.Set;import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.Servlet;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;import org.apache.catalina.Globals;
import org.apache.catalina.security.SecurityUtil;
import org.apache.tomcat.util.ExceptionUtils;
import org.apache.tomcat.util.res.StringManager;/*** Implementation of <code>javax.servlet.FilterChain</code> used to manage the execution of a set of filters for a* particular request. When the set of defined filters has all been executed, the next call to <code>doFilter()</code>* will execute the servlet's <code>service()</code> method itself.** @author Craig R. McClanahan*/
public final class ApplicationFilterChain implements FilterChain {// Used to enforce requirements of SRV.8.2 / SRV.14.2.5.1private static final ThreadLocal<ServletRequest> lastServicedRequest;private static final ThreadLocal<ServletResponse> lastServicedResponse;static {if (ApplicationDispatcher.WRAP_SAME_OBJECT) {lastServicedRequest = new ThreadLocal<>();lastServicedResponse = new ThreadLocal<>();} else {lastServicedRequest = null;lastServicedResponse = null;}}// -------------------------------------------------------------- Constantspublic static final int INCREMENT = 10;// ----------------------------------------------------- Instance Variables/*** Filters.*/private ApplicationFilterConfig[] filters = new ApplicationFilterConfig[0];/*** The int which is used to maintain the current position in the filter chain.*/private int pos = 0;/*** The int which gives the current number of filters in the chain.*/private int n = 0;/*** The servlet instance to be executed by this chain.*/private Servlet servlet = null;/*** Does the associated servlet instance support async processing?*/private boolean servletSupportsAsync = false;/*** The string manager for our package.*/private static final StringManager sm = StringManager.getManager(ApplicationFilterChain.class);/*** Static class array used when the SecurityManager is turned on and <code>doFilter</code> is invoked.*/private static final Class<?>[] classType =new Class[] { ServletRequest.class, ServletResponse.class, FilterChain.class };/*** Static class array used when the SecurityManager is turned on and <code>service</code> is invoked.*/private static final Class<?>[] classTypeUsedInService =new Class[] { ServletRequest.class, ServletResponse.class };// ---------------------------------------------------- FilterChain Methods/*** Invoke the next filter in this chain, passing the specified request and response. If there are no more filters in* this chain, invoke the <code>service()</code> method of the servlet itself.** @param request The servlet request we are processing* @param response The servlet response we are creating** @exception IOException if an input/output error occurs* @exception ServletException if a servlet exception occurs*/@Overridepublic void doFilter(ServletRequest request, ServletResponse response) throws IOException, ServletException {if (Globals.IS_SECURITY_ENABLED) {final ServletRequest req = request;final ServletResponse res = response;try {java.security.AccessController.doPrivileged((java.security.PrivilegedExceptionAction<Void>) () -> {internalDoFilter(req, res);return null;});} catch (PrivilegedActionException pe) {Exception e = pe.getException();if (e instanceof ServletException) {throw (ServletException) e;} else if (e instanceof IOException) {throw (IOException) e;} else if (e instanceof RuntimeException) {throw (RuntimeException) e;} else {throw new ServletException(e.getMessage(), e);}}} else {internalDoFilter(request, response);}}private void internalDoFilter(ServletRequest request, ServletResponse response)throws IOException, ServletException {// Call the next filter if there is oneif (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;}// We fell off the end of the chain -- call the servlet instancetry {if (ApplicationDispatcher.WRAP_SAME_OBJECT) {lastServicedRequest.set(request);lastServicedResponse.set(response);}if (request.isAsyncSupported() && !servletSupportsAsync) {request.setAttribute(Globals.ASYNC_SUPPORTED_ATTR, Boolean.FALSE);}// Use potentially wrapped request from this pointif ((request instanceof HttpServletRequest) && (response instanceof HttpServletResponse) &&Globals.IS_SECURITY_ENABLED) {final ServletRequest req = request;final ServletResponse res = response;Principal principal = ((HttpServletRequest) req).getUserPrincipal();Object[] args = new Object[] { req, res };SecurityUtil.doAsPrivilege("service", servlet, classTypeUsedInService, args, principal);} else {servlet.service(request, response);}} catch (IOException | ServletException | RuntimeException e) {throw e;} catch (Throwable e) {e = ExceptionUtils.unwrapInvocationTargetException(e);ExceptionUtils.handleThrowable(e);throw new ServletException(sm.getString("filterChain.servlet"), e);} finally {if (ApplicationDispatcher.WRAP_SAME_OBJECT) {lastServicedRequest.set(null);lastServicedResponse.set(null);}}}/*** The last request passed to a servlet for servicing from the current thread.** @return The last request to be serviced.*/public static ServletRequest getLastServicedRequest() {return lastServicedRequest.get();}/*** The last response passed to a servlet for servicing from the current thread.** @return The last response to be serviced.*/public static ServletResponse getLastServicedResponse() {return lastServicedResponse.get();}// -------------------------------------------------------- Package Methods/*** Add a filter to the set of filters that will be executed in this chain.** @param filterConfig The FilterConfig for the servlet to be executed*/void addFilter(ApplicationFilterConfig filterConfig) {// Prevent the same filter being added multiple timesfor (ApplicationFilterConfig filter : filters) {if (filter == filterConfig) {return;}}if (n == filters.length) {ApplicationFilterConfig[] newFilters = new ApplicationFilterConfig[n + INCREMENT];System.arraycopy(filters, 0, newFilters, 0, n);filters = newFilters;}filters[n++] = filterConfig;}/*** Release references to the filters and wrapper executed by this chain.*/void release() {for (int i = 0; i < n; i++) {filters[i] = null;}n = 0;pos = 0;servlet = null;servletSupportsAsync = false;}/*** Prepare for reuse of the filters and wrapper executed by this chain.*/void reuse() {pos = 0;}/*** Set the servlet that will be executed at the end of this chain.** @param servlet The Wrapper for the servlet to be executed*/void setServlet(Servlet servlet) {this.servlet = servlet;}void setServletSupportsAsync(boolean servletSupportsAsync) {this.servletSupportsAsync = servletSupportsAsync;}/*** Identifies the Filters, if any, in this FilterChain that do not support async.** @param result The Set to which the fully qualified class names of each Filter in this FilterChain that does not* support async will be added*/public void findNonAsyncFilters(Set<String> result) {for (int i = 0; i < n; i++) {ApplicationFilterConfig filter = filters[i];if ("false".equalsIgnoreCase(filter.getFilterDef().getAsyncSupported())) {result.add(filter.getFilterClass());}}}
}
Spring拦截器:
/** Copyright 2002-2020 the original author or authors.** Licensed under the Apache License, Version 2.0 (the "License");* you may not use this file except in compliance with the License.* You may obtain a copy of the License at** https://www.apache.org/licenses/LICENSE-2.0** Unless required by applicable law or agreed to in writing, software* distributed under the License is distributed on an "AS IS" BASIS,* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.* See the License for the specific language governing permissions and* limitations under the License.*/package org.springframework.web.servlet;import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;import org.springframework.lang.Nullable;
import org.springframework.util.CollectionUtils;/*** Handler execution chain, consisting of handler object and any handler interceptors.* Returned by HandlerMapping's {@link HandlerMapping#getHandler} method.** @author Juergen Hoeller* @since 20.06.2003* @see HandlerInterceptor*/
public class HandlerExecutionChain {private static final Log logger = LogFactory.getLog(HandlerExecutionChain.class);private final Object handler;private final List<HandlerInterceptor> interceptorList = new ArrayList<>();private int interceptorIndex = -1;/*** Create a new HandlerExecutionChain.* @param handler the handler object to execute*/public HandlerExecutionChain(Object handler) {this(handler, (HandlerInterceptor[]) null);}/*** Create a new HandlerExecutionChain.* @param handler the handler object to execute* @param interceptors the array of interceptors to apply* (in the given order) before the handler itself executes*/public HandlerExecutionChain(Object handler, @Nullable HandlerInterceptor... interceptors) {this(handler, (interceptors != null ? Arrays.asList(interceptors) : Collections.emptyList()));}/*** Create a new HandlerExecutionChain.* @param handler the handler object to execute* @param interceptorList the list of interceptors to apply* (in the given order) before the handler itself executes* @since 5.3*/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);}/*** Return the handler object to execute.*/public Object getHandler() {return this.handler;}/*** Add the given interceptor to the end of this chain.*/public void addInterceptor(HandlerInterceptor interceptor) {this.interceptorList.add(interceptor);}/*** Add the given interceptor at the specified index of this chain.* @since 5.2*/public void addInterceptor(int index, HandlerInterceptor interceptor) {this.interceptorList.add(index, interceptor);}/*** Add the given interceptors to the end of this chain.*/public void addInterceptors(HandlerInterceptor... interceptors) {CollectionUtils.mergeArrayIntoCollection(interceptors, this.interceptorList);}/*** Return the array of interceptors to apply (in the given order).* @return the array of HandlerInterceptors instances (may be {@code null})*/@Nullablepublic HandlerInterceptor[] getInterceptors() {return (!this.interceptorList.isEmpty() ? this.interceptorList.toArray(new HandlerInterceptor[0]) : null);}/*** Return the list of interceptors to apply (in the given order).* @return the list of HandlerInterceptors instances (potentially empty)* @since 5.3*/public List<HandlerInterceptor> getInterceptorList() {return (!this.interceptorList.isEmpty() ? Collections.unmodifiableList(this.interceptorList) :Collections.emptyList());}/*** 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);if (!interceptor.preHandle(request, response, this.handler)) {triggerAfterCompletion(request, response, null);return false;}this.interceptorIndex = i;}return true;}/*** Apply postHandle methods of registered interceptors.*/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);}}/*** Trigger afterCompletion callbacks on the mapped HandlerInterceptors.* Will just invoke afterCompletion for all interceptors whose preHandle invocation* has successfully completed and returned true.*/void triggerAfterCompletion(HttpServletRequest request, HttpServletResponse response, @Nullable Exception ex) {for (int i = this.interceptorIndex; i >= 0; i--) {HandlerInterceptor interceptor = this.interceptorList.get(i);try {interceptor.afterCompletion(request, response, this.handler, ex);}catch (Throwable ex2) {logger.error("HandlerInterceptor.afterCompletion threw exception", ex2);}}}/*** Apply afterConcurrentHandlerStarted callback on mapped AsyncHandlerInterceptors.*/void applyAfterConcurrentHandlingStarted(HttpServletRequest request, HttpServletResponse response) {for (int i = this.interceptorList.size() - 1; i >= 0; i--) {HandlerInterceptor interceptor = this.interceptorList.get(i);if (interceptor instanceof AsyncHandlerInterceptor) {try {AsyncHandlerInterceptor asyncInterceptor = (AsyncHandlerInterceptor) interceptor;asyncInterceptor.afterConcurrentHandlingStarted(request, response, this.handler);}catch (Throwable ex) {if (logger.isErrorEnabled()) {logger.error("Interceptor [" + interceptor + "] failed in afterConcurrentHandlingStarted", ex);}}}}}/*** Delegates to the handler's {@code toString()} implementation.*/@Overridepublic String toString() {return "HandlerExecutionChain with [" + getHandler() + "] and " + this.interceptorList.size() + " interceptors";}}