zoukankan      html  css  js  c++  java
  • StandardContext

    StandardContext

    Context实例表示一个具体的Web应用程序,其中包含一个或者多个Wrapper实例,每个Wrapper表示一个具体的Servlet定义,Context还需要其他组件的支持,典型的如加载器 和 Session管理器,下面就对org.apache.catlainia.core.StandardContext类的工作机制进行详细记录,该类是Context的标准实现,先来简单回顾一下StandardContext类的实例化 和 配置,然后 在了解下与StandardContext类相关的StandardContextMapper类(存在Tomcat4中),和ContextConfig类,然后呢学习一下对于每一个引入的HTTP请求的方法的方法的调用序列,然后在了解下StandardContext类的几个重要属性

    StandardContext的配置

    在创建了StandardContext实例之后,必须调用其start()方法来为引入的每个http请求提供服务,但是可能会因为某种原因,StandardContext对象可能会启动失败,这时StandardContext类的available属性会被设置为false,available属性表明属性StandardContext对象是否可用,展示一下其生命变量 以及其设置 与获取的方法

       /**
         * 该 {@link Context} 的应用程序可用标志
         */
        private boolean available = false;        
     1 /**
     2      * 返回这个{@link Context} 应用程序的可用标志
     3      */
     4     public boolean getAvailable() {
     5 
     6         return (this.available);
     7 
     8     }
     9 
    10     /**
    11      * 
    12      * <dd>设置这个{@link Context} 应用程序的可用标志</dd>
    13      *
    14      * @param available
    15      *            应用程序新的可用标志
    16      */
    17     public void setAvailable(boolean available) {
    18 
    19         boolean oldAvailable = this.available;
    20         this.available = available;
    21         // 触发属性改变的监听事件
    22         support.firePropertyChange("available", new Boolean(oldAvailable), new Boolean(this.available));
    23 
    24     }

      若是start()方法正确执行,则表明StandardContext对象配置正确,在Tomcat的实际部署中,配置StandardContext对象需要一系列的操作,正确的设置后,StandardContext对象才能读取并解析默认的web.xml文件,该文件位于%CATALINA_HOME%/conf目录下,该文件的内容会应用到所有部署到Tomcat中的应用程序中,这也保证了StandardContext实例可以处理应用程序级的web.xml文件,此外还会配置验证器阀和许可阀。

    StandardContext类的configured属性是一个布尔变量,表明 StandardContext实例是否正确设置,StandardContext类使用一个事件监听器作为其配置器,当调用StandardContext实例的start方法时,其中要做的第一件事就是触发一个声明周期事件,该事件调用监听器,对StandardContext实例进行配置,若配置成功,监听器会将configured属性设置为true,否则StandardContext实例会拒绝启动,也就无法为http请求提供服务了。

    下面我们从StandardContext的类构造器来开始了解它的工作原理

     1   /**
     2      *
     3      * 用默认的基本阀 创建一个新的标准Context组件
     4      */
     5     public StandardContext() {
     6 
     7         super();
     8         pipeline.setBasic(new StandardContextValve());
     9         namingResources.setContainer(this);
    10 
    11     }

    构造函数中最重要的事情是为StandardContext实例的管道对象设置基础阀,其类型是 org.apache.catalina.core.StandardContextValue类,该基础阀会处理从连接器中接收到的每个Http请求。

    启动 StandardContext实例

      start方法会初始化StandardContext对象,用生命周期监听器配置StandardContext实例,当配置成功后,监听器会将其configured属性设置为true,最后,start方法会将available属性设置为true或者false,true表明StandardContext对象设置正确,与其相关联的子容器和组件都正确启动,因此,StandardContext实例可以准备为引入的Http请求提供服务了,若期间 发生任何错误,可用属性 available会被设置为false。在Tomcat实际部署中,负责配置StandardContext实例的生命周期监听器是org.apache.catalina.core.startup.ContextConfig类。这个类我会在后续的随笔中详细讨论。

      StandardContext使用一个初始化为false的布尔类型变量configured来表明StandardContext对象是否正确配置,如果生命周期监听器成功了执行其配置StandardContext实例的任务,生命周期监听器就会将StandardContext对象的configured属性设置为true,在StandardContext类的

    start方法末尾,会检查StandardContext对象的configured属性的值,若configured属性的值为true,则StandardContext实例启动成功,否则将调用stop方法,关闭在start方法中启动的所有组件。

      1 /**
      2      * 启动这个 {@link Context} 组件
      3      *
      4      * @exception LifecycleException
      5      *                如果在启动时发生错误
      6      */
      7     public synchronized void start() throws LifecycleException {
      8         if (started)
      9             throw new LifecycleException(sm.getString("containerBase.alreadyStarted", logName()));
     10 
     11         if (debug >= 1)
     12             log("Starting");
     13 
     14         // 第一步:触发生命监听器的 BEFORE_START_EVENT 事件
     15         lifecycle.fireLifecycleEvent(BEFORE_START_EVENT, null);
     16 
     17         if (debug >= 1)
     18             log("Processing start(), current available=" + getAvailable());
     19         // 先将Context 的available 和 configured 属性都设置为false
     20         // 表示当前的StandardContext实例不可以用 且 没有配置成功
     21         setAvailable(false);
     22         setConfigured(false);
     23         // start方法中 正确执行的状态值 由于下面的代码可能会出现某些意外情况导致 启动失败 ok为 下面代码是否可以正确执行的标志
     24         boolean ok = true;
     25         // --------------配置资源
     26         if (getResources() == null) { // (1) Required by Loader
     27             if (debug >= 1)
     28                 log("Configuring default Resources");
     29             try {
     30                 if ((docBase != null) && (docBase.endsWith(".war")))
     31                     setResources(new WARDirContext());
     32                 else
     33                     setResources(new FileDirContext());
     34             } catch (IllegalArgumentException e) {
     35                 log("Error initializing resources: " + e.getMessage());
     36                 ok = false;
     37             }
     38         }
     39         if (ok && (resources instanceof ProxyDirContext)) {
     40             DirContext dirContext = ((ProxyDirContext) resources).getDirContext();
     41             if ((dirContext != null) && (dirContext instanceof BaseDirContext)) {
     42                 ((BaseDirContext) dirContext).setDocBase(getBasePath());
     43                 ((BaseDirContext) dirContext).allocate();
     44             }
     45         }
     46         // ------------------设置载入器
     47         if (getLoader() == null) { // 若没有配置好的加载器
     48             if (getPrivileged()) {
     49                 if (debug >= 1)
     50                     log("Configuring privileged default Loader");
     51                 // 使用默认的加载器
     52                 setLoader(new WebappLoader(this.getClass().getClassLoader()));
     53             } else {
     54                 if (debug >= 1)
     55                     log("Configuring non-privileged default Loader");
     56                 setLoader(new WebappLoader(getParentClassLoader()));
     57             }
     58         }
     59         // ----------------设置Session管理器
     60         if (getManager() == null) { // 若没有配置好的Session管理器 设置默认的管理器
     61             if (debug >= 1)
     62                 log("Configuring default Manager");
     63             setManager(new StandardManager());
     64         }
     65 
     66         // 初始化字符集映射器
     67         getCharsetMapper();
     68 
     69         // Post work directory
     70         postWorkDirectory();
     71 
     72         // Reading the "catalina.useNaming" environment variable
     73         String useNamingProperty = System.getProperty("catalina.useNaming");
     74         if ((useNamingProperty != null) && (useNamingProperty.equals("false"))) {
     75             useNaming = false;
     76         }
     77 
     78         if (ok && isUseNaming()) {
     79             if (namingContextListener == null) {
     80                 namingContextListener = new NamingContextListener();
     81                 namingContextListener.setDebug(getDebug());
     82                 namingContextListener.setName(getNamingContextName());
     83                 addLifecycleListener(namingContextListener);
     84             }
     85         }
     86 
     87         // Binding thread
     88         ClassLoader oldCCL = bindThread();
     89 
     90         // Standard container startup
     91         if (debug >= 1)
     92             log("Processing standard container startup");
     93 
     94         if (ok) {
     95 
     96             try {
     97 
     98                 addDefaultMapper(this.mapperClass);
     99                 started = true;
    100 
    101                 // ------------ 启动我们的附属组件,如果有的话
    102                 if ((loader != null) && (loader instanceof Lifecycle))
    103                     ((Lifecycle) loader).start();
    104                 if ((logger != null) && (logger instanceof Lifecycle))
    105                     ((Lifecycle) logger).start();
    106 
    107                 // Unbinding thread
    108                 unbindThread(oldCCL);
    109 
    110                 // Binding thread
    111                 oldCCL = bindThread();
    112 
    113                 if ((cluster != null) && (cluster instanceof Lifecycle))
    114                     ((Lifecycle) cluster).start();
    115                 if ((realm != null) && (realm instanceof Lifecycle))
    116                     ((Lifecycle) realm).start();
    117                 if ((resources != null) && (resources instanceof Lifecycle))
    118                     ((Lifecycle) resources).start();
    119 
    120                 // 启动该StandardContext关联的映射器
    121                 Mapper mappers[] = findMappers();
    122                 for (int i = 0; i < mappers.length; i++) {
    123                     if (mappers[i] instanceof Lifecycle)
    124                         ((Lifecycle) mappers[i]).start();
    125                 }
    126 
    127                 // 启动该StandardContext拥有的所有生命周期子容器
    128                 Container children[] = findChildren();
    129                 for (int i = 0; i < children.length; i++) {
    130                     if (children[i] instanceof Lifecycle)
    131                         ((Lifecycle) children[i]).start();
    132                 }
    133 
    134                 // 启动管道对象
    135                 if (pipeline instanceof Lifecycle)
    136                     ((Lifecycle) pipeline).start();
    137 
    138                 // 触发Start监听事件,在这里(ContextConfig)监听器
    139                 // 会执行一些配置操作,若设置成功,ContextConfig实例就会将StandardContext实例的configured
    140                 // 设置为true。
    141                 lifecycle.fireLifecycleEvent(START_EVENT, null);
    142 
    143                 // 启动设置好的Session管理器
    144                 if ((manager != null) && (manager instanceof Lifecycle))
    145                     ((Lifecycle) manager).start();
    146 
    147             } finally {
    148                 // Unbinding thread
    149                 unbindThread(oldCCL);
    150             }
    151 
    152         }
    153         // 检查Configured的值 若为false(也就是在上面的Start监听事件中 ContextConfig实例没有正确配置
    154         // StandContext)所以会将 ok 标志位赋值为false 表示启动失败
    155         if (!getConfigured())
    156             ok = false;
    157 
    158         // We put the resources into the servlet context
    159         if (ok)
    160             getServletContext().setAttribute(Globals.RESOURCES_ATTR, getResources());
    161 
    162         // Binding thread
    163         oldCCL = bindThread();
    164 
    165         // Create context attributes that will be required
    166         if (ok) {
    167             if (debug >= 1)
    168                 log("Posting standard context attributes");
    169             postWelcomeFiles();
    170         }
    171 
    172         // Configure and call application event listeners and filters
    173         if (ok) {
    174             if (!listenerStart())
    175                 ok = false;
    176         }
    177         if (ok) {
    178             if (!filterStart())
    179                 ok = false;
    180         }
    181 
    182         // Load and initialize all "load on startup" servlets
    183         if (ok)
    184             loadOnStartup(findChildren());
    185 
    186         // Unbinding thread
    187         unbindThread(oldCCL);
    188 
    189         // Set available status depending upon startup success
    190         if (ok) {
    191             if (debug >= 1)
    192                 log("Starting completed");
    193             setAvailable(true);
    194         } else {
    195             log(sm.getString("standardContext.startFailed"));
    196             try {
    197                 stop();
    198             } catch (Throwable t) {
    199                 log(sm.getString("standardContext.startCleanup"), t);
    200             }
    201             setAvailable(false);
    202         }
    203 
    204         // 触发 AFTER——START事件
    205         lifecycle.fireLifecycleEvent(AFTER_START_EVENT, null);
    206 
    207     }

    start方法主要做了以下工作

    • 触发生命周期监听器的 BEFORE_START_EVEN事件
    • 将代表StandardContext实例是否可用的标志属性available 赋值为false 代表还没有启动成功 在方法最后会对该值进行重新赋值;
    • 将代表StandardContext实例是否正确得到配置的标志属性configured 设置false;代表还没有成功配置。在方法中触发生命周期事件的START事件时 ContextConfig实例会对configured属性重新赋值;
    • 获取并配置资源
    • 获取配置的载入器并设置载入器 
    • 获取配置的Session管理器并设置Session管理器
    • 初始化默认字符映射集 插入点细致的分析这一步 因为看了 所以就记录下来吧 要不就白看了 

         先看看获取字符映射集

        // 初始化字符集映射器
            getCharsetMapper();
    /**
         * 
         * 返回此Context的字符集映射器的区域设置。
         */
        public CharsetMapper getCharsetMapper() {
    
            // 在第一次调用该方法时 创建映射器
            if (this.charsetMapper == null) {
                try {
                    Class clazz = Class.forName(charsetMapperClass);
                    this.charsetMapper = (CharsetMapper) clazz.newInstance();
                } catch (Throwable t) {
                    this.charsetMapper = new CharsetMapper();
                }
            }
    
            return (this.charsetMapper);
    
        }

    StandardContext类 用一个名为 charsetMapperClass 的String类型 成员变量表示 默认字符映射集类的 的完全限定名

    /**
         * 要创建的字符集类的Java类名.
         */
        private String charsetMapperClass = "org.apache.catalina.util.CharsetMapper";

    有对应的get与set方法 但是set方法最好在 StandardContext类的START方法执行前 设置 看上面可以了解到 字符映射集类 是在start方法中被创建的,又或者可以写一个属性监听器来做这件事情 因为StandardContext中大多数set方法都会触发

    属性更改监听事件,可以在监听器中 将StandardContext的 charsetMapper属性重置。

    public void setCharsetMapper(CharsetMapper mapper) {
    
            CharsetMapper oldCharsetMapper = this.charsetMapper;
            this.charsetMapper = mapper;
            support.firePropertyChange("charsetMapper", oldCharsetMapper, this.charsetMapper);
    
        }

    那么下面看下默认的字符集映射的默认实现类

    package org.apache.catalina.util;
    
    import java.io.InputStream;
    import java.util.Locale;
    import java.util.Properties;
    
    /**
     * 
     * <p>
     * <b>Title:CharsetMapper.java</b>
     * </p>
     * <p>
     * Copyright:ChenDong 2018
     * </p>
     * <p>
     * Company:仅学习时使用
     * </p>
     * <p>
     * 类功能描述: 实用程序类,当请求头集合中不包括Content-Type时,尝试从Locale映射到用于解释输入文本(或生成输出文本)的相应字符集。
     * 您可以通过修改它加载的映射数据,或者通过子类化(以改变方法法),然后为特定的Web应用程序使用您自己的版本,来定制这个类的行为
     * </p>
     * 
     * @author 陈东
     * @date 2018年12月3日 下午8:35:28
     * @version 1.0
     */
    public class CharsetMapper {
    
        // ---------------------------------------------------- Manifest Constants
    
        /**
         * 默认属性资源名称。
         */
        public static final String DEFAULT_RESOURCE = "/org/apache/catalina/util/CharsetMapperDefault.properties";
    
        // ---------------------------------------------------------- Constructors
    
        /**
         * 使用默认属性资源构造一个新的字符集
         */
        public CharsetMapper() {
    
            this(DEFAULT_RESOURCE);
    
        }
    
        /**
         * 
         * 使用指定的属性资源来构造一个新的字符集
         *
         * @param name
         *            要被加载属性资源的完全限定名
         *
         * @exception IllegalArgumentException
         *                如果这个指定的属性资源因为任何原因不能被加载的话
         */
        public CharsetMapper(String name) {
    
            try {
                InputStream stream = this.getClass().getResourceAsStream(name);
                map.load(stream);
                stream.close();
            } catch (Throwable t) {
                throw new IllegalArgumentException(t.toString());
            }
    
        }
    
        // ---------------------------------------------------- Instance Variables
    
        /**
         * 已经从默认的资源属性或者指定资源 加载了资源并且初始化了的字符集合
         */
        private Properties map = new Properties();
    
        // ------------------------------------------------------- Public Methods
    
        /**
         * 根据指定的区域设置获取字符集编码
         *
         * @param locale
         *            用于计算字符集的区域设置
         */
        public String getCharset(Locale locale) {
    
            String charset = null;
    
            // 首先, 尝试全名匹配(语言和国家)
            charset = map.getProperty(locale.toString());
            if (charset != null)
                return (charset);
    
            // 其次, 尝试语言匹配
            charset = map.getProperty(locale.getLanguage());
            return (charset);
    
        }
    
    }

    看下 CharsetMapperDefault.properties的内容

    ar=ISO-8859-6
    be=ISO-8859-5
    bg=ISO-8859-5
    ca=ISO-8859-1
    cs=ISO-8859-2
    da=ISO-8859-1
    de=ISO-8859-1
    el=ISO-8859-7
    en=ISO-8859-1
    es=ISO-8859-1
    et=ISO-8859-1
    fi=ISO-8859-1
    fr=ISO-8859-1
    hr=ISO-8859-2
    hu=ISO-8859-2
    is=ISO-8859-1
    it=ISO-8859-1
    iw=ISO-8859-8
    ja=Shift_JIS
    ko=EUC-KR
    lt=ISO-8859-2
    lv=ISO-8859-2
    mk=ISO-8859-5
    nl=ISO-8859-1
    no=ISO-8859-1
    pl=ISO-8859-2
    pt=ISO-8859-1
    ro=ISO-8859-2
    ru=ISO-8859-5
    sh=ISO-8859-5
    sk=ISO-8859-2
    sl=ISO-8859-2
    sq=ISO-8859-2
    sr=ISO-8859-5
    sv=ISO-8859-1
    tr=ISO-8859-9
    uk=ISO-8859-5
    zh=GB2312
    zh_TW=Big5
    • 启动StandContext的相互关联的组件
    • 启动该StandardContext拥有的所有生命周期子容器
    • 启动管道对象
    • 启动生命周期STRART事件ContextConfig对象就是在这里为StandardContext对象进行配置
    • 启动设置好的Session管理器
    • 检查configured标志是否为true也就是检查ContextConfig配置StandardContext对象是否成功若不成功 会导致 不会讲available标志重新赋值为true导致启动失败 该StandardContext不可用
    • 触发AFTERSTART事件

    invoke方法

      在Tomcat4中,StandardContext类的invoke方法由其相关联的连接器调用,或者当StandardContext类实例是Host容器的一个子容器时,由HOST实例的invoke方法调用,StandardContext类的invoke方法首先会检查应用程序是否正在重载过程中,若是

    则等待应用程序重载完成,然后,它将调用其父类ContainBase的invoke方法

        /**
         * 
         * 根据特定容器的设计,处理指定的请求,并生成相应的响应。
         * 
         * @param request
         *            将被处理的请求
         * @param response
         *            生产的响应
         *
         * @exception IOException
         *                if an input/output error occurred while processing
         * @exception ServletException
         *                if a ServletException was thrown while processing this
         *                request
         */
        public void invoke(Request request, Response response) throws IOException, ServletException {
    
            // 检查当前StandardContext实例是否在重载 若是 则等待一段之后重新检查 一直到 重载之后 才会进行以后的操作
            while (getPaused()) {
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    ;
                }
            }
    
            // Normal request processing
            if (swallowOutput) {
                try {
                    SystemLogHandler.startCapture();
                    super.invoke(request, response);
                } finally {
                    String log = SystemLogHandler.stopCapture();
                    if (log != null && log.length() > 0) {
                        log(log);
                    }
                }
            } else {
                // 调用其父类的invoke方法 其实就是开始调用管道的的invoke方法了
                super.invoke(request, response);
            }
    
        }

    在Tomcat5中,StandardContext类并没有提供invoke方法的实现 。因此会执行其父类ContainerBase类的invoke方法,检查应用程序是否在重载的工作就移动到了 ContainerBase的invoke方法中

    StandardContextMapper类

      对于每一个引入的HTTP请求,都会调用StandardContext实例的管道对象的基础阀的invoke方法来处理,StandardContex实例(就是StandardContext实例的管道对象)的基础阀 是org.apache.catalina.core.StandardContextValue类的实例,

    StandardContextValue类的invoke方法要做的第一件事就是获取一个要处理HTTP请求的Wrapper实例。

      在Tomcat4中,StandardContextValue实例在它包含的属性StandardContext中查找,StandardContextValue实例会使用StandardContext实例的映射器找到一个合适的Wrapper实例。获得了对应的Wrapper实例之后,他就会调用Wrapper实例的invoke方法,在深入无挖掘StandardContextValue类的工作原理之前,先介绍一些映射器组件。

      ContainerBase类是StandardContext类的父类,前者定义了一个addDefaultMapper()方法用来添加一个默认的映射器。

        /**
         * 如果没有显式配置,则添加默认Mapper实现
         *
         * @param mapperClass
         *            Mapper实现的java完全限定类名
         */
        protected void addDefaultMapper(String mapperClass) {
    
            //  若限定名为null 则证明我们不需要映射器 直接返回
            if (mapperClass == null)
                return;
            //如果已经存在了mapper 则也直接返回
            if (mappers.size() >= 1)
                return;
    
            // 根据指定的限定名 初始化并添加一个 映射器默
            try {
                Class clazz = Class.forName(mapperClass);
                Mapper mapper = (Mapper) clazz.newInstance();
                //固定http协议
                mapper.setProtocol("http");
                addMapper(mapper);
            } catch (Exception e) {
                log(sm.getString("containerBase.addDefaultMapper", mapperClass), e);
            }
    
        }

    StandardContext类在其start方法中调用 addDefaultMapper方法,并传入变量mapperClass的值;

    public synchronized void start() throws LifecycleException {
    /***/
    if (ok) {
    
                try {
    
                    addDefaultMapper(this.mapperClass);
    }

    所以通过设置StandardContext的mapperClass的值就可以控制 使用自定义的映射器。

    那么StandardContext 的mapperClass的默认值如下;

        /**
         * 
         * 与该容器相关联的默认映射器对象的完全限定名
         */
        private String mapperClass = "org.apache.catalina.core.StandardContextMapper";

      必须要调用映射器的setContainer方法,通过传入一个容器的实例,将映射器和容器相关联,在Catalina中,org.apache.catalina.Mapper接口的实现类是org,apache.catalina.core.StandardContextMapper类。StandardContextMapper实例只能与Context级别容器相互关联,看下其setContaliner方法

    /**
         *
         * 设置一个与该映射器相关联的容器
         *
         * @param container
         *            关联的新容器
         *
         * @exception IllegalArgumentException
         *                如果这个容器不是一个StandardContext实现则抛出错误
         */
        public void setContainer(Container container) {
    
            if (!(container instanceof StandardContext))
                throw new IllegalArgumentException(sm.getString("httpContextMapper.container"));
            context = (StandardContext) container;
    
        }

    映射器中最重要的就是map方法,该方法返回用来处理HTTP请求的子容器。方法签名如下

     public Container map(Request request, boolean update);

    在StandardContextMapper类中,map方法返回一个Wrapper实例,用于处理请求,若找不到适合的Wrapper实例,则返回null

      对于引入的每一个http请求,StandardContextValue实例调用Context容器的map方法,并传入一个org.apache.catalina.Request对象,map方法(实际上是定义在其父类ContainerBase类中的)会针对某个特定的协议调用findMapper方法返回一个映射器对象,然后调用映射器对象的map方法获取Wrapper实例。

    /**
         * 
         * 根据请求的特性,返回应该用于处理该请求的子容器。如果无法标识此类子容器,则返回<code>null</code>。
         * 
         * @param request
         *            Request being processed
         * @param update
         *            Update the Request to reflect the mapping selection?
         */
        public Container map(Request request, boolean update) {
    
            // 根据指定请求的协议返回一个Mapper 
            Mapper mapper = findMapper(request.getRequest().getProtocol());
            if (mapper == null)
                return (null);
    
            // 使用这个Mapper获取一个处理该请求的Wrapper实例
            return (mapper.map(request, update));
    
        }

    那么在看下StandardContextMapper类的map方法实现

    /**
         * 
         * 根据指的request 从 StandardContext对象的子容器中 找到 匹配的 Wrapper容器,若无则返回null
         * 
         * @param request
         *            要被处理的request
         * @param update
         *            是否更新request中的Wrapper
         *
         * @exception IllegalArgumentException
         *                如果路径的相对部分不能被URL解码
         */
        public Container map(Request request, boolean update) {
    
    
            int debug = context.getDebug();
    
            // 这个请求已经被映射了一个wrapper对象了么?
            if (update && (request.getWrapper() != null))
                return (request.getWrapper());
    
            //先获取到相对于Context的URI 就是将请求的整个URI截掉Context的URI 后剩下的URI,
            String contextPath =
                ((HttpServletRequest) request.getRequest()).getContextPath();
            String requestURI = ((HttpRequest) request).getDecodedRequestURI();
            String relativeURI = requestURI.substring(contextPath.length());
    
    
            if (debug >= 1)
                context.log("Mapping contextPath='" + contextPath +
                            "' with requestURI='" + requestURI +
                            "' and relativeURI='" + relativeURI + "'");
    
            // 应用规范中的标准请求URI映射规则
            Wrapper wrapper = null;
            String servletPath = relativeURI;
            String pathInfo = null;
            String name = null;
    
            // 规则 1 -- 精确匹配
            if (wrapper == null) {
                if (debug >= 2)
                    context.log("  Trying exact match(试着精确匹配)");
                if (!(relativeURI.equals("/")))
                    //根据相对于Context的URI 从Context容器的serveletMapping集合中找到对应wrapper的名字
                    name = context.findServletMapping(relativeURI);
                if (name != null)
                    //如果扎到了名字 则利用Context的 findChild方法 从其子容器中根据名字 找到对应wrapper
                    wrapper = (Wrapper) context.findChild(name);
                if (wrapper != null) {
                    servletPath = relativeURI;
                    pathInfo = null;
                }
            }
    
            // 规则 2 -- 前缀匹配
            if (wrapper == null) {
                if (debug >= 2)
                    context.log("  Trying prefix match(试着前缀匹配)");
                servletPath = relativeURI;
                while (true) {
                    //前缀匹配 就是 把 相对Context的URI 作为前缀 后面加上/*看能不能找到name
                    name = context.findServletMapping(servletPath + "/*");
                    if (name != null)
                        wrapper = (Wrapper) context.findChild(name);
                    if (wrapper != null) {
                        pathInfo = relativeURI.substring(servletPath.length());
                        if (pathInfo.length() == 0)
                            pathInfo = null;
                        break;
                    }
                    int slash = servletPath.lastIndexOf('/');
                    if (slash < 0)
                        break;
                    //逐一减掉最后的/之后的URI
                    servletPath = servletPath.substring(0, slash);
                }
            }
    
            // Rule 3 -- 扩展匹配
            if (wrapper == null) {
                if (debug >= 2)
                    context.log("  Trying extension match(试着扩展匹配)");
                //最后一个斜杠的位置
                int slash = relativeURI.lastIndexOf('/');
                //如果存在一个斜杠
                if (slash >= 0) {
                    //截取最后一个斜杠之后的URI
                    String last = relativeURI.substring(slash);
                    //斜杠之后URI中最后一个.的位置
                    int period = last.lastIndexOf('.');
                    //如果斜杠之后URI存在 .
                    if (period >= 0) {
                        //匹配字符串 = * + 斜杠URI 最后一个.之后的URI
                        String pattern = "*" + last.substring(period);
                        //根据 扩展匹配规则 寻找name
                        name = context.findServletMapping(pattern);
                        if (name != null)
                            wrapper = (Wrapper) context.findChild(name);
                        if (wrapper != null) {
                            servletPath = relativeURI;
                            pathInfo = null;
                        }
                    }
                }
            }
    
            // 规则 4 -- 默认匹配规则
            if (wrapper == null) {
                if (debug >= 2)
                    context.log("  Trying default match(试着默认匹配)");
                //直接斜杠匹配
                name = context.findServletMapping("/");
                if (name != null)
                    wrapper = (Wrapper) context.findChild(name);
                if (wrapper != null) {
                    servletPath = relativeURI;
                    pathInfo = null;
                }
            }
    
            // 更新请求中的Wrapper(如果请求 update为true ),然后返回此包装器
            if ((debug >= 1) && (wrapper != null))
                context.log(" Mapped to servlet '" + wrapper.getName() +
                            "' with servlet path '" + servletPath +
                            "' and path info '" + pathInfo +
                            "' and update=" + update);
            //如果需要更新则 将新匹配到的wrpper 更新到request中
            if (update) {
                request.setWrapper(wrapper);
                ((HttpRequest) request).setServletPath(servletPath);
                ((HttpRequest) request).setPathInfo(pathInfo);
            }
            return (wrapper);
    
        }

    也许会有疑问 但是目前本人还是得很清楚关于Context实例是如何得到这些信息用来映射servlet的。Context的addServletMapping方法。

            context.addServletMapping("/Primitive", "Primitive");
            context.addServletMapping("/Modern", "Modern");

    在Tomcat5 中,Mapper接口以及其相关类已经被移除了,事实上,StandardContextValue类的invoke方法会从request对象中获取适合的Wrapper实例:

    对重载的支持

      StandardContext类定义了 reloadable属性来指明该应用程序是否启用了重载功能,当启用了重载功能的时候,当web.xml文件发生变化或者WEB-INF/classes目录下的其中一个文件被重新编译后,应用程序会重载。

    StandardContext类时通过载入器实现应用程序重载的,在Tomcat4中,StandardContext对象中的WebappLoader类实现了Loader接口,并使用了一个线程检查WEB-INF目录中的所有类和jar文件的时间戳。只需要调用其setContainer方法将WebappLoader对象与StandardContext对象相关联 就可以启动该检查线程,下面是Tomcat4中的WebappLoader类的setContainer方法的实现代码 

     1 /**
     2      * 
     3      * 
     4      * <p>
     5      * Title: setContainer
     6      * </p>
     7      * 
     8      * @date 2018年11月17日 下午8:46:22
     9      * 
    10      *       <p>
    11      *       功能描述: 设置与该加载器关联的 {@link Container}容器
    12      *       </p>
    13      * 
    14      * @param container
    15      */
    16     public void setContainer(Container container) {
    17 
    18         //如果当前组件存在关联容器 且 关联的容器 为 Context 实例,则将该监听器 从 旧容器中移除
    19         if ((this.container != null) && (this.container instanceof Context))
    20             ((Context) this.container).removePropertyChangeListener(this);
    21 
    22         // 处理这个 container 值 更改 所触发的事件(监听当前WebappLoader的 属性 改变事件监听器)
    23         Container oldContainer = this.container;
    24         this.container = container;
    25         // 触发监听 container 属性值改变 事件
    26         support.firePropertyChange("container", oldContainer, this.container);
    27 
    28         // 如果当前container不为空 且 container是Context的实例 则 调用setRealoadable方法 将 Context中的realoadable的值设置当该对象中
    29         if ((this.container != null) && (this.container instanceof Context)) {
    30             setReloadable(((Context) this.container).getReloadable());
    31             ((Context) this.container).addPropertyChangeListener(this);
    32         }
    33 
    34     }

    注意最后一个if控制块如果 传入的Container容器是一个 Context 容器的话 就会同步Context容器中的readloader属性到该加载器中。下面看下 setRealoaderable方法

    /**
         * 为当前 loader 设置 是否自动重载的标志
         *
         * @param reloadable
         *            新的自动重载标志
         */
        public void setReloadable(boolean reloadable) {
    
            // 通知  监听自动重载值变换的监听器  
            boolean oldReloadable = this.reloadable;
            this.reloadable = reloadable;
            support.firePropertyChange("reloadable", new Boolean(oldReloadable), new Boolean(this.reloadable));
    
            // 根据 reloadable 的值 来决定是否需要 启动 或者 停止 当前的 后台线程(是为了自动重载 周期性检查 已经载入的类 是否发生变化)
            if (!started)
                return;
            //如果旧的自动重载标志为 不支持自动重载 且 新的自动重载标志 为 支持自动重载 则 启动 后台线程 进行周期性检查 已经载入的类 是否发生变化
            if (!oldReloadable && this.reloadable)
                threadStart();
            //如果旧的自动重载标志位 支持自动重载 且新的自动重载标志 为 不支持自动重载。则停止后台线程的 的周期性检查
            else if (oldReloadable && !this.reloadable)
                threadStop();
    
        }

      若reaoladable属性的值从false修改为true,则会调用threadStart方法;若reaoladable属性从true修改为false,则会调用threadStop方法 ,threadStart方法启动一个专门用来不断检查WEB-INF/classes目录下的类 和  JAR文件的时间戳,而threadStop方法则会终止检查线程,

      在Tomcat5中,为支持StandardContext重载而进行的检查 类 和jar文件的时间戳的工作改为由backgroundprocess方法执行。

    backgroundProcess方法

      Context容器的运行需要其他组件的支持,例如载入器 和 session管理器,通常来说,这件组件需要使用各自的线程来执行一些后台处理任务,例如。为了支持自动重载,载入器需要使用一个线程来周期性的检查WEB-INF/clasees目录下的java类和 jar文件的时间戳;

    Session管理器使用一个线程来周期性的检查它所管理的所有Session对象的过期时间,在Tomcat4中,这些组件最终拥有各自的线程。

      为了节省资源,在Tomcat5中,使用了不同的方法,所有的后台处理共享一个线程,若某个组件或servlet容器需要周期性的执行一个操作,只需要将代码写到其backgroundProcess方法中即可。

      这个共享线程在ContainerBase对象中创建,ContainerBase类在其start方法中(即当该容器启动时)调用其threadStart方法启动该后台线程,

    /**
         * 后台线程是否已经关闭
         */
        protected boolean threadDone = true;
                
        /**
         * 后台线程周期间隔 事件 秒为单位
         */
        protected int backgroundProcessorDelay;
        
        public int getBackgroundProcessorDelay() {
            return this.backgroundProcessorDelay;
        }
        
        
        /**
         * 
         * <p>
         * <b>Title:ContainerBase.java</b>
         * </p>
         * <p>
         * Copyright:ChenDong 2018
         * </p>
         * <p>
         * Company:仅学习时使用
         * </p>
         * <p>
         * 类功能描述:Context容器的运行需要其他组件的支持,例如载入器,Session管理器。通常来说这些组件需要使用各自的线程执行一些
         * 自己的后台任务,例如,载入器为了 实现自动重载,载入器需要使用一个线程来周期性的检查WEb-INF/class目录下的所有类 和
         * JAR文件的时间戳,Session管理器需要使用一个线程来周期性的检查它所管理的
         * Session对象的过期时间,在Tomcat4中这些任务都是由组件自己拥有的线程来执行。
         * 但是为了节省资源,在Tomcat5中,所有的后台处理共享同一个线程,若某个组件
         * 或者servlet容器需要周期性的执行一个操作,只需要将代码写到其backgroundProcess方法中 即可。下面就是 该共享线程的实现
         * </p>
         * 
         * @author 陈东
         * @date 2018年12月15日 下午3:49:18
         * @version 1.0
         */
        protected class ContainerBackgroundProcessor implements Runnable{
    
            
            /**  
            
             * <p>Title: run</p>  
            
             * @date 2018年12月15日 下午3:48:54
            
             * <p>功能描述: </p>  
              
            
             */ 
            @Override
            public void run() {
                //若后台线程关闭标志 为 未关闭
                while(!threadDone){
    
                    try {
                        Thread.sleep(backgroundProcessorDelay * 1000L);
                    } catch (InterruptedException e) {
                        ;
                    }
                
                    if(!threadDone){
                        Container parent = getParent();//tomcat4中暂且没有getMappingObject
                        ClassLoader cl = Thread.currentThread().getContextClassLoader();
                        
                        if(parent.getLoader()!=null)
                            cl = parent.getLoader().getClassLoader();
                         processChildren(parent, cl);
                        
                        
                    }
                    
                }
                
            }
            
            protected void processChildren(Container container, ClassLoader cl) {
                try {
                    if (container.getLoader() != null) {
                        Thread.currentThread().setContextClassLoader(container.getLoader().getClassLoader());
                    }
                    container.backgroundProcess();
    
                } catch (Throwable e) {
                    log("调用周期性操作的异常", e);
                } finally {
                    Thread.currentThread().setContextClassLoader(cl);
                }
                //执行每个子容器的 backgroundProcess
                Container[] children = container.findChildren();
                
                for (int i = 0; i < children.length; i++) {
                    if (children[i].getBackgroundProcessorDelay() <= 0) {
                        processChildren(children[i], cl);
                    }
                }
    
            }
            
        }
        

    ContainerBackgroundProcessor类实际上是ContainerBase类的内部类,在其run方法中是一个while循环,周期性的调用 其processChildren方法。而processChildren方法会调用自身对象的backgroundProcess方法和 其每一个子容器的processChildren方法,通过实现

    backgroundProcess方法,ContainerBase类的子类可以使用一个专用线程来执行周期性任务,例如检查类的时间戳 或者session对象的超时时间,下面看下Tomcat5中 StandardContext中backgroundProcess方法的实现;

     1 /**  
     2     
     3      * <p>Title: backgroundProcess</p>  
     4     
     5      * @date 2018年12月15日 下午5:56:54
     6     
     7      * <p>功能描述: </p>  
     8       
     9     
    10      */ 
    11     @Override
    12     public void backgroundProcess() {
    13         if(!started)
    14             return;
    15         count = (count + 1) % managerChecksFrequency;
    16         if(getManager()!=null&& count==0){
    17             
    18             try{
    19                 //启动session管理器的 backgroundProcess方法
    20                 getManager().backgroundProcess();
    21             }catch(Exception e){
    22                 
    23             }
    24             
    25         }
    26         
    27         if (getLoader() != null) {
    28             if (reloadable && getLoader().modified()) {
    29                 try {
    30                     Thread.currentThread().setContextClassLoader(StandardContext.class.getClassLoader());
    31                     //启动重载方法
    32                     reload();
    33                 } finally {
    34                     Thread.currentThread().setContextClassLoader(getLoader().getClassLoader());
    35                 }
    36             }
    37 
    38         }
    39 
    40         if (getLoader() instanceof WebappLoader) {
    41             ((WebappLoader) getLoader()).closeJARs(false);
    42         }
    43         
    44     }
  • 相关阅读:
    Android开发之深入理解NFC(一)
    NetBeans找不到C/C++编译器
    【图解HTTP】第二章 简单的http协议
    长时间停留在calculating requirements and dependencies
    【图解HTTP】第一章 了解web及网络基础
    自定义DropDownMenu菜单
    【Android开发精要笔记】Android的Intent机制
    【操作系统】进程管理
    【Head First Java 读书笔记】(七)继承
    网易电面题总结
  • 原文地址:https://www.cnblogs.com/ChenD/p/10061008.html
Copyright © 2011-2022 走看看