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     }

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM