tomcat4标准包装器StandardWrapper

1、关于Mapper的补充

对于Mapper,其有一个默认实现为StandardContextMapper


image.png

在StandardContext的start()方法中,这个默认的Mapper通过addDefaultMapper()方法会被初始化,然后通过Container的addMapper()方法加入到Container中。

private String mapperClass =
        "org.apache.catalina.core.StandardContextMapper";

// StandardContext.addDefaultMapper
protected void addDefaultMapper(String mapperClass) {
        super.addDefaultMapper(this.mapperClass);
    }

// ContainerBase.addDefaultMapper
protected void addDefaultMapper(String mapperClass) {
        // Do we need a default Mapper?
        if (mapperClass == null)
            return;
        if (mappers.size() >= 1)
            return;
        // Instantiate and add a default Mapper
        try {
            Class clazz = Class.forName(mapperClass);
            Mapper mapper = (Mapper) clazz.newInstance();
// 默认Mapper协议为HTTP
            mapper.setProtocol("http");
            addMapper(mapper);
        } catch (Exception e) {
            log(sm.getString("containerBase.addDefaultMapper", mapperClass),
                e);
        }
    }

2、关于SingleThreadModel

对于一个实现了SingleThreadModel接口的servlet,其目的是保证servlet 一次只能有一个请求,也即保证不会有两个线程同是使用 servlet
的 service 方法,该接口并不能避免同步而产生的问题,如访问静态
类变量或该 servlet 以外的类或变量。

3、StandardWrapper处理过程

3、1 Context把请求交给Wrapper过程

在tomcat中,Wrapper容器的父容器为Context,Context的默认实现为StandardContext,其Pipeline的basic Valve为StandardContextValve,当一个请求被Context的invoke方法接收到的时候,根据Pipeline的处理流程,最终交给了StandardContextValve的invoke(Request request, Response response,ValveContext valveContext)方法来处理,而StandardContextValve是通过Context.map()方法来找到对应的Wrapper,Context如何找Wrapper这里略去,比较简单,主要看看Wrapper处理请求的过程,在tomcat中,Wrapper的默认实现为StandardWrapper,其Pipeline的basic Valve为StandardWrapperValve,这里就从这里开始分析:

public void invoke(Request request, Response response,
                       ValveContext valveContext){
        StandardWrapper wrapper = (StandardWrapper) getContainer();
        ServletRequest sreq = request.getRequest();
        ServletResponse sres = response.getResponse();
        try {
            if (!unavailable) {
// 这里通过allocate()方法获取到一个Servlet实例
                servlet = wrapper.allocate();
            }
        } catch (ServletException e) {
        }
        //......
}
3、2 StandardWrapper.allocate()
public Servlet allocate() throws ServletException {
        // If not SingleThreadedModel, return the same instance every time
        if (!singleThreadModel) {
// 如果Servlet没有实现SingleThreadModel,返回的是Servlet单例
// 对于非SingleThreadModel的Servlet,在整个tomcat中只会有一个实例来处理客户端的请求,
// 也就是说多个线程共享一个Servlet实例,这个要考虑资源共享的问题
            // Load and initialize our instance if necessary
            if (instance == null) {
                synchronized (this) {
                    if (instance == null) {
                        try {
                            instance = loadServlet();
                        } catch (ServletException e) {
                            throw e;
                        } catch (Throwable e) {
                            throw new ServletException
                                    (sm.getString("standardWrapper.allocate"), e);
                        }
                    }
                }
            }

            if (!singleThreadModel) {
                if (debug >= 2)
                    log("  Returning non-STM instance");
                countAllocated++;
                return (instance);
            }
        }

        // 而对于SingleThreadModel,tomcat为了提高并发问题,
// 创建了多个Servlet实例来处理请求,实例保存在一个Stack中。
        synchronized (instancePool) {
            while (countAllocated >= nInstances) {
                // Allocate a new instance if possible, or else wait
                if (nInstances < maxInstances) {
                    try {
                        instancePool.push(loadServlet());
                        nInstances++;
                    } catch (ServletException e) {
                        throw e;
                    } catch (Throwable e) {
                        throw new ServletException
                                (sm.getString("standardWrapper.allocate"), e);
                    }
                } else {
                    try {
                        instancePool.wait();
                    } catch (InterruptedException e) {
                        ;
                    }
                }
            }
            countAllocated++;
            return (Servlet) instancePool.pop();
        }
    }

不管是SingleThreadModel的Servlet还是非SingleThreadModel的Servlet,其创建的方法都为loadServlet()

3、3 StandardWrapper.loadServlet()
public synchronized Servlet loadServlet() throws ServletException {
  // 因为jsp最终是被tomcat编译成class文件的,tomcat也需要能处理JSP文件
String actualClass = servletClass;
            if ((actualClass == null) && (jspFile != null)) {
                Wrapper jspWrapper = (Wrapper)
                        ((Context) getParent()).findChild(Constants.JSP_SERVLET_NAME);
                if (jspWrapper != null)
                    actualClass = jspWrapper.getServletClass();
            }
// tomcat有自己的一套类加载机制,主要原因一是安全机制,
// tomcat只允许访问WEB-INF/classes下的servlet,
//二是tomcat要能够热加载servlet,即所谓热加载、动态部署
Loader loader = getLoader();// 得到一个Loader
// 再从loader中取得ClassLoader
ClassLoader classLoader = loader.getClassLoader();

// Special case class loader for a container provided servlet
if (isContainerProvidedServlet(actualClass)) {
    // 查看目标是否为org.apache.catalina包下的Servlet或者是ContainerServlet子接口或实现
    classLoader = this.getClass().getClassLoader();
    log(sm.getString
            ("standardWrapper.containerServlet", getName()));
}

Class classClass = null;
if (classLoader != null) {
  System.out.println("Using classLoader.loadClass");
  classClass = classLoader.loadClass(actualClass);
} else {
    System.out.println("Using forName");
    classClass = Class.forName(actualClass);
}

servlet = (Servlet) classClass.newInstance();
// 初始化操作
// class StandardWrapperFacade implements ServletConfig
// StandardWrapperFacade facade = new StandardWrapperFacade(this);
// public StandardWrapperFacade(StandardWrapper config){}
// 这里传的是包装类,使得servlet只能访问ServletConfig中的方法
servlet.init(facade);
// Invoke jspInit on JSP pages
if ((loadOnStartup > 0) && (jspFile != null)) {
// 如果是JSP文件,并且loadOnStartup 大于0,则立马就调用了service方法
    // Invoking jspInit
    HttpRequestBase req = new HttpRequestBase();
    HttpResponseBase res = new HttpResponseBase();
    req.setServletPath(jspFile);
    req.setQueryString("jsp_precompile=true");
    servlet.service(req, res);
}
// 如果是SingleThreadModel,则初始化一个Stack,用来存放servlet实例
singleThreadModel = servlet instanceof SingleThreadModel;
if (singleThreadModel) {
    if (instancePool == null){
        instancePool = new Stack();
    }
}
}
3、4 ServletConfig对象

在servlet初始化的时候,其需要一个参数为ServletConfig,先看下类图:


image.png

StandardWrapperFacade是一个包装类,内部对于ServletConfig的实现由StandardWrapper来完成,这里只看一下StandardWrapper是如何获取ServletContext 的:

protected Container parent = null;

public ServletContext getServletContext() {
        if (parent == null)
            return (null);
        else if (!(parent instanceof Context))
            return (null);
        else
            return (((Context) parent).getServletContext());
    }

servlet运行的上下文环境由Context创建,从这里可以看到,一个单独的wrapper是不能单独部署的,单独部署的Wrapper是没法获取到servletcontext的。在StandardContext中的getServletContext方法中创建了servletcontext。

public ServletContext getServletContext() {
       if (context == null){
           context = new ApplicationContext(getBasePath(), this);
       }
       return (context);
   }
3、5 StandardWrapperValve后续处理流程

在获取到servlet实例后,StandardWrapperValve继续对请求进行相关处理

// StandardWrapperValve.invoke()
// Create the filter chain for this request
 ApplicationFilterChain filterChain = createFilterChain(request, servlet);

在分析ApplicationFilterChain的创建过程之前,先了解下filter关联的类图


image.png
private ApplicationFilterChain createFilterChain(Request request, Servlet servlet) {

        // If there is no servlet to execute, return null
        if (servlet == null)
            return (null);

        // Create and initialize a filter chain object
        // 构建了一个filterChain,
        ApplicationFilterChain filterChain = new ApplicationFilterChain();
        // 把servlet设置到filterChain中
        filterChain.setServlet(servlet);
        //
        StandardWrapper wrapper = (StandardWrapper) getContainer();
        filterChain.setSupport(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 requestPath = null;
        if (request instanceof HttpRequest) {
            HttpServletRequest hreq = (HttpServletRequest) request.getRequest();
            String contextPath = hreq.getContextPath();
            if (contextPath == null){
                contextPath = "";
            }
            String requestURI = ((HttpRequest) request).getDecodedRequestURI();
            if (requestURI.length() >= contextPath.length()){
                requestPath = requestURI.substring(contextPath.length());
            }
        }
        String servletName = wrapper.getName();
        int n = 0;

        // Add the relevant path-mapped filters to this filter chain
        // 根据url-pattern去添加过滤器
        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++;
        }

        // Add filters that match on servlet name second
        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);
    }

这个构建ApplicationFilterChain的逻辑还是比较简单的,主要看这句:

ApplicationFilterConfig filterConfig = (ApplicationFilterConfig)
                context.findFilterConfig(filterMaps[i].getFilterName());

我们在web.xml中配置的filter,如

    <filter>
        <filter-name>filterName</filter-name>
        <filter-class>filterClass</filter-class>
    </filter>
    <filter-mapping>
        <filter-name>filterName</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>

都会被先转化为FilterDef和FilterMap,这些FilterDef和FilterMap被保存在Context中,在Context的启动过程中会被转化为ApplicationFilterConfig:
StandardContext.start():

public synchronized void start() throws LifecycleException {
    if (ok) {
            if (!filterStart())
                ok = false;
        }
}

StandardContext.filterStart():

public boolean filterStart() {
        // Instantiate and record a FilterConfig for each defined filter
        boolean ok = true;
        synchronized (filterConfigs) {
            filterConfigs.clear();
            Iterator names = filterDefs.keySet().iterator();
            while (names.hasNext()) {
                String name = (String) names.next();
                if (debug >= 1)
                    log(" Starting filter '" + name + "'");
                ApplicationFilterConfig filterConfig = null;
                try {
                    filterConfig = new ApplicationFilterConfig
                      (this, (FilterDef) filterDefs.get(name));
                    filterConfigs.put(name, filterConfig);
                } catch (Throwable t) {
                    log(sm.getString("standardContext.filterStart", name), t);
                    ok = false;
                }
            }
        }
        return (ok);
    }

所以StandardContext找FilterConfig的方式就比较简单了:

public FilterConfig findFilterConfig(String name) {
        synchronized (filterConfigs) {
            return ((FilterConfig) filterConfigs.get(name));
        }
    }

在获取到ApplicationFilterChain之后,StandardWrapperValve.invoke()中紧跟着就调用其doFilter方法,ApplicationFilterChain中doFilter最终调用了internalDoFilter()方法:

if ((servlet != null) && (filterChain != null)) {
                filterChain.doFilter(sreq, sres);
            }

ApplicationFilterChain.internalDoFilter()

private void internalDoFilter(ServletRequest request, ServletResponse response){
    if (this.iterator == null){
            this.iterator = filters.iterator();
    }
// 如果还有拦截器
    if (this.iterator.hasNext()) {
            ApplicationFilterConfig filterConfig =
              (ApplicationFilterConfig) iterator.next();
            Filter filter = null;
            filter = filterConfig.getFilter();
            filter.doFilter(request, response, this);
            return;
      }
// 所有的拦截器都走完了,最后调用servlet的service()
      if ((request instanceof HttpServletRequest) &&
                (response instanceof HttpServletResponse)) {
                servlet.service((HttpServletRequest) request,
                                (HttpServletResponse) response);
            } else {
                servlet.service(request, response);
            }
}

从这段逻辑中可以看出,在自己定义拦截器的时候,如果最后没有调用ApplicationFilterChain.doFilter()方法,那后续所有的filter和servlet的service()都是没有机会被执行的,从而达到了拦截的目的。

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

推荐阅读更多精彩内容