作用:
OncePerRequestFilter抽象类最主要的作用就是保证一次请求只通过一次filter,而不需要重复执行。从下图也可以看到在spring的filter中都继承了OncePerRequestFilter,为什么要这么做呢?
场景:
通过官方英文注释可以找到一些解释,这个抽象类的出现是为了兼容不同的web容器。因为在不同的servlet版本中,执行过程也不同。例如:
在Servlet3.0中,如果一个请求是DispatcherType.ASYNC类型的,那么在一个单一请求的过程中,filter能够被多个线程调用,
也就是意味着一个filter可能在一次请求中被多次执行,这显然是会有问题的,这个时候OncePerRequestFilter起到了关键的作用
尤其是我们在使用spring-security时往往需要用到OncePerRequestFilter来避免多次过滤,而不是直接Filter接口。
注意:
在其父类GenericFilterBean中:
@Override
public final void init(FilterConfig filterConfig) throws ServletException {
Assert.notNull(filterConfig, "FilterConfig must not be null");
this.filterConfig = filterConfig;
// Set bean properties from init parameters.
PropertyValues pvs = new FilterConfigPropertyValues(filterConfig, this.requiredProperties);
if (!pvs.isEmpty()) {
try {
BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);
ResourceLoader resourceLoader = new ServletContextResourceLoader(filterConfig.getServletContext());
Environment env = this.environment;
if (env == null) {
env = new StandardServletEnvironment();
}
bw.registerCustomEditor(Resource.class, new ResourceEditor(resourceLoader, env));
initBeanWrapper(bw);
bw.setPropertyValues(pvs, true);
}
catch (BeansException ex) {
String msg = "Failed to set bean properties on filter '" +
filterConfig.getFilterName() + "': " + ex.getMessage();
logger.error(msg, ex);
throw new NestedServletException(msg, ex);
}
}
// Let subclasses do whatever initialization they like.
initFilterBean();
if (logger.isDebugEnabled()) {
logger.debug("Filter '" + filterConfig.getFilterName() + "' configured for use");
}
}
它Final掉了init方法,因此若我们继承它,无法使用init方法了。但我们可以复写initFilterBean这个方法,实现我们比init方法更强大的一些逻辑。
子类可以重写此项以执行自定义初始化。
此筛选器的所有bean属性都将在此之前设置
方法被调用。
<p>注意:此方法将从标准筛选器初始化中调用
以及Spring应用程序上下文中的filter bean初始化。
过滤器名称和ServletContext在这两种情况下都可用。
<p>此默认实现为空。
protected void initFilterBean() throws ServletException {
}
简单源码分析:
下面看看源码中具体时怎么做的,英文注解已经介绍的比较清楚我们只需要对照英文加以理解
// 附加到筛选器名称的后缀,用于标识。默认为filter的名字加后缀,如果filter没有完全初始化,则改为类名加后缀,后缀为“.FILTERED
public static final String ALREADY_FILTERED_SUFFIX = ".FILTERED";
// spring会给已经过滤过的request设置一个attribute,在filter链和目标方法执行完毕之后才会释放这个attribute,attribute的名字是从
// getAlreadyFilteredAttributeName() 方法得来,默认为filter的名字加后缀,如果filter没有完全初始化,则改为类名加后缀,后缀为 “.FILTERED”
// 生成已过滤的过滤器的名字(全局唯一的),获取完名字之后,需要进行判断是否已经执行过这个filter了
protected String getAlreadyFilteredAttributeName() {
String name = getFilterName();
if (name == null) {
name = getClass().getName();
}
return name + ALREADY_FILTERED_SUFFIX;
}
// 获取完名字之后,需要进行判断是否已经执行过这个filter了,判断条件有3个,当前方法就时其中之一
// 请求处理是否处于异步模式意味着,当前线程退出后将不会提交响应。
// 1 是异步并且不应该过滤异步,则skipDispatch为true,即不进行过滤 ,2 是ERROR请求并且不应该过滤ERROR,同样返回true
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;
}
//判断该请求是否是异步请求(Servlet 3.0后有异步请求,Spring MVC3.2开始)
protected boolean isAsyncDispatch(HttpServletRequest request) {
return WebAsyncUtils.getAsyncManager(request).hasConcurrentResult();
}
//是否需要不过滤异步的请求(默认是不多次过滤异步请求的)
//javadoc:javax.servlet.DispatcherType.ASYNC的请求方式意味着可能在一个请求里这个过滤器会被多个不同线程调用多次,而这里返回true,就能保证只会被调用一次
protected boolean shouldNotFilterAsyncDispatch() {
return true;
}
/**
*是否过滤错误调度,例如当servlet容器在{@code web.xml}中映射的进程和错误。默认返回值为“true”,
*这意味着在发生错误时不会调用筛选器派遣。
*/
//可以人工直接返回true 那这个请求就肯定不会被过滤了
protected boolean shouldNotFilterErrorDispatch() {
return true;
}
@Override
public final void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
//只处理http请求
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)) {
// 直接放行,不执行此过滤器的过滤操作
filterChain.doFilter(request, response);
} else {
// 执行过滤,并且向请求域设置一个值,key就是生成的全局唯一的·alreadyFilteredAttributeName·
request.setAttribute(alreadyFilteredAttributeName, Boolean.TRUE);
try {
//由子类自己去实现拦截的逻辑 注意 自己写时,filterChain.doFilter(request, response);这句代码不要忘了
doFilterInternal(httpRequest, httpResponse, filterChain);
}
finally {
// Remove the "already filtered" request attribute for this request.
request.removeAttribute(alreadyFilteredAttributeName);
}
}
}