java中的过滤器,我们一般认为在某次请求中,其中的doFilter方法只会执行一次。但是实际上并非如此。
查看Filter的源码,开发人员在doFilter方法上面写了如下备注:
/**
* Called by the web container to indicate to a filter that it is being
* placed into service. The servlet container calls the init method exactly
* once after instantiating the filter. The init method must complete
* successfully before the filter is asked to do any filtering work.
* <p>
大概可以理解为,这个过滤方法的执行次数跟所用的容器有关。
以servlet为例,servlet2.3与servlet2.4也有一定差异。
在servlet2.3中:
在servlet-2.3中,Filter会过滤一切请求,包括服务器内部使用forward转发请求和<%@ include file="/index.jsp"%>的情况。
而servlet2.4中:
到了servlet-2.4中Filter默认下只拦截外部提交的请求,forward和include这些内部转发都不会被过滤,但是有时候我们需要 forward的时候也用到Filter。
因此,为了兼容不同的servlet版本,Spring提供了OncePerRequestFilter这个过滤器。
顾名思义,这个类是用来确保一个请求只执行一次过滤器的。
从类定义看:
public abstract class OncePerRequestFilter extends GenericFilterBean {
他的父类GenericFilterBean的定义:
public abstract class GenericFilterBean implements Filter, BeanNameAware, EnvironmentAware,
EnvironmentCapable, ServletContextAware, InitializingBean, DisposableBean {
所以本质上这个类也只是传统过滤器的一个扩展。
看下GenericFilterBean的具体实现:
public final void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
if (!(request instanceof HttpServletRequest) || !(response instanceof HttpServletResponse)) {
throw new ServletException("OncePerRequestFilter just supports HTTP requests");
}
HttpServletRequest httpRequest = (HttpServletRequest) request;
HttpServletResponse httpResponse = (HttpServletResponse) response;
String alreadyFilteredAttributeName = getAlreadyFilteredAttributeName();
boolean hasAlreadyFilteredAttribute = request.getAttribute(alreadyFilteredAttributeName) != null;
if (hasAlreadyFilteredAttribute || skipDispatch(httpRequest) || shouldNotFilter(httpRequest)) {
// Proceed without invoking this filter...
filterChain.doFilter(request, response);
}
else {
// Do invoke this filter...
request.setAttribute(alreadyFilteredAttributeName, Boolean.TRUE);
try {
doFilterInternal(httpRequest, httpResponse, filterChain);
}
finally {
// Remove the "already filtered" request attribute for this request.
request.removeAttribute(alreadyFilteredAttributeName);
}
}
}
关键点在于这个判定:
boolean hasAlreadyFilteredAttribute = request.getAttribute(alreadyFilteredAttributeName) != null;
一个请求如果已经做过请求,则在给这个请求设定标记。如果下续这个请求还需要经过过滤器,则优先判定这个标记是否存在,如果存在则不再执行下续过滤操作。
一个简单的实现就是这样子了。