zoukankan      html  css  js  c++  java
  • java filter过滤链组装和调用过程解析

    Servlet API中提供了一个Filter接口,开发web应用时,如果编写的Java类实现了这个接口,则把这个java类称之为过滤器Filter

    通过Filter技术,可以实现在访问某个目标资源之前,对访问的请求和响应进行拦截。简单说,就是可以实现web容器对某资源的访问前截获进行相关的处理,还可以在某资源向web容器返回响应前进行截获进行处理。

    分析源码主要是主要目的是什么?了解FilterChain的创建、在代码里具体是怎么调用Servlet、filter是如何通过doFilter()这个方法实现递归的调用

    springboot的相关依赖:

    <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-web</artifactId>
     </dependency>

    在解析之前先看两张图,了解filter的执行时机

    添加filter之前:

     添加filter之后:

    要说明一下的是过滤链不是责任链的设计模式,因为一个request可以被链条上的多个filter处理。但是,对于责任链来说,一个request只能被链条中的一个handler处理。

    1、过滤链的创建

     主要看这个方法org.apache.catalina.core.ApplicationFilterFactory#createFilterChain

    return null;
            } else {
                ApplicationFilterChain filterChain = null;
                if (request instanceof Request) {
                    Request req = (Request)request;
                    if (Globals.IS_SECURITY_ENABLED) {
                        filterChain = new ApplicationFilterChain();
                    } else {
                        filterChain = (ApplicationFilterChain)req.getFilterChain();
                        if (filterChain == null) {
                            filterChain = new ApplicationFilterChain();
                            req.setFilterChain(filterChain);
                        }
                    }
                } else {
                    filterChain = new ApplicationFilterChain();
                }
    //上面执行的语句都是为创建一个空的FilterChain对象
    
                filterChain.setServlet(servlet);//将被调用的servlet设置进去
                filterChain.setServletSupportsAsync(wrapper.isAsyncSupported());
                StandardContext context = (StandardContext)wrapper.getParent();
                FilterMap[] filterMaps = context.findFilterMaps();//获取所有的filter的映射对象,保存的是各个filter的元数据信息
                if (filterMaps != null && filterMaps.length != 0) {
                  ......
                    FilterMap filterMap;
                    ApplicationFilterConfig filterConfig;//这个对象中保存者filter的bean引用,是要被filterChain所引用
                    for(var12 = 0; var12 < var11; ++var12) {
                        filterMap = var10[var12];
                        if (matchDispatcher(filterMap, dispatcher) && matchFiltersURL(filterMap, requestPath)) {//请求路径和filter设置的路径匹配模式进行匹配
                            filterConfig = (ApplicationFilterConfig)context.findFilterConfig(filterMap.getFilterName());
                            if (filterConfig != null) {
                                filterChain.addFilter(filterConfig);
                            }
                        }
                    }
              ......
    for(var12 = 0; var12 < var11; ++var12) {
                        filterMap = var10[var12];
                        if (matchDispatcher(filterMap, dispatcher) && matchFiltersServlet(filterMap, servletName)) {//执行的servlet和filter的selvetNames进行匹配
                            filterConfig = (ApplicationFilterConfig)context.findFilterConfig(filterMap.getFilterName());
                            if (filterConfig != null) {
                                filterChain.addFilter(filterConfig);
                            }
                        }
                    }
    
                    return filterChain;//filterChain创建完成返回
                } else {
                    return filterChain;
                }
            }
        }

    2、接下来看如何去调用执行的servlet和形成多个filter形成套娃式的递归调用的

    org/apache/catalina/core/StandardWrapperValve.class:153  这个是filterChain的调用入口了

     其主要执行的话是下面这个方法

     private void internalDoFilter(ServletRequest request, ServletResponse response) throws IOException, ServletException {
            if (this.pos < this.n) {//filtechain就保存filterConfig的长度-》n和当前执行到的filter下标-》post
                ApplicationFilterConfig filterConfig = this.filters[this.pos++];
    
                try {
                    Filter filter = filterConfig.getFilter();
                    if (request.isAsyncSupported() && "false".equalsIgnoreCase(filterConfig.getFilterDef().getAsyncSupported())) {
                        request.setAttribute("org.apache.catalina.ASYNC_SUPPORTED", Boolean.FALSE);
                    }
              //下面两种方式来调用filter重写的filter方法,参数就是filterChian、request和response,然后在filter我们又调用了filterChain的dofilter(),所以就形成了这么个套娃式的递归调用
                    if (Globals.IS_SECURITY_ENABLED) {
                        Principal principal = ((HttpServletRequest)request).getUserPrincipal();
                        Object[] args = new Object[]{request, response, this};
                        SecurityUtil.doAsPrivilege("doFilter", filter, classType, args, principal);
                    } else {
                        filter.doFilter(request, response, this);
                    }
    
                } catch (ServletException | RuntimeException | IOException var15) {
                    throw var15;
                } catch (Throwable var16) {
                    Throwable e = ExceptionUtils.unwrapInvocationTargetException(var16);
                    ExceptionUtils.handleThrowable(e);
                    throw new ServletException(sm.getString("filterChain.filter"), e);
                }
            } else {
                try {
                    if (ApplicationDispatcher.WRAP_SAME_OBJECT) {
                        lastServicedRequest.set(request);
                        lastServicedResponse.set(response);
                    }
    
                    if (request.isAsyncSupported() && !this.servletSupportsAsync) {
                        request.setAttribute("org.apache.catalina.ASYNC_SUPPORTED", Boolean.FALSE);
                    }
    
                    if (request instanceof HttpServletRequest && response instanceof HttpServletResponse && Globals.IS_SECURITY_ENABLED) {
                        Principal principal = ((HttpServletRequest)request).getUserPrincipal();
                        Object[] args = new Object[]{request, response};
                        SecurityUtil.doAsPrivilege("service", this.servlet, classTypeUsedInService, args, principal);
                    } else {
                        this.servlet.service(request, response);//当所有filter都被调用之后就执行了servlet,servlet之后就逐渐的递归返回了
                    }
                } catch (ServletException | RuntimeException | IOException var17) {
                    throw var17;
                } catch (Throwable var18) {
                    Throwable e = ExceptionUtils.unwrapInvocationTargetException(var18);
                    ExceptionUtils.handleThrowable(e);
                    throw new ServletException(sm.getString("filterChain.servlet"), e);
                } finally {
                    if (ApplicationDispatcher.WRAP_SAME_OBJECT) {
                        lastServicedRequest.set((Object)null);
                        lastServicedResponse.set((Object)null);
                    }
    
                }
    
            }
        }
    

    整个请求中过滤链的组装和调用过程就是这样了。

    进行springboot整合过滤器的过程中可能会遇到过滤器执行两次的问题,针对这个问题可能出现的一种原因就是因为在请求执行完之后浏览器会再发一次请求.ico的请求,具体内容可以参考:

    参考地址:https://blog.csdn.net/qq_39210972/article/details/103377201

  • 相关阅读:
    SQL——索引
    const 与 readonly知多少
    ASP.NET MVC 4 RC的JS/CSS打包压缩功能
    学习IIS & MVC的运行原理
    IIS中使用ASP.NET MVC的经验总结
    cookie 和session 的区别详解
    SQL之经典语句
    SQL存储过程,使用事务(try catch),游标
    深入理解SQL的四种连接-左外连接、右外连接、内连接、全连接
    Sql效能优化总结(续)- sql语句优化篇
  • 原文地址:https://www.cnblogs.com/qizhufeitian/p/13934776.html
Copyright © 2011-2022 走看看