而Spring的OncePerRequestFilter类实际上是一个实现了Filter接口的抽象类。spring对Filter进行了一些封装处理
OncePerRequestFilter,顾名思义,它能够确保在一次请求中只通过一次filter,而需要重复的执行。大家常识上都认为,一次请求本来就只filter一次,为什么还要由此特别限定呢,往往我们的常识和实际的实现并不真的一样,经过一番资料的查阅,此方法是为了兼容不同的web container,也就是说并不是所有的container都入我们期望的只过滤一次,servlet版本不同,执行过程也不同,因此,为了兼容各种不同运行环境和版本,默认filter继承OncePerRequestFilter是一个比较稳妥的选择
源码说明:
- public abstract class OncePerRequestFilter extends GenericFilterBean {
- //一个标记,后面会用到
- public static final String ALREADY_FILTERED_SUFFIX = ".FILTERED";
-
- @Override
- 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;
- //这里获取一个名称,该名称后面会被用于放到request当作key
- String alreadyFilteredAttributeName = getAlreadyFilteredAttributeName();
- //检测当前请求是否已经拥有了该标记,如果拥有该标记则代表该过滤器执行过了(后面注释有说明)
- boolean hasAlreadyFilteredAttribute = request.getAttribute(alreadyFilteredAttributeName) != null;
-
- if (skipDispatch(httpRequest) || shouldNotFilter(httpRequest)) {
-
- // Proceed without invoking this filter...
- filterChain.doFilter(request, response);
- }
- //如果此过滤器已经被执行过则执行如下的逻辑
- else if (hasAlreadyFilteredAttribute) {
-
- if (DispatcherType.ERROR.equals(request.getDispatcherType())) {
- doFilterNestedErrorDispatch(httpRequest, httpResponse, filterChain);
- return;
- }
-
- // Proceed without invoking this filter...
- filterChain.doFilter(request, response);
- }
- //走到这里说明该过滤器没有被执行过
- else {
- // Do invoke this filter...
- // 在当前请求里面设置一个标记,key就是前面拼接的那个变量,value是true,这个标记如果在request存在则在前面会被检测到并改变hasAlreadyFilteredAttribute的值
- request.setAttribute(alreadyFilteredAttributeName, Boolean.TRUE);
- try {
- // 这个方法是一个抽象方法需要子类去实现具体的过滤逻辑
- doFilterInternal(httpRequest, httpResponse, filterChain);
- }
- finally {
- // Remove the "already filtered" request attribute for this request.
- // 执行完毕之后移除该标记
- request.removeAttribute(alreadyFilteredAttributeName);
- }
- }
- }
- //其余代码略
- }
自定义代码案例:
- @Component
- @Order(-1)
- @Slf4j
- public class SessionContextFilter extends OncePerRequestFilter implements Ordered {
-
- /**
- * 请求拦截器
- */
- @Override
- protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
-
- Thread thread = Thread.currentThread();
- Long userId = request.getHeader("userId") == null ? 0L : Long.valueOf(request.getHeader("userId"));
- String userName = request.getHeader("userName") == null ? "admin" : request.getHeader("userName");
- ContextHolder.setContext(new OperationVO(userId,userName));
- try {
- OperationVO operationVO = ContextHolder.getContext();
- // 除这行代码外,其他均为自定义业务代码
- filterChain.doFilter(request,response);
- } finally {
- ContextHolder.clearContext();
- OperationVO operationVO = ContextHolder.getContext();
- }
- }
-
- @Override
- public int getOrder() {
- return 0;
- }
- }
实现filterchain的dofilter方法采用 责任链设计模式,把自身接收到的请求request对象和response对象和自身对象即filterchain
作为下一个过滤器的dofilter的三个形参传递过去,这样才能使得过滤器传递下去,当然这个方法中还存在一些判断if等机制
用来判断现在的这个过滤器是不是最后一个,是的话就可以把请求和响应对象传递给浏览器请求的页面
第一个疑问是该过滤器链里面的过滤器源于哪里?
答案是该类里面包含了一个ApplicationFilterConfig对象,而该对象则是个filter容器
当web容器启动是ApplicationFilterConfig自动实例化,它会从该web工程的web.xml文件中读取配置的filter信息,然后装进该容器
下个疑问是它如何执行该过滤器容器里面的filter呢?
答案是通过pos它来标识当前ApplicationFilterChain(当前过滤器链)执行到哪个过滤器
ApplicationFilterChain采用责任链设计模式达到对不同过滤器的执行
首先ApplicationFilterChain 会调用它重写FilterChain的doFilter方法,然后doFilter里面会调用
internalDoFilter(request,response)方法;该方法使过滤器容器拿到每个过滤器,然后调用它们重写Filter接口里面的dofilter方法
以下是ApplicationFilterChain 里面重写FilterChain里面的doFilter方法的描述
- /**
- * Invoke the next filter in this chain, passing the specified request
- * and response. If there are no more filters in this chain, invoke
- * the
service()
method of the servlet itself. - *
- * @param request The servlet request we are processing
- * @param response The servlet response we are creating
- *
- * @exception IOException if an input/output error occurs
- * @exception ServletException if a servlet exception occurs
- */
- @Override
- public void doFilter(ServletRequest request, ServletResponse response)
以下是internalDoFilter的部分代码
- // Call the next filter if there is one
- if (pos < n) {
- //先拿到下个过滤器,将指针向下移动一位
- ApplicationFilterConfig filterConfig = filters[pos++];
- Filter filter = null;
- try {
- //获取当前指向的filter实例
- filter = filterConfig.getFilter();
- support.fireInstanceEvent(InstanceEvent.BEFORE_FILTER_EVENT,
- filter, request, response);
-
- if (request.isAsyncSupported() && "false".equalsIgnoreCase(
- filterConfig.getFilterDef().getAsyncSupported())) {
- request.setAttribute(Globals.ASYNC_SUPPORTED_ATTR,
- Boolean.FALSE);
- }
- if( Globals.IS_SECURITY_ENABLED ) {
- final ServletRequest req = request;
- final ServletResponse res = response;
- Principal principal =
- ((HttpServletRequest) req).getUserPrincipal();
-
- Object[] args = new Object[]{req, res, this};
- SecurityUtil.doAsPrivilege
- ("doFilter", filter, classType, args, principal);
-
- } else {
- //filter调用doFilter(request, response, this)方法
- //ApplicationFilterChain里面的filter都实现了filter
- filter.doFilter(request, response, this);
- }
- }
- }
以下是Filter接口doFilter定义如下
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
过滤器链里面的filter在调用dofilter完成后,会继续调用chain.doFilter(request,response)方法,而这个chain其实就是applicationfilterchain,所以调用过程又回到了上面调用dofilter和调用internalDoFilter方法,这样执行直到里面的过滤器全部执行
当filte都调用完成后,它就会初始化相应的servlet,(例如jsp资源,默认它会开启一个 JspServlet对象)
- // We fell off the end of the chain -- call the servlet instance
- try {
- if (ApplicationDispatcher.WRAP_SAME_OBJECT) {
- lastServicedRequest.set(request);
- lastServicedResponse.set(response);
- }
-
- support.fireInstanceEvent(InstanceEvent.BEFORE_SERVICE_EVENT,
- servlet, request, response);