Spring Web 4 源码阅读笔记 : 过滤器 OncePerRequestFilter

2,331 阅读5分钟
原文链接: blog.csdn.net

一个Filter在对一次客户端请求request的处理中,常识上的理解,应该只被应用一次。这种理解很直观,也很合理,然而实际情况并非如此。

举例来讲,使用tomcatSpring MVC + JSP开发web时应用,客户端请求某个JSP页面的URL时,你就会发现你的一个过滤器会被应用多次,也就是它的 doFilter()方法会被调用多次。其他的情况,比如某个JSP页面中使用 include包含了另外一个JSP片段(比如页面统一的头部或者底部区域),在这样的JSP页面被请求时,因为这些include指令,该页面请求的的处理中,同一个Filter也会被应用多次。

上面举的例子并不是一个请求中过滤器被多次应用的所有场景,还存在一些其他的场景,比如Servlet 3.0 中的会发生在不同线程中的ASYNC dispatch

产生以上问题的原因不是这篇文章关心的重点。这里关心的重点时:对于这种问题,应该怎么解决呢?Spring给出的解决方案是OncePerRequestFilter。如果想让一个Filter在一次请求处理的过程中只被应用一次,就让这个Filter继承自OncePerRequestFilterSpring自身提供的这类的Filter,都继承自该过滤器基类。

下面我们看看OncePerRequestFilter的源代码,分析一下OncePerRequestFilter是如何保证一个Filter在一个请求的处理过程中只被应用一次的。

package org.springframework.web.filter;

import java.io.IOException;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.springframework.web.context.request.async.WebAsyncManager;
import org.springframework.web.context.request.async.WebAsyncUtils;
import org.springframework.web.util.WebUtils;

public abstract class OncePerRequestFilter extends GenericFilterBean {

	/**
	 * Suffix that gets appended to the filter name for the
	 * "already filtered" request attribute.
	 * @see #getAlreadyFilteredAttributeName
	 */
	public static final String ALREADY_FILTERED_SUFFIX = ".FILTERED";


	/**
	 * 标准的过滤器方法doFilter实现。该方法在一个请求的处理过程中有可能会被调用多次,但首次调用时该方法
	 * 会对请求添加一个属性标记该过滤器已经针对该请求应用过。然后,在同一请求处理过程中如果此过滤器
	 * 再次被应用时,也就是该方法再次被调用时,他会检查请求是否包含该属性,如果包含该属性,说明已经
	 * 应用过,则不再次调用当前过滤器真真要应用的过滤器逻辑。
	 */
	@Override
	public final void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain)
			throws ServletException, IOException {

		// 该过滤器仅针对 HttpServletRequest/HttpServletResponse,如果是其他 Request,Response不支持
		if (!(request instanceof HttpServletRequest) || !(response instanceof HttpServletResponse)) {
			throw new ServletException("OncePerRequestFilter just supports HTTP requests");
		}
		HttpServletRequest httpRequest = (HttpServletRequest) request;
		HttpServletResponse httpResponse = (HttpServletResponse) response;

		// 检查当前过滤器针对当前请求是否已经应用,已经应用的标志 :
		// 当前请求存在名称为  [Filter Name] .FILTERED 值不为空(实际上值为 true)的属性
		String alreadyFilteredAttributeName = getAlreadyFilteredAttributeName();
		boolean hasAlreadyFilteredAttribute = request.getAttribute(alreadyFilteredAttributeName) != null;

		if (hasAlreadyFilteredAttribute || skipDispatch(httpRequest) || shouldNotFilter(httpRequest)) {

			// 当前过滤器针对该请求已经应用过,或者有其它一些不需要应用的理由,则不再调用
			// 当前过滤器真正的过滤器逻辑,直接跳到下一步
			filterChain.doFilter(request, response);
		}
		else {
			// 该过滤器在当前请求处理中首次被调用到的情况,调用该过滤器真正的过滤逻辑,并且
			// 标记该过滤器针对当前请求已经处理过
			request.setAttribute(alreadyFilteredAttributeName, Boolean.TRUE);
			try {
				// 调用该过滤器真正的过滤逻辑,并在里面进一步调用过滤器链的其它部分
				doFilterInternal(httpRequest, httpResponse, filterChain);
			}
			finally {
				// 该过滤器真正的过滤逻辑执行完成,对过滤器链的其他部分调用的执行也已经完成,
				// 现在处于输出请求响应阶段,从请求属性中删除该过滤器是否已经应用的标记,
				// 该标记此时已经没用了。
				request.removeAttribute(alreadyFilteredAttributeName);
			}
		}
	}


	private boolean skipDispatch(HttpServletRequest request) {
		if (isAsyncDispatch(request) && shouldNotFilterAsyncDispatch()) {
			return true;
		}
		if (request.getAttribute(WebUtils.ERROR_REQUEST_URI_ATTRIBUTE) != null && shouldNotFilterErrorDispatch()) {
			return true;
		}
		return false;
	}

	/**
	 * The dispatcher type javax.servlet.DispatcherType.ASYNC introduced
	 * in Servlet 3.0 means a filter can be invoked in more than one thread over
	 * the course of a single request. This method returns true if the
	 * filter is currently executing within an asynchronous dispatch.
	 * @param request the current request
	 * @since 3.2
	 * @see WebAsyncManager#hasConcurrentResult()
	 */
	protected boolean isAsyncDispatch(HttpServletRequest request) {
		return WebAsyncUtils.getAsyncManager(request).hasConcurrentResult();
	}

	/**
	 * Whether request processing is in asynchronous mode meaning that the
	 * response will not be committed after the current thread is exited.
	 * @param request the current request
	 * @since 3.2
	 * @see WebAsyncManager#isConcurrentHandlingStarted()
	 */
	protected boolean isAsyncStarted(HttpServletRequest request) {
		return WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted();
	}

	/**
	* 根据当前过滤器的名字构造一个属性名称,格式为 : [Filter Name] .FILTERED
	* 比如当前过滤器为 charactorEncodingFilter , 则改属性名称为 :  charactorEncodingFilter.FILTERED
	**/
	protected String getAlreadyFilteredAttributeName() {
		String name = getFilterName();
		if (name == null) {
			name = getClass().getName();
		}
		return name + ALREADY_FILTERED_SUFFIX;
	}

	/**
	* 留给实现子类用于定制当前过滤器是否不要被应用的扩展方法。
	* 这里缺省返回 false,表示需要应用当前 Filter 的过滤逻辑。
	**/
	protected boolean shouldNotFilter(HttpServletRequest request) throws ServletException {
		return false;
	}

	/**
	 * The dispatcher type javax.servlet.DispatcherType.ASYNC introduced
	 * in Servlet 3.0 means a filter can be invoked in more than one thread
	 * over the course of a single request. Some filters only need to filter
	 * the initial thread (e.g. request wrapping) while others may need
	 * to be invoked at least once in each additional thread for example for
	 * setting up thread locals or to perform final processing at the very end.
	 * Note that although a filter can be mapped to handle specific dispatcher
	 * types via web.xml or in Java through the ServletContext,
	 * servlet containers may enforce different defaults with regards to
	 * dispatcher types. This flag enforces the design intent of the filter.
	 * The default return value is "true", which means the filter will not be
	 * invoked during subsequent async dispatches. If "false", the filter will
	 * be invoked during async dispatches with the same guarantees of being
	 * invoked only once during a request within a single thread.
	 * @since 3.2
	 */
	protected boolean shouldNotFilterAsyncDispatch() {
		return true;
	}

	/**
	 * Whether to filter error dispatches such as when the servlet container
	 * processes and error mapped in web.xml. The default return value
	 * is "true", which means the filter will not be invoked in case of an error
	 * dispatch.
	 * @since 3.2
	 */
	protected boolean shouldNotFilterErrorDispatch() {
		return true;
	}


	/**
	 * Same contract as for doFilter, but guaranteed to be
	 * just invoked once per request within a single request thread.
	 * See  #shouldNotFilterAsyncDispatch() for details.
	 * Provides HttpServletRequest and HttpServletResponse arguments instead of the
	 * default ServletRequest and ServletResponse ones.
	 *  过滤器真正需要被应用的过滤逻辑,OncePerRequestFilter 会保证它在一次请求处理中只被执行最多一次
	 * 
	 *  本来 doFilter 用来实现一个过滤器的过滤逻辑,但是基类OncePerRequestFilter实现了doFilter
	 *  用于上面所提到的设计目的,所以实现类需要实现此方法doFilterInternal用于实现真正的过滤逻辑。
	 *  
	 *  一个实现类一旦继承了OncePerRequestFilter,针对同一请求处理,其doFilter可能被多次调用,
	 * 但该实现类的 doFilterInternal 可以确保只被应用一次。
	 */
	protected abstract void doFilterInternal(
			HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
			throws ServletException, IOException;

}

从上面的分析可以看出,如果想确保一段过滤器逻辑针对一次请求处理只被应用一次,可以实现一个过滤器继承自OncePerRequestFilter,相应的过滤器逻辑实现在方法doFilterInternal即可。