TomcatJava学习笔记程序员

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

2018-03-10  本文已影响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得到用户信息并将用户对象放入请求,从而方便后续流程使用。

上一篇 下一篇

猜你喜欢

热点阅读