一个Filter
在对一次客户端请求request
的处理中,常识上的理解,应该只被应用一次。这种理解很直观,也很合理,然而实际情况并非如此。
举例来讲,使用tomcat
和Spring MVC + JSP
开发web
时应用,客户端请求某个JSP
页面的URL
时,你就会发现你的一个过滤器会被应用多次,也就是它的 doFilter()
方法会被调用多次。其他的情况,比如某个JSP
页面中使用 include
包含了另外一个JSP
片段(比如页面统一的头部或者底部区域),在这样的JSP
页面被请求时,因为这些include
指令,该页面请求的的处理中,同一个Filter
也会被应用多次。
上面举的例子并不是一个请求中过滤器被多次应用的所有场景,还存在一些其他的场景,比如Servlet 3.0
中的会发生在不同线程中的ASYNC dispatch
。
产生以上问题的原因不是这篇文章关心的重点。这里关心的重点时:对于这种问题,应该怎么解决呢?Spring
给出的解决方案是OncePerRequestFilter
。如果想让一个Filter
在一次请求处理的过程中只被应用一次,就让这个Filter
继承自OncePerRequestFilter
。Spring
自身提供的这类的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
即可。