Tomcat4源码分析之StandardWrapperValve

  1. standardWrapperValue 是 StanderWrapper运行时使用的基础阀
  2. standardWrapperValue 会在这里通过 StanderWrapper 加载Servlet,并且调用里面的service方法
  3. standardWrapperValue 会在这里完成设置的Filter中的过滤方法的调用,并且在调用完成之后,会再次调用Servlet的service方法

[TOC]

StandardWrapperValue的主要作用

StandardWrapperValue是Wrapper的标准阀,用在Pipleline流程中的最后一个valve执行。当Request请求到达Context的时候,继续传递给Wrapper,首先是调用StandardWrapper的Invoke方法(ContainerBase父类中),如下.

public void invoke(Request request, Response response)
        throws IOException, ServletException {
        pipeline.invoke(request, response);
    }

pipeline就是与容器关联的一系列处理流程,StandardWrapperValve在初始化的时候就被加入到了pipeline中。而下面调用的pipeline的invoke方法,就是调用里面的各种Valve的invoke方法。

下面就是是StandedWrapperValve的核心逻辑,invoke方法:

public void invoke(Request request, Response response,
                       ValveContext valveContext)
        throws IOException, ServletException {
        // Initialize local variables we may need
        boolean unavailable = false;
        Throwable throwable = null;
        StandardWrapper wrapper = (StandardWrapper) getContainer();
        ServletRequest sreq = request.getRequest();
        ServletResponse sres = response.getResponse();
        Servlet servlet = null;
        HttpServletRequest hreq = null;
        if (sreq instanceof HttpServletRequest)
            hreq = (HttpServletRequest) sreq;
        HttpServletResponse hres = null;
        if (sres instanceof HttpServletResponse)
            hres = (HttpServletResponse) sres;

        // Check for the application being marked unavailable
        if (!((Context) wrapper.getParent()).getAvailable()) {
            //503
            hres.sendError(HttpServletResponse.SC_SERVICE_UNAVAILABLE,
                           sm.getString("standardContext.isUnavailable"));
            unavailable = true;
        }

        // Check for the servlet being marked unavailable
        if (!unavailable && wrapper.isUnavailable()) {
            log(sm.getString("standardWrapper.isUnavailable",
                             wrapper.getName()));
            if (hres == null) {
                ;       // NOTE - Not much we can do generically
            } else {
                long available = wrapper.getAvailable();
                if ((available > 0L) && (available < Long.MAX_VALUE))
                    hres.setDateHeader("Retry-After", available);
                hres.sendError(HttpServletResponse.SC_SERVICE_UNAVAILABLE,
                               sm.getString("standardWrapper.isUnavailable",
                                            wrapper.getName()));
            }
            unavailable = true;
        }

        // Allocate a servlet instance to process this request
        try {
            if (!unavailable) {
                //通过Wrapper获取Servlet实例,内部已经调用了service(request, response)方法,对req与res进行了字段赋值
                //下面是对Response和Request进行后续的处理
                servlet = wrapper.allocate();
            }
        } catch (ServletException e) {
            log(sm.getString("standardWrapper.allocateException",
                             wrapper.getName()), e);
            throwable = e;
            exception(request, response, e);
            servlet = null;
        } catch (Throwable e) {
            log(sm.getString("standardWrapper.allocateException",
                             wrapper.getName()), e);
            throwable = e;
            exception(request, response, e);
            servlet = null;
        }

        // Acknowlege the request
        try {
            response.sendAcknowledgement();
        } catch (IOException e) {
            sreq.removeAttribute(Globals.JSP_FILE_ATTR);
            log(sm.getString("standardWrapper.acknowledgeException",
                             wrapper.getName()), e);
            throwable = e;
            exception(request, response, e);
        } catch (Throwable e) {
            log(sm.getString("standardWrapper.acknowledgeException",
                             wrapper.getName()), e);
            throwable = e;
            exception(request, response, e);
            servlet = null;
        }

        // Create the filter chain for this request
        // 创建对于Request与Response的过滤链
        ApplicationFilterChain filterChain =
            createFilterChain(request, servlet);

        // Call the filter chain for this request
        // NOTE: This also calls the servlet's service() method
        try {
            String jspFile = wrapper.getJspFile();
            if (jspFile != null)
                sreq.setAttribute(Globals.JSP_FILE_ATTR, jspFile);
            else
                sreq.removeAttribute(Globals.JSP_FILE_ATTR);
            if ((servlet != null) && (filterChain != null)) {
                //使用过滤链过滤Request与Response
                filterChain.doFilter(sreq, sres);
            }
            sreq.removeAttribute(Globals.JSP_FILE_ATTR);
        } catch (IOException e) {
            sreq.removeAttribute(Globals.JSP_FILE_ATTR);
            log(sm.getString("standardWrapper.serviceException",
                             wrapper.getName()), e);
            throwable = e;
            exception(request, response, e);
        } catch (UnavailableException e) {
            sreq.removeAttribute(Globals.JSP_FILE_ATTR);
            log(sm.getString("standardWrapper.serviceException",
                             wrapper.getName()), e);
            wrapper.unavailable(e);
            long available = wrapper.getAvailable();
            if ((available > 0L) && (available < Long.MAX_VALUE))
                hres.setDateHeader("Retry-After", available);
            hres.sendError(HttpServletResponse.SC_SERVICE_UNAVAILABLE,
                           sm.getString("standardWrapper.isUnavailable",
                                        wrapper.getName()));
            // Do not save exception in 'throwable', because we
            // do not want to do exception(request, response, e) processing
        } catch (ServletException e) {
            sreq.removeAttribute(Globals.JSP_FILE_ATTR);
            log(sm.getString("standardWrapper.serviceException",
                             wrapper.getName()), e);
            throwable = e;
            exception(request, response, e);
        } catch (Throwable e) {
            sreq.removeAttribute(Globals.JSP_FILE_ATTR);
            log(sm.getString("standardWrapper.serviceException",
                             wrapper.getName()), e);
            throwable = e;
            exception(request, response, e);
        }

        // Release the filter chain (if any) for this request
        try {
            if (filterChain != null)
                //释放过滤链
                filterChain.release();
        } catch (Throwable e) {
            log(sm.getString("standardWrapper.releaseFilters",
                             wrapper.getName()), e);
            if (throwable == null) {
                throwable = e;
                exception(request, response, e);
            }
        }

        // Deallocate the allocated servlet instance
        try {
            if (servlet != null) {
                //释放servlet
                wrapper.deallocate(servlet);
            }
        } catch (Throwable e) {
            log(sm.getString("standardWrapper.deallocateException",
                             wrapper.getName()), e);
            if (throwable == null) {
                throwable = e;
                exception(request, response, e);
            }
        }

        // If this servlet has been marked permanently unavailable,
        // unload it and release this instance
        try {
            if ((servlet != null) &&
                (wrapper.getAvailable() == Long.MAX_VALUE)) {
                //回收所有的Servlet
                wrapper.unload();
            }
        } catch (Throwable e) {
            log(sm.getString("standardWrapper.unloadException",
                             wrapper.getName()), e);
            if (throwable == null) {
                throwable = e;
                exception(request, response, e);
            }
        }

    }

上面的代码看着挺长的,对照代码主要做了下面几件事:

  1. 通过Wrapper获取Servlet实例,内部已经调用了service(request, response)方法,对req与res进行了字段赋值
  2. 给请求发送确认,这时还是空实现
  3. 创建对于Request与Response的过滤链
  4. 使用过滤链过滤Request与Response
  5. 释放servlet,释放的STM类型的servlet会被放入的stack中,以后会被再次使用
  6. 根据情况决定是否回收所有的Servlet

1. 就是通过Wrapper加载Servlet,并且调用了Servlet的Service方法。

这里的的Wrapper是指Wrapper的标准实现:StandardWrapper。里面的allocate方法会通过ClassLoader来加载一个Servlet。
具体的逻辑看StandWrapper

2. 给请求发送确认,这时还是空实现

Send an acknowledgment of a request.发送一个请求的确认。在tomcat4版本还是空实现

3. 创建对于Request与Response的过滤链

过滤链的过滤项是在Wrapper的父容器Context(StanderContext)中设置的。StanderContext提供了FilterMap[]数组来存储各个过滤项。Filter使用户可以改变一个request和修改一个response,它不是一个servlet,也不能产生response,它能够在一个request到达servlet之前预处理request,也可以在response离开servlet时处理response。

How Tomcat Works - Chapter 11 - Standard Wrapper
Tomcat Filter 源码分析

private FilterMap filterMaps[] = new FilterMap[0];

那么FilterMap到底是什么呢?下面是FilterMap的定义,里面只包含了是三个字段:

private String filterName = null;       //当成功匹配时,执行名字为filterName的筛选器
private String servletName = null;      //用于匹配Request的name,对应的StandardWrapper设置的Name
private String urlPattern = null;       //用于匹配Request中的RequestURL,最终要对应一个StanderWrapper

而在StanderWrapperValue中,通过父容器StanderderContext获取到这个FielterMap[]数组,通过下面的代码构建过滤链:

private ApplicationFilterChain createFilterChain(Request request,
                                                     Servlet servlet) {

        if (servlet == null)
            return (null);

        // 创建并且初始化一个过滤链对象ApplicationFilterChain
        ApplicationFilterChain filterChain = new ApplicationFilterChain();
        filterChain.setServlet(servlet);

        StandardWrapper wrapper = (StandardWrapper) getContainer();
        filterChain.setSupport(wrapper.getInstanceSupport());

        // 获取Context的过滤mappings
        StandardContext context = (StandardContext) wrapper.getParent();
        FilterMap filterMaps[] = context.findFilterMaps();

        if ((filterMaps == null) || (filterMaps.length == 0))
            return (filterChain);

        // 获取我们需要的匹配 fliter mappings 的信息
        String requestPath = null;
        if (request instanceof HttpRequest) {
            HttpServletRequest hreq = (HttpServletRequest) request.getRequest();
            String contextPath = hreq.getContextPath(); //设置的一个context对用的mapper匹配路径
            if (contextPath == null) {
                contextPath = "";
            }
            String requestURI = ((HttpRequest) request).getDecodedRequestURI();
            //去掉RequestURI中的contextPath
            if (requestURI.length() >= contextPath.length()) {
                requestPath = requestURI.substring(contextPath.length());
            }
        }

        String servletName = wrapper.getName();
        int n = 0;

        // 根据RequestURI匹配FilterMaps中的过滤项,添加到filterChain中
        for (int i = 0; i < filterMaps.length; i++) {
            if (!matchFiltersURL(filterMaps[i], requestPath))
                continue;
            ApplicationFilterConfig filterConfig = (ApplicationFilterConfig) context.findFilterConfig(filterMaps[i].getFilterName());
            if (filterConfig == null) {
                continue;
            }
            filterChain.addFilter(filterConfig);
            n++;
        }

        // 根据StanderWrapper的Name来匹配FilterMaps中的过滤项,添加到filterChain
        for (int i = 0; i < filterMaps.length; i++) {
            if (!matchFiltersServlet(filterMaps[i], servletName))
                continue;
            ApplicationFilterConfig filterConfig = (ApplicationFilterConfig) context.findFilterConfig(filterMaps[i].getFilterName());
            if (filterConfig == null) {
                continue;
            }
            filterChain.addFilter(filterConfig);
            n++;
        }

        return (filterChain);
    }

在 createFilterChain 的过程中,调用了matchFiltersURL(),matchFiltersServlet()方法来判断FliterMap是否匹配到当前Request

  • matchFiltersServlet:比较匹配ServletName
    private boolean matchFiltersServlet(FilterMap filterMap,String servletName) {

        if (servletName == null)
            return (false);
        else {
            //比较的是FilterMap的ServletName与StanderWrapper的Name(即Servlet)
            return (servletName.equals(filterMap.getServletName()));
          }
    }
  • 调用了matchFiltersURL:比较匹配URL。解释在注释里面
private boolean matchFiltersURL(FilterMap filterMap, String requestPath) {

        if (requestPath == null)
            return (false);

        String testPath = filterMap.getURLPattern();
        if (testPath == null)
            return (false);

        // Case 1 - Exact Match 完全匹配
        if (testPath.equals(requestPath))
            return (true);

        // Case 2 - Path Match ("/.../*")
        if (testPath.equals("/*"))
            return (true);
        if (testPath.endsWith("/*")) {
            String comparePath = requestPath;
            //前缀匹配,如:匹配a/, /a/b/c:匹配顺序就是a/b/c --> /a/b --> a/ -->true
            while (true) {
                if (testPath.equals(comparePath + "/*"))
                    return (true);
                int slash = comparePath.lastIndexOf('/');
                if (slash < 0)
                    break;
                comparePath = comparePath.substring(0, slash);
            }
            return (false);
        }

        // Case 3 - Extension Match 后缀匹配RequestURL '.' 出现之后的部分
        if (testPath.startsWith("*.")) {
            int slash = requestPath.lastIndexOf('/');
            int period = requestPath.lastIndexOf('.');
            if ((slash >= 0) && (period > slash))
                return (testPath.equals("*." + requestPath.substring(period + 1)));
        }

        // Case 4 - "Default" Match
        return (false); // NOTE - Not relevant for selecting filters
    }

4. 使用过滤链过滤Request与Response:filterChain.doFilter(sreq, sres);

这里是调用ApplicationFilterChain的doFilter()方法,doFilter内部有调用了 internalDoFilter()方法。下面是internalDoFilter方法的实现:

private void internalDoFilter(ServletRequest request, ServletResponse response)
        throws IOException, ServletException {

        if (this.iterator == null)
            this.iterator = filters.iterator();

        //调用每一个Filter的Filter方法
        if (this.iterator.hasNext()) {
            ApplicationFilterConfig filterConfig =
              (ApplicationFilterConfig) iterator.next();
            Filter filter = null;
            try {
                filter = filterConfig.getFilter();
                support.fireInstanceEvent(InstanceEvent.BEFORE_FILTER_EVENT,
                                          filter, request, response);
                //调用过滤方法
                filter.doFilter(request, response, this);
                support.fireInstanceEvent(InstanceEvent.AFTER_FILTER_EVENT,
                                          filter, request, response);
            } 
            return;
        }

        try {
            support.fireInstanceEvent(InstanceEvent.BEFORE_SERVICE_EVENT,
                                      servlet, request, response);
            if ((request instanceof HttpServletRequest) &&
                (response instanceof HttpServletResponse)) {
                servlet.service((HttpServletRequest) request,
                                (HttpServletResponse) response);
            } else {
                //如果存在Filter,调用service方法处理request
                servlet.service(request, response);
            }
            support.fireInstanceEvent(InstanceEvent.AFTER_SERVICE_EVENT,
                                      servlet, request, response);
        }

    }

上面方法就只做了两件事:

  1. 调用每一个Filter的doFilter方法
  2. 在调用每一个Filter完成之后,调用service方法处理request。

doFilter的之后的逻辑,没有看,就不说了。

5. 释放servlet,释放的STM类型的servlet会被放入的stack中,以后会被再次使用:deallocate(Servlet servlet)

dealoocate的实现是在StanderWrapper中,在servlet完成service方法,完成对于request,response处理之后,之后就调用这个方法,完成回收工作

public void deallocate(Servlet servlet) throws ServletException {

        //如果不是STM,就直接返回。STM类型的Servlet每次只有一个Servelt在执行Service方法
        if (!singleThreadModel) {
            countAllocated--;
            return;
        }

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

推荐阅读更多精彩内容

  • 本文包括:1、Filter简介2、Filter是如何实现拦截的?3、Filter开发入门4、Filter的生命周期...
    廖少少阅读 7,272评论 3 56
  • 从三月份找实习到现在,面了一些公司,挂了不少,但最终还是拿到小米、百度、阿里、京东、新浪、CVTE、乐视家的研发岗...
    时芥蓝阅读 42,246评论 11 349
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,656评论 18 139
  • IOC 控制反转容器控制程序对象之间的关系,而不是传统实现中,有程序代码之间控制,又名依赖注入。All 类的创建,...
    irckwk1阅读 942评论 0 0
  • 其实,我没有想象中那么坚强 其实,哪怕被父母一句气话,说我是个自私的人,我也能依旧花上2两分钟的时间,然后笑脸依旧...
    冲向心中的那座城阅读 247评论 0 0