美文网首页TomcatJava学习笔记程序员
基于Tomcat的Servlet过滤器(1)实例及加载执行源码简

基于Tomcat的Servlet过滤器(1)实例及加载执行源码简

作者: 禾边的晓作坊 | 来源:发表于2018-03-10 15:46 被阅读34次

    实例

    实现一个简单的过滤器只需要两步
    1,实现Filter接口写一个过滤器实现类

    public class DemoFilter implements Filter {
    
        @Override
        public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
                throws IOException, ServletException {
            System.out.println("[DemoFilter-before]doFilter");
            chain.doFilter(request, response);
            System.out.println("[DemoFilter-after]doFilter");
        }
        
        @Override
        public void init(FilterConfig filterConfig) throws ServletException {
            System.out.println("[DemoFilter-before]init");
        }
    
        @Override
        public void destroy() {
            System.out.println("[DemoFilter-before]destroy");
        }
    
    }
    

    2,web.xml文件中新增相关filter配置

    <filter>  
            <filter-name>DemoFilter</filter-name>  
            <filter-class>com.ryan.springtest.filter.DemoFilter</filter-class>  
    </filter>
      
    <filter-mapping>  
            <filter-name>DemoFilter</filter-name>  
            <url-pattern>/*</url-pattern>  
    </filter-mapping>
    

    输出
    此时启动tomcat访问任一url,即可看到相应的输出信息

    [DemoFilter-before]doFilter
    [DemoFilter-after]doFilter
    

    注:filterChain为过滤器链,表示执行完这个过滤器之后接着执行下一个过滤器

    原理

    过滤器的具体实现依赖于容器,本文的源码分析是基于Tomcat的实现。
    要完成过滤器的实现,Tomcat首先需要加载我们定义的过滤器,接着针对每一次请求找到对应的过滤器,最后是执行过滤器中的doFilter,触发过滤器链的执行,下面将按照这个逻辑对源码进行简单的分析。

    过滤器加载

    过滤器的加载是在Tomcat启动的时候完成的,Tomcat启动的时候,会加载web.xml中的配置信息,filter的加载具体是在ContextConfig类的configureContext方法中,关键代码如下

    for (FilterDef filter : webxml.getFilters().values()) {
        if (filter.getAsyncSupported() == null) {
            filter.setAsyncSupported("false");
        }
        context.addFilterDef(filter);
    }
    for (FilterMap filterMap : webxml.getFilterMappings()) {
        context.addFilterMap(filterMap);
    }
    

    此时分别加载filter和filterMap相关信息,并保存在上下文环境中

    加载完相关配置信息后,还需对具体的filter进行初始化,这一步在StandardContext类的startInternal方法中完成,关键代码如下

    if (ok) {
        if (!filterStart()) {
            log.error(sm.getString("standardContext.filterFail"));
            ok = false;
        }
    }
    
    public boolean filterStart() {
    
            if (getLogger().isDebugEnabled()) {
                getLogger().debug("Starting filters");
            }
            // Instantiate and record a FilterConfig for each defined filter
            boolean ok = true;
            synchronized (filterConfigs) {
                filterConfigs.clear();
                for (Entry<String,FilterDef> entry : filterDefs.entrySet()) {
                    String name = entry.getKey();
                    if (getLogger().isDebugEnabled()) {
                        getLogger().debug(" Starting filter '" + name + "'");
                    }
                    try {
                        ApplicationFilterConfig filterConfig =
                                new ApplicationFilterConfig(this, entry.getValue());
                        filterConfigs.put(name, filterConfig);
                    } catch (Throwable t) {
                        t = ExceptionUtils.unwrapInvocationTargetException(t);
                        ExceptionUtils.handleThrowable(t);
                        getLogger().error(sm.getString(
                                "standardContext.filterStart", name), t);
                        ok = false;
                    }
                }
            }
    
            return ok;
        }
    

    遍历刚刚从web.xml解析出来的filter配置信息,并调用ApplicationFilterConfig构造方法进行初始化,保存在filterConfigs中并存到上下文环境中。

    过滤器链生成

    当请求进入tomcat的时候,会被匹配的过滤器过滤,多个匹配的过滤器组成一个过滤器链,并按照我们在web.xml中定义的filter-mapping的顺序执行。
    被tomcat处理的请求,最终会被StandardWrapperValve类的invoke方法处理,对应的过滤器链也是在此时生成的,关键代码如下

    ApplicationFilterChain filterChain = ApplicationFilterFactory.createFilterChain(request, wrapper, servlet);
    
    @SuppressWarnings("deprecation")
    public static ApplicationFilterChain createFilterChain (ServletRequest request, Wrapper wrapper, Servlet servlet) {
            ApplicationFilterChain filterChain = null;
            if (request instanceof Request) {
                //略
            } else {
                // Request dispatcher in use
                filterChain = new ApplicationFilterChain();
            }
    
            filterChain.setServlet(servlet);
            filterChain.setSupport(((StandardWrapper)wrapper).getInstanceSupport());
    
            // Acquire the filter mappings for this Context
            StandardContext context = (StandardContext) wrapper.getParent();
            FilterMap filterMaps[] = context.findFilterMaps();
    
            // If there are no filter mappings, we are done
            if ((filterMaps == null) || (filterMaps.length == 0))
                return (filterChain);
    
            // Acquire the information we will need to match filter mappings
            String servletName = wrapper.getName();
    
            // Add the relevant path-mapped filters to this filter chain
            for (int i = 0; i < filterMaps.length; i++) {
                if (!matchDispatcher(filterMaps[i] ,dispatcher)) {
                    continue;
                }
                if (!matchFiltersURL(filterMaps[i], requestPath))
                    continue;
                ApplicationFilterConfig filterConfig = (ApplicationFilterConfig)
                    context.findFilterConfig(filterMaps[i].getFilterName());
                if (filterConfig == null) {
                    // FIXME - log configuration problem
                    continue;
                }
                //略
                } else {
                    filterChain.addFilter(filterConfig);
                }
            }
    
            // Add filters that match on servlet name second
            for (int i = 0; i < filterMaps.length; i++) {
                if (!matchDispatcher(filterMaps[i] ,dispatcher)) {
                    continue;
                }
                if (!matchFiltersServlet(filterMaps[i], servletName))
                    continue;
                ApplicationFilterConfig filterConfig = (ApplicationFilterConfig)
                    context.findFilterConfig(filterMaps[i].getFilterName());
                if (filterConfig == null) {
                    // FIXME - log configuration problem
                    continue;
                }
                //略
                } else {
                    filterChain.addFilter(filterConfig);
                }
            }
    
            // Return the completed filter chain
            return (filterChain);
    }
    

    上述的代码比较长,主要的逻辑有

    1. 每个请求需要生成对应的ApplicationFilterChain,invoke方法中调用ApplicationFilterFactory类的createFilterChain方法创建一个ApplicationFilterChain,其中包含了目标servlet以及对应的过滤器链。
    2. createFilterChain方法中,首先设置了目标servlet,filterChain.setServlet(servlet);
    3. 接着从上下文环境中取出之前解析的filterMaps信息,FilterMap filterMaps[] = context.findFilterMaps();
    4. 遍历filterMaps,判断当前的请求是否符合拦截条件,若符合则将filterConfig放进filterChain中,从这里可以看出,实际决定过滤器执行顺序的是filter-mapping在web.xml中的配置顺序。

    至此一个ApplicationFilterChain便构建好了,包含一个目标servlet和我们想要的过滤器链。

    过滤器链执行

    获取到过滤器链之后,接下来就是过滤器链的具体执行,回到上一步分析开始的StandardWrapperValve类的invoke方法中,现在我们拿到的ApplicationFilterChain,便可以继续向下分析了。

    try {
        if ((servlet != null) && (filterChain != null)) {
            // Swallow output if needed
        if (context.getSwallowOutput()) {
                try {
                    SystemLogHandler.startCapture();
                    if (request.isAsyncDispatching()) {
                        request.getAsyncContextInternal().doInternalDispatch();
                    } else if (comet) {
                        filterChain.doFilterEvent(request.getEvent());
                    } else {
                        filterChain.doFilter(request.getRequest(), response.getResponse());
                    }
                } finally {
                    String log = SystemLogHandler.stopCapture();
                    if (log != null && log.length() > 0) {
                        context.getLogger().info(log);
                    }
                }
            } else {
                if (request.isAsyncDispatching()) {
                    request.getAsyncContextInternal().doInternalDispatch();
                } else if (comet) {
                    filterChain.doFilterEvent(request.getEvent());
                } else {
                    filterChain.doFilter(request.getRequest(), response.getResponse());
                }
            }
        }
    //略
    

    上述代码中,我们关注的是filterChain.doFilter方法,在这里将会触发过滤器链的执行,继续跟踪源码

    public void doFilter(ServletRequest request, ServletResponse response)  throws IOException, ServletException {  
        if( Globals.IS_SECURITY_ENABLED ) {  
            //略
        } else {  
            internalDoFilter(request,response);  
        }  
    }  
    

    最终实际的处理方法是internalDoFilter

    private void internalDoFilter(ServletRequest request, ServletResponse response)  throws IOException, ServletException {
        if (pos < n) {
                ApplicationFilterConfig filterConfig = filters[pos++];
                Filter filter = null;
                try {
                    filter = filterConfig.getFilter();
                    support.fireInstanceEvent(InstanceEvent.BEFORE_FILTER_EVENT, filter, request, response);
    
                    //略
                    if( Globals.IS_SECURITY_ENABLED ) {
                        //略
                    } else {
                        filter.doFilter(request, response, this);
                    }
                    support.fireInstanceEvent(InstanceEvent.AFTER_FILTER_EVENT, filter, request, response);
                } catch (IOException | ServletException | RuntimeException e) {
                    //略
                } catch (Throwable e) {
                    //略
                }
                return;
            }
    
            // We fell off the end of the chain -- call the servlet instance
            try {
                //略
                if ((request instanceof HttpServletRequest) && (response instanceof HttpServletResponse)) {
                    if( Globals.IS_SECURITY_ENABLED ) {
                        //略
                    } else {
                        servlet.service(request, response);
                    }
                } else {
                    servlet.service(request, response);
                }
                support.fireInstanceEvent(InstanceEvent.AFTER_SERVICE_EVENT,
                                          servlet, request, response);
            } catch (IOException e) {
                //略
            } finally {
                if (ApplicationDispatcher.WRAP_SAME_OBJECT) {
                    lastServicedRequest.set(null);
                    lastServicedResponse.set(null);
                }
            }
    }
    

    上面只列出我们关注的关键代码

    1. ApplicationFilterConfig filterConfig = filters[pos++];此处取出当前要执行的filter,并把pos加1。
    2. 执行filter.doFilter方法,并将当前的filterChain传入过滤器中。
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
            System.out.println("[DemoFilter-before]doFilter");
            chain.doFilter(request, response);
            System.out.println("[DemoFilter-after]doFilter");
        }
    
    1. 上面是我们定义的filter,当我们调用chain.doFilter的时候,最终又回到上面的internalDoFilter方法中,取出过滤器链中的下一个过滤器进行执行。
    2. 当过滤器链执行完成后,便会执行servlet.service方法。
    3. 最后internalDoFilter执行完成后,便会回到上一个过滤器的doFilter中,继续执行chain.doFilter之后的代码,直到执行完所有匹配的过滤器。

    至此,过滤器链的执行便完成了。

    过滤器关键类与接口

    1. Filter:实现一个过滤器可以实现该接口
    2. ContextConfig:加载web.xml中的配置信息,并保存到上下文环境中
    3. StandardContext:对具体的filter进行初始化,并保存到上下文环境中
    4. StandardWrapperValve:将请求映射到ApplicationFilterChain,并负责过滤器的执行。
    5. ApplicationFilterChain:负责过滤器链的递归调用

    过滤器应用示例

    编码设置:设置请求及相应的编码
    日志记录:记录请求信息的日志,以便进行信息监控、信息统计、计算PV(Page View)等。
    权限检查:如登录检测,进入处理器检测检测是否登录,如果没有直接返回到登录页面。
    通用行为:读取cookie得到用户信息并将用户对象放入请求,从而方便后续流程使用。

    相关文章

      网友评论

        本文标题:基于Tomcat的Servlet过滤器(1)实例及加载执行源码简

        本文链接:https://www.haomeiwen.com/subject/wefcfftx.html