相关阅读
【小家Java】Servlet规范之—请求(request):Servlet中如何获取POST的请求参数?(使用getParameter())
前言
首先我附上一个截图:
从截图上我们可以看到我标红的我们比较熟悉,配置过的一些Filter,他们都继承自OncePerRequestFilter。
该Filter从字面上理解:只执行一次的Filter。可能有人会问了,我们自己写的Filter不都只执行一次吗?为何Spring还要专门提供这么一个类来处理呢?
这就是本文关心的内容,就Spring内置的这些Filter,我们去理解下Spring的用意何在~
在Spring中,Filter默认继承OncePerRequestFilter
关于OncePerRequestFilter
OncePerRequestFilter:顾名思义,它能够确保在一次请求中只通过一次filter,而需要重复的执行。大家常识上都认为,一次请求本来就只filter一次,为什么还要由此特别限定呢。
往往我们的常识和实际的实现并不真的一样,经过一番资料的查阅,此方法是为了兼容不同的web container,也就是说并不是所有的container都入我们期望的只过滤一次,servlet版本不同,执行过程也不同,我们可以看看Spring的javadoc怎么说:
* * <p>As of Servlet 3.0, a filter may be invoked as part of a * {@link javax.servlet.DispatcherType#REQUEST REQUEST} or * {@link javax.servlet.DispatcherType#ASYNC ASYNC} dispatches that occur in * separate threads. A filter can be configured in {@code web.xml} whether it * should be involved in async dispatches. However, in some cases servlet * containers assume different default configuration.
简单的说就是去适配了不同的web容器,以及对异步请求,也只过滤一次的需求。另外打个比方:如:servlet2.3与servlet2.4也有一定差异:
在servlet2.3中,Filter会经过一切请求,包括服务器内部使用的forward转发请求和<%@ include file=”/login.jsp”%>的情况
servlet2.4中的Filter默认情况下只过滤外部提交的请求,forward和include这些内部转发都不会被过滤,
因此此处我有个建议:我们若是在Spring环境下使用Filter的话,个人建议继承OncePerRequestFilter吧,而不是直接实现Filter接口。这是一个比较稳妥的选择
需要注意的是:
@Override public final void init(FilterConfig filterConfig) throws ServletException {}
它Final掉了init方法,因此若我们继承它,无法使用init方法了。但我们可以复写initFilterBean
这个方法,实现我们比init方法更强大的一些逻辑,可以直接使用容器对象了,如下:~
@Component("helloFilter") public class HelloFilter extends OncePerRequestFilter { @Override protected void initFilterBean() throws ServletException { System.out.println("Filter初始化..."); } @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { FilterConfig filterConfig = super.getFilterConfig(); ServletContext servletContext = super.getServletContext(); Environment environment = super.getEnvironment(); filterChain.doFilter(request, response); } }
需要注意的是,这个方法在初始化的时候,会被执行两次。虽然没什么影响,但个人认为这是Spring的Bug吧,执行两次的地方在GenericFilterBean这个类:
@Override public void afterPropertiesSet() throws ServletException { initFilterBean(); }
以及它的init方法:
@Override public final void init(FilterConfig filterConfig) throws ServletException { ... // Let subclasses do whatever initialization they like. initFilterBean(); ... }
但是,继承自OncePerRequestFilter的Filter采用@WebFilter以及Spring Bean的方式都是ok好使的。若采用@WebFilterinitFilterBean方法就只会被执行一次,但是,但是,但是此时@Autowaire自动注入就不好使了,需要自己去容器里拿:
super.getServletContext();
所以看自己需求,哪个方便选哪个。(比如我的Filter只需要记录一下请求日志,没必要注入Spring的Bean,那么我觉得知己@WebFilter
就更方便了)
OncePerRequestFilter源码解读
为了方便小伙伴阅读源码,此处我直接吧我自己的理解写在代码上:
/** * 过滤器基类,旨在确保每个请求调度在任何servlet容器上执行一次执行。 * 它提供了一个带有HttpServletRequest和HttpServletResponse参数的{@link #doFilterInternal}方法。 */ public abstract class OncePerRequestFilter extends GenericFilterBean { ... }
我们知道filter最主要的是doFilter方法:
// 已过滤过的过滤器的固定后缀名 public static final String ALREADY_FILTERED_SUFFIX = ".FILTERED"; // 相当于生成已过滤的过滤器的名字(全局唯一的) protected String getAlreadyFilteredAttributeName() { String name = getFilterName(); if (name == null) { name = getClass().getName(); } return name + ALREADY_FILTERED_SUFFIX; } //判断该请求是否是异步请求(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; } //原理基本同上 protected boolean shouldNotFilterErrorDispatch() { return true; } //可以人工直接返回true 那这个请求就肯定不会被过滤了~~~~ protected boolean shouldNotFilter(HttpServletRequest request) throws ServletException { return false; } //这里很清楚的记录着 需要被跳过的请求们,这种请求直接就放行了 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; }
有了介绍上面这些基础方法,那么问题接下来我们就可以看doFilter方法了:
@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); } } }
最后有必要父类中提供的一个方法,获取到该Filter的名字:
// 如果在Spring应用程序上下文中初始化为bean,那么它将返回到bean工厂中定义的bean名称。 // 需要注意的是,如果是以bean的形式加入了。(比如Boot环境下),此时FilterConfig还未null的,所以有这个判断 @Nullable protected String getFilterName() { return (this.filterConfig != null ? this.filterConfig.getFilterName() : this.beanName); }