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

实例

实现一个简单的过滤器只需要两步
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得到用户信息并将用户对象放入请求,从而方便后续流程使用。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,386评论 6 479
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,939评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,851评论 0 341
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,953评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,971评论 5 369
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,784评论 1 283
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,126评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,765评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,148评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,744评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,858评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,479评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,080评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,053评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,278评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,245评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,590评论 2 343

推荐阅读更多精彩内容

  • 仅作为自己学习记录使用,文章来自: 1、http://blog.csdn.net/csh624366188/art...
    BakerZhang阅读 1,009评论 1 5
  • 本文包括:1、Filter简介2、Filter是如何实现拦截的?3、Filter开发入门4、Filter的生命周期...
    廖少少阅读 7,247评论 3 56
  • 监听器(listener) 监听器简介 :监听器就是一个实现特定接口的普通java程序,这个程序专门用于监听另一个...
    奋斗的老王阅读 2,488评论 0 53
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,598评论 18 139
  • 这周本来想请假,因为要回上海参加培训考试。但是想着,就算是流水账,也要把写作这个良好的习惯坚持下去,我不是因...
    Linda_f34d阅读 224评论 0 1