Nemo

关注TA

路漫漫其修远兮,吾将上下而求索。

  • 深圳市
  • 菜鸟工程师

最近留言

Spring中的OncePerRequestFilter

2019年06月19 12:14 200 0 复制链接

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;

一个请求如果已经做过请求,则在给这个请求设定标记。如果下续这个请求还需要经过过滤器,则优先判定这个标记是否存在,如果存在则不再执行下续过滤操作。

一个简单的实现就是这样子了。





点赞(0)
点了个评