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     }
  • 相关阅读:
    vue中的 computed 和 watch 的区别
    mysql8.0 初始化数据库及表名大小写问题
    sql server alwayson 调整数据文件路径
    zabbix 自定义监控 SQL Server
    mysql 创建用户及授权
    mysql 设置从库只读模式
    mysql8.0 主从复制安装及配置
    centos8.0安装mysql8.0
    centos8替换阿里数据源
    npm publish 报错 【you or one of your dependencies are requesting a package version that is forbidden by your security policy】
  • 原文地址:https://www.cnblogs.com/ChenD/p/10061008.html
Copyright © 2011-2022 走看看