zoukankan      html  css  js  c++  java
  • how tomcat works 读书笔记 十一 StandWrapper 上

    方法调用序列

    下图展示了方法调用的协作图:


     这个是前面第五章里,我画的图:

     我们再回想一下自从连接器里
     connector.getContainer().invoke(request, response);
     这句代码执行之后发生的事情;
     这是第五章的时序图,放在这一章相同适用。


     我们细致分析一下:
     1首先连接器创建请求与响应对象;
     2调用这行代码
       connector.getContainer().invoke(request, response)
       (我们一StandContext为顶层容器)
     3在容器里会先调用StandardPipeline的invoke方法。例如以下:

       public void invoke(Request request, Response response)
            throws IOException, ServletException {
    
            // Invoke the first Valve in this pipeline for this request
            (new StandardPipelineValveContext()).invokeNext(request, response);
    
        }
     4StandardPipeline有个内部类StandardPipelineValveContext,它的invokeNext方法会循环管道(StandardContext的管道)里的全部阀,直到基础阀,而且调用其invoke方法。

    基础阀在StandardContext初始化的时候就默认指定了StandardContextValve();
     5基础阀的invoke方法调用StandardContext的map方法得到Wrapper
     6调用得到的wrapper(这里是StandardWrapper)的invoke方法...
     7先反复上面的第三第四步,这时获得的基础阀是StandardWrapperValve,它的invoke方法就是调用StandardWrapper类的allocate方法,allocate调用loadServlet获得servlet。


     8产生一个在ApplicationFilterChain:createFilterChain(request, servlet),然后在其internalDoFilter方法中调用servlet的service方法。


       

     SingleThreadModel接口

     这个接口能保证,在容器内部,不论什么时候仅仅有一个进程訪问某个serlvet的service方法。
     这个接口是一个空接口,或者叫标志接口,内部什么都没有。

    就是一种标示而已。

     实现此接口的一个servlet通俗称为SingleThreadModel(STM)的程序组件。

     一个SingleThreadModel组件,能够保证在不论什么时候,仅仅有一个线程来訪问自己。


     非常多程序猿以为,仅仅要实现了上述接口就能保证自己的servlet是线程安全的。

     事实上不然,由于虽然stm能够保证一个servlet能够仅仅被一个线程訪问,但由于效率的关系,对于stm,tomcat会设置一个servletpool,里面会放置多个servlet(都是某一个servlet的实例,当然一个 StanderWrapper也就管一个servlet类)那么这个时候就会出问题了,比如假设若干个servlet的service方法都訪问某个静态类的变量或servelt类以外的类或变量呢?

    相关代码

    public Servlet allocate() throws ServletException {
    
    
            // If not SingleThreadedModel, return the same instance every time
            if (!singleThreadModel) {
    
                // Load and initialize our instance if necessary
                if (instance == null) {
                    synchronized (this) {
                        if (instance == null) 
                                instance = loadServlet();
                    }
                }
                if (!singleThreadModel) {
                   
                    countAllocated++;
                    return (instance);
                }
            }
    
            synchronized (instancePool) {          //能执行到这里,说明一定是实现了singleThreadModel
    
                while (countAllocated >= nInstances) {       //会给池中不断地放置servlet
                    if (nInstances < maxInstances) {
                            instancePool.push(loadServlet());
                            nInstances++;
                       
                    } else {
                            instancePool.wait();  
                    }
                }
                countAllocated++;
                return (Servlet) instancePool.pop();  //最后返回顶上的一个
    
            }
    
        }
    当然当servlet消亡的时候,还会回到池中

     public void deallocate(Servlet servlet) throws ServletException {
    
            // If not SingleThreadModel, no action is required
            if (!singleThreadModel) {
                countAllocated--;
                return;
            }
    
            // Unlock and free this instance
            synchronized (instancePool) {
                countAllocated--;
                instancePool.push(servlet);
                instancePool.notify();
            }
    
        }


     因而这个接口在Servlet 2.4中就已经废弃了,就是由于它让程序猿产生了虚假的安全感。

     StandardWrapper

    StandardWrapper的主要作用就是加载它自己所代表的servlet类,并进行实例化,可是通过分析上面的调用时序图,大家知道它是先调用自己的管道,然后是基础阀,由基础阀调用StandardWrapper的alloacte方法。
    上面已经说了,有个STM,假设容器仅仅是维护一个实现了STM接口的servelt那么调用的时候就应该是这种
        Servlet instance = <get an instance of the servlet>;
        if ((servlet implementing SingleThreadModel>) {
            synchronized (instance) {
                instance.service(request, response);
            }
        }
        else {
            instance.service(request, response);
        }
    只是为了保持性能,StandardWrapper一般维护了一个STM servlet 实例池。
    一个包装器还负责准备一个 javax.servlet.ServletConfig 的实例,这能够在
    servlet 内部完毕,接下来两小节讨论怎样分配和载入 servlet。


     Alloacte方法

     本方法事实上能够分为两部分:
     第一 产生非STMServlet
    StandardWrapper 定义一个 java.servlet.Servlet 类型的实例
    private Servlet instance = null;
    方法 allocate 检查该实例是否为 null。假设是调用 loadServlet 方法来载入servlet。然后添加 contAllocated 整型并返回该实例。

     第二 产生STMServlet
     方法 allocate 尝试返回池中的一个实例,变量intancePool是一个java.util.Stack类型的 STM servlet实例池。
     在这里我须要给大家解释三个变量:
    countAllocated: 眼下存活的servelt数量
    the count of allocations that are currently active (even if they are for the same instance, as will be true on a non-STM servlet).
    nInstances : 已经载入的serlvet数量
    Number of instances currently loaded for a STM servlet.
    maxInstances: 这个看名字就知道了,StandardWrapper支持的最大数目的servlet。


    上面三个变量肯定把大家搞晕了,可是我想说记着一个StandardWrapper就维护一个servlet,它仅仅有一个class地址,那三个变量是在多线程的时候用的!
    这部分的代码比較麻烦,无论是理解还是说都非常麻烦
    总而言之,最開始的时候countAllocated与nInstances都是0,先loadServlet()出一个servelt,push到servlet实例池里面,然后取出来..
    这部分大家自己看代码吧(我认为自己偷的一把好懒呀)

    loadServlet方法

    这种方法首先会回检查instance,假设不为null,而且当前的StandardWrapper并不表示一个STMServlet类,那么就直接返回。
    还有一方面,Catalina也是jsp容器,假设请求的servelt是一个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();
                }
    以下就是获得classloader,默认情况下,我们看前面几章的内容就知道,在Bootstrap里我们就已经指定了WebappLoader,通过它,我们能够获得WebappClassLoader这个对象。
    只是在tomcat中,假设要载入的servlet位于org.apache.catalina.下,那么classLoader就是
            classLoader = this.getClass().getClassLoader(); //this 就是StandardWrapper
    再下来就是loadClass,接着用Class的newInstance获得servlet;
    下来就是检查该servlet是否同意加载(这步我看的不是非常懂),看它是否实现了ContainerServlet接口。
    ContainerServlet接口中有set/getWrapper方法,就是能够让servlet訪问Catalina的内部功能。
    以下就是调用 servlet.init(facade);这个facade是javax.servlet.ServletConfig的一个外观变量。


    假设该StandardWrapper对象表示的是一个STM Servlet。将该实例加入到实例池中,因此,假设实例池假设为null,首先须要创建它。
       // Register our newly initialized instance
       singleThreadModel = servlet instanceof SingleThreadModel;
       if (singleThreadModel) {
        if (instancePool == null)
            instancePool = new Stack();
        }
        fireContainerEvent("load", this);
        }
     最后返回servlet。


     ServletConfig对象

     上面servlet的init方法的參数实际上就是javax.servlet.ServletConfig接口的实例。
     问题出现了,这个接口的实例从哪来来呢?

    大家看看StandardWrapper的声明部分,就知道它本身就实现了ServletConfig接口。


     可是在调用init方法是,StandardWrapper并不会直接把自己传过去而是使用了一个facade,为什么我主要是直接把StandardWrapper传过去,那么StandardWrapper里面的全部public方法不都暴露了么?
     ServletConfig 接口有下面四个方法getServletContext,getServletName,getInitParameter,和getInitParameterNames。



    1 getServletContext
         public ServletContext getServletContext() {
            if (parent == null)
                return (null);
            else if (!(parent instanceof Context))
                return (null);
            else
                return (((Context) parent).getServletContext());
        }
    如今你知道不能单独部署一个包装器来表示一个 Servlet,包装器必须从属于一个上下文容器,这样才干使用 ServletConfig 对象使用getServletContext 方法获得一个 ServletContext 实例。



    2 getServletName
      获得servlet的名字,没有什么好说的

    3 getInitParameter
      获得指定的初始參数的值。StandardWrapper中的初始參数放在一个HashMap中:
      private HashMap<String, String> parameters = new HashMap<String, String>();
      详细的实现大家看代码,这块非常easy。

    4 getInitParameterNames
      返回的是初始化參数的名字的集合,是一个枚举类。

    StandardWrapperFacade类

    类图例如以下:
    ServletConfig共同拥有四个方法,facade类getServletName,getInitParameter,getInitParameterNames都是直接调用StandardWrapper,这些都比較简单,没有什么要说的。
    只是getServletContext就有点复杂了:
        public ServletContext getServletContext() {
            ServletContext theContext = config.getServletContext();
            if ((theContext != null) &&
                (theContext instanceof ApplicationContext))
                theContext = ((ApplicationContext) theContext).getFacade();
            return (theContext);
        }
    看到了吧,先调用StandardWrapper获得Context,可是这里最后给外面返回的还是Facade。(真tm复杂)。



    以下的章节我们会讲
    StandardWrapperValve,FilterDef,ApplicationFilterConfig,ApplicationFilterChain

  • 相关阅读:
    横竖屏判断及禁止浏览器滑动条
    sinablog to cnblogs
    qml 3d 纪念那些曾经爬过的坑
    opencv 学习一安装环境vs2015+opencv3
    vs2015 调试 无法启动程序
    qtableview 表格风格设置
    qt 给父窗体设置样式不影响子控件样式以及子控件设置透明
    Qt ASSERT:"QMetaObjectPrivate::get(smeta)->revision>= 7"in file kernelqobject.cpp,line 2646
    cmake 学习-cmakelists.txt
    CMake学习- 使用批处理设置编译环境
  • 原文地址:https://www.cnblogs.com/yxysuanfa/p/7235512.html
Copyright © 2011-2022 走看看