Tomcat中的Host和Engine級別的servlet容器


 這邊文章主要介紹的是Host容器 和 Engine容器。如果你想在同一個Tomcat上部署運行多個Context容器的話,你就需要使用Host容器,從理論上來講,如果你的Tomcat只想要部署一個Context容器的話,你可以不使用Host容器。

在org.apache.catalina.Context接口的描述有下一段話:

  Context容器的父容器通常是Host容器,也有可能是其他實現,或者如果不是必要的話,就可以不使用父容器。

  但是 在tomcat的實際部署中,總會使用一個Host容器,在下面在解釋原因,

  Engine容器表示Catalina的整個Servlet引擎,如果使用了Engine容器,那么它總是處於容器層級的最頂層,添加到Enginer容器中的子容器通常是org.apache.catalina.Host 或者 org.apahce.catalina.Context的實現,默認情況下Tomcat會使用一個Engine容器並且使用一個Host容器作為其子容器,

Host接口 

  host容器是 org.apahce.catalina.Host接口的實例,Host接口繼承自Container接口

package org.apache.catalina;

/**
 * 
 * <p>
 * <b>Title:Host.java</b>
 * </p>
 * <p>
 * Copyright:ChenDong 2018
 * </p>
 * <p>
 * Company:僅學習時使用
 * </p>
 * <p>
 * 類功能描述:Host是表示Catalina servlet引擎中的虛擬主機的容器。它在以下類型的場景中很有用:
 * 
 * 
 * 
 * 您希望使用攔截器來查看此特定虛擬主機處理的每個請求。
 * 
 * 您希望使用獨立的HTTP連接器運行Catalina,但是仍然希望支持多個虛擬主機。
 * 
 * 通常,在部署連接到Web服務器(如Apache)的Catalina時,您不會使用主機,因為連接器將利用Web服務器的設施來確定應該使用哪個上下文(
 * 或者甚至哪個包裝器)來處理這個請求。
 * 
 * 附加到主機的父容器通常是一個引擎,但是可以是一些其他的實現,或者如果不必要的話可以省略。
 * 
 * 
 * 
 * 附加到主機的子容器通常是上下文的實現(表示單個servlet上下文)。
 * </p>
 * 
 * @author 
 * @date 2018年12月15日 下午9:28:58
 * @version 1.0
 */
public interface Host extends Container {

    // ----------------------------------------------------- Manifest Constants

    /**
     *
     * 
     * 當使用<code>addAlias()</code>方法添加新的別名時發送的 {@code ContainerEvent}事件類型。
     */
    public static final String ADD_ALIAS_EVENT = "addAlias";

    /**
     * 當使用<code>removeAlias()</code>移除一個舊的別名時 觸發的 {@code ContainerEvent}事件類型
     */
    public static final String REMOVE_ALIAS_EVENT = "removeAlias";

    // ------------------------------------------------------------- Properties

    /**
     * 返回此{@code Host}容器的 根路徑,它可以是 絕對路徑、相對路徑、或者URL
     */
    public String getAppBase();

    /**
     * 
     * 為這個{@code Host}容器 設置一個根路徑,它可以是 絕對路徑、相對路徑、或者URL
     *
     * @param appBase
     *            新的容器根路徑
     */
    public void setAppBase(String appBase);

    /**
     * Return the value of the auto deploy flag. If true, it indicates that this
     * host's child webapps should be discovred and automatically deployed.
     */
    public boolean getAutoDeploy();

    /**
     * Set the auto deploy flag value for this host.
     * 
     * @param autoDeploy
     *            The new auto deploy flag
     */
    public void setAutoDeploy(boolean autoDeploy);

    /**
     * 
     * 為新的web應用程序設置 {@code DefaultContext}。
     * 
     * @param defaultContext
     *            新的 DefaultContext
     */
    public void addDefaultContext(DefaultContext defaultContext);

    /**
     * 為新的web應用程序檢索 並返回 DefaultContext.
     */
    public DefaultContext getDefaultContext();

    /**
     * 返回此容器表示的虛擬主機的規范、完全限定的名稱
     */
    public String getName();

    /**
     * 設置此容器表示的虛擬主機的規范、完全限定的名稱
     *
     * @param name
     *            虛擬主機的名稱
     *
     * @exception IllegalArgumentException
     *                如果這個名字是 {@code null}
     */
    public void setName(String name);

    // --------------------------------------------------------- Public Methods

    /**
     * 
     * 將DefaultContext 的 config 導入到web應用程序上下文中。
     * 
     * @param context
     *            導入默認Context的web應用程序Context
     */
    public void importDefaultContext(Context context);

    /**
     * 添加應該映射到同一主機的別名
     *
     * @param alias
     *            要被添加的別名
     */
    public void addAlias(String alias);

    /**
     * 
     * 返回此主機的別名集。如果沒有定義,則返回一個零長度數組
     */
    public String[] findAliases();

    /**
     * 
     * 返回一個用來處理引用Http請求的 Context 根據 請求的URI 若果不存在則返回
     *
     * @param uri
     *            Request URI to be mapped
     */
    public Context map(String uri);

    /**
     * 從此主機的別名中刪除指定的別名
     *
     * @param alias
     *            要被刪除的別名
     */
    public void removeAlias(String alias);

}

下面說下它在Tomat中的標准實現

StandardHost類

  在Catalina中的  org.apache.catalina.core.StandardHost類 是 org.apache.catalin.Host接口的標准實現,該類繼承自 org.apache.catalina.core.ContainerBase類 ,實現了 Host 和 Deployer接口。

與StandardContext 和 StandardWrapper 類 相似,StandardHost類的構造器函數會將一個基礎閥的實例 添加到其管道對相中。

    /**
     * 
     * 創建一個帶有基礎閥的 {@code  StandardHost}實例
     */
    public StandardHost() {

        super();
        pipeline.setBasic(new StandardHostValve());

    }

那么 它的基礎閥 就是 org.apahce.catalina.core.StandardHostValue類的實例,

  當調用 StandardHost 類的 start()方法時,StandardHost實例 會新添加兩個閥,分別是 ErrorReportValue類 和 ErrorDispatcherValue類的實例,這個兩個閥均位於org.apahce.catalina.values包下,

 1 /**
 2      * 啟動這個Host.
 3      *
 4      * @exception LifecycleException
 5      *                如果此組件檢測到阻止其啟動的致命錯誤
 6      * 
 7      */
 8     public synchronized void start() throws LifecycleException {
 9         // 如果 errorReportValveClass 閥的 完全限定名 不為空 的話 
10         if ((errorReportValveClass != null) && (!errorReportValveClass.equals(""))) {
11             try {
12                 Valve valve = (Valve) Class.forName(errorReportValveClass).newInstance();
13                 //添加這個ErrorReportValve閥
14                 addValve(valve);
15             } catch (Throwable t) {
16                 log(sm.getString("standardHost.invalidErrorReportValveClass", errorReportValveClass));
17             }
18         }
19 
20         //添加一個ErrorDispatcherValve閥
21         addValve(new ErrorDispatcherValve());
22 
23         super.start();
24 
25     }

變量 errorReportValueClass的值 定義在StandardHost類中;

private String errorReportValveClass = "org.apache.catalina.valves.ErrorReportValve";

  每當引入一個Http請求的時候,都會調用StandardHost實例的 invoke方法,由於StandardHost類並沒有提供invoke方法的實現,因此它會調用父類 ContainerBase 類的 invoke方法,而ContainerBase類 的invoke方法將會調用StandardHost類的 基礎閥StandardHostValue實例的invoke方法,StandardHostValue的invoke方法將會調用StandardHost類的map方法來獲取響應的Context實例來處理Http請求。

 1 /**
 2      * 
 3      * 返回一個Context實例 來處理這個 相對於Host容器的 相對URI所代表的請求,如果沒有則返回 <code>null</code>
 4      *
 5      * @param uri
 6      *            要被映射的請求URI
 7      */
 8     public Context map(String uri) {
 9 
10         if (debug > 0)
11             log("Mapping request URI '" + uri + "'");
12         if (uri == null)
13             return (null);
14 
15         // Match on the longest possible context path prefix
16         // 匹配可能是最長的Context路徑前綴
17         if (debug > 1)
18             log("  Trying the longest context path prefix");
19         Context context = null;
20         String mapuri = uri;
21         while (true) {
22             // 不斷嘗試根據路徑去子容器中找對應的Context
23             context = (Context) findChild(mapuri);
24             if (context != null)
25                 break;
26             int slash = mapuri.lastIndexOf('/');
27             if (slash < 0)
28                 break;
29             // 不斷截取路徑最后一個/之前的路徑 做匹配路徑
30             mapuri = mapuri.substring(0, slash);
31         }
32 
33         
34         // 如果沒有匹配到Context 則選擇 默認的Context
35         if (context == null) {
36             if (debug > 1)
37                 log("  Trying the default context");
38             context = (Context) findChild("");
39         }
40 
41         //如果還是沒有選中的 Context 直接返回null 並返回 錯誤信息
42         if (context == null) {
43             log(sm.getString("standardHost.mappingError", uri));
44             return (null);
45         }
46 
47         // 返回映射的上下文(如果有的話)
48         if (debug > 0)
49             log(" Mapped to context '" + context.getPath() + "'");
50         return (context);
51 
52     }

在Tomcat 5 和之后的版本中,映射器組件已經移除,Context實例是通過request對象獲取。

 

 

StandardHostMapper類

   在Tomcat4 中,ContainerBase類 (也就是StandardHost的父類),會調用其addDefaultMapper()方法創建一個默認的映射器,默認的映射器的類型 mapperClass屬性的值決定,下面是ContainerBase類的addDefaultMapper實現

 1 /**
 2      * 如果沒有顯式配置,則添加默認Mapper實現
 3      *
 4      * @param mapperClass
 5      *            Mapper實現的java完全限定類名
 6      */
 7     protected void addDefaultMapper(String mapperClass) {
 8 
 9         //  若限定名為null 則證明我們不需要映射器 直接返回
10         if (mapperClass == null)
11             return;
12         //如果已經存在了mapper 則也直接返回
13         if (mappers.size() >= 1)
14             return;
15 
16         // 根據指定的限定名 初始化並添加一個 映射器默
17         try {
18             Class clazz = Class.forName(mapperClass);
19             Mapper mapper = (Mapper) clazz.newInstance();
20             //固定http協議
21             mapper.setProtocol("http");
22             addMapper(mapper);
23         } catch (Exception e) {
24             log(sm.getString("containerBase.addDefaultMapper", mapperClass), e);
25         }
26 
27     }

變量 mapperClass的值定義在StandardHost類中;

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

Standardhost類的start方法 在方法的默認會調用父類的start方法確保默認映射器的創建完成。

注意:學習到這里的朋友 也可能會和我有同樣的 疑問 StandardContext 在start方法的結尾難道也調用了 其父類ContainerBase的start方法了么,答案是不是的,在Tomcat4中,StandardContext類創建默認映射器的方法略有不同,它的start方法並不會調用其父類的Start方法,StandardContext類的Start方法會自己調用addDefaultMapper方法 來創建默認的映射器。

當然StandardHostMapper類中最重要的方法 還是map方法,這個map方法 與StandardContextMapper類的map方法相比就要簡單的多的多了

StandardHostMapper類的map方法展示

 1 public Container map(Request request, boolean update) {
 2         // 如果這個request已經獲得了映射的Context對象則直接返回
 3         if (update && (request.getContext() != null))
 4             return (request.getContext());
 5 
 6         // 對我們的請求URI執行映射
 7         String uri = ((HttpRequest) request).getDecodedRequestURI();
 8         //還調用host的map 可見中重點邏輯在於host的map方法
 9         Context context = host.map(uri);
10 
11         //需要更新請求中的映射Context對象么 並返回所選的上下文
12         if (update) {
13             request.setContext(context);
14             if (context != null)
15                 ((HttpRequest) request).setContextPath(context.getPath());
16             else
17                 ((HttpRequest) request).setContextPath(null);
18         }
19         return (context);
20 
21     }

在來一個StandardContextMapper對象的map方法 做一下對比

  1 /**
  2      * 
  3      * 根據指的request 從 StandardContext對象的子容器中 找到 匹配的 Wrapper容器,若無則返回null
  4      * 
  5      * @param request
  6      *            要被處理的request
  7      * @param update
  8      *            是否更新request中的Wrapper
  9      *
 10      * @exception IllegalArgumentException
 11      *                如果路徑的相對部分不能被URL解碼
 12      */
 13     public Container map(Request request, boolean update) {
 14 
 15 
 16         int debug = context.getDebug();
 17 
 18         // 這個請求已經被映射了一個wrapper對象了么?
 19         if (update && (request.getWrapper() != null))
 20             return (request.getWrapper());
 21 
 22         //先獲取到相對於Context的URI 就是將請求的整個URI截掉Context的URI 后剩下的URI,
 23         String contextPath =
 24             ((HttpServletRequest) request.getRequest()).getContextPath();
 25         String requestURI = ((HttpRequest) request).getDecodedRequestURI();
 26         String relativeURI = requestURI.substring(contextPath.length());
 27 
 28 
 29         if (debug >= 1)
 30             context.log("Mapping contextPath='" + contextPath +
 31                         "' with requestURI='" + requestURI +
 32                         "' and relativeURI='" + relativeURI + "'");
 33 
 34         // 應用規范中的標准請求URI映射規則
 35         Wrapper wrapper = null;
 36         String servletPath = relativeURI;
 37         String pathInfo = null;
 38         String name = null;
 39 
 40         // 規則 1 -- 精確匹配
 41         if (wrapper == null) {
 42             if (debug >= 2)
 43                 context.log("  Trying exact match(試着精確匹配)");
 44             if (!(relativeURI.equals("/")))
 45                 //根據相對於Context的URI 從Context容器的serveletMapping集合中找到對應wrapper的名字
 46                 name = context.findServletMapping(relativeURI);
 47             if (name != null)
 48                 //如果扎到了名字 則利用Context的 findChild方法 從其子容器中根據名字 找到對應wrapper
 49                 wrapper = (Wrapper) context.findChild(name);
 50             if (wrapper != null) {
 51                 servletPath = relativeURI;
 52                 pathInfo = null;
 53             }
 54         }
 55 
 56         // 規則 2 -- 前綴匹配
 57         if (wrapper == null) {
 58             if (debug >= 2)
 59                 context.log("  Trying prefix match(試着前綴匹配)");
 60             servletPath = relativeURI;
 61             while (true) {
 62                 //前綴匹配 就是 把 相對Context的URI 作為前綴 后面加上/*看能不能找到name
 63                 name = context.findServletMapping(servletPath + "/*");
 64                 if (name != null)
 65                     wrapper = (Wrapper) context.findChild(name);
 66                 if (wrapper != null) {
 67                     pathInfo = relativeURI.substring(servletPath.length());
 68                     if (pathInfo.length() == 0)
 69                         pathInfo = null;
 70                     break;
 71                 }
 72                 int slash = servletPath.lastIndexOf('/');
 73                 if (slash < 0)
 74                     break;
 75                 //逐一減掉最后的/之后的URI
 76                 servletPath = servletPath.substring(0, slash);
 77             }
 78         }
 79 
 80         // Rule 3 -- 擴展匹配
 81         if (wrapper == null) {
 82             if (debug >= 2)
 83                 context.log("  Trying extension match(試着擴展匹配)");
 84             //最后一個斜杠的位置
 85             int slash = relativeURI.lastIndexOf('/');
 86             //如果存在一個斜杠
 87             if (slash >= 0) {
 88                 //截取最后一個斜杠之后的URI
 89                 String last = relativeURI.substring(slash);
 90                 //斜杠之后URI中最后一個.的位置
 91                 int period = last.lastIndexOf('.');
 92                 //如果斜杠之后URI存在 .
 93                 if (period >= 0) {
 94                     //匹配字符串 = * + 斜杠URI 最后一個.之后的URI
 95                     String pattern = "*" + last.substring(period);
 96                     //根據 擴展匹配規則 尋找name
 97                     name = context.findServletMapping(pattern);
 98                     if (name != null)
 99                         wrapper = (Wrapper) context.findChild(name);
100                     if (wrapper != null) {
101                         servletPath = relativeURI;
102                         pathInfo = null;
103                     }
104                 }
105             }
106         }
107 
108         // 規則 4 -- 默認匹配規則
109         if (wrapper == null) {
110             if (debug >= 2)
111                 context.log("  Trying default match(試着默認匹配)");
112             //直接斜杠匹配
113             name = context.findServletMapping("/");
114             if (name != null)
115                 wrapper = (Wrapper) context.findChild(name);
116             if (wrapper != null) {
117                 servletPath = relativeURI;
118                 pathInfo = null;
119             }
120         }
121 
122         // 更新請求中的Wrapper(如果請求 update為true ),然后返回此包裝器
123         if ((debug >= 1) && (wrapper != null))
124             context.log(" Mapped to servlet '" + wrapper.getName() +
125                         "' with servlet path '" + servletPath +
126                         "' and path info '" + pathInfo +
127                         "' and update=" + update);
128         //如果需要更新則 將新匹配到的wrpper 更新到request中
129         if (update) {
130             request.setWrapper(wrapper);
131             ((HttpRequest) request).setServletPath(servletPath);
132             ((HttpRequest) request).setPathInfo(pathInfo);
133         }
134         return (wrapper);
135 
136     }

可以看到host的映射器的map方法只是簡單的調用了 host的map方法而已。

StandardHostValue(StandardHost的基礎閥)

  org.apache.catalina.core.StandardHostValue類時StandardHost實例的基礎閥,當有引入的HTTp請求時,會調用StandardHost的involve方法 繼而 調用ContainerBase類的invoke方法 繼而在調用 其管道的invoke方法 繼而在調用 StandardHostValue的invoke方法

對其進行處理,

 public void invoke(Request request, Response response,
                       ValveContext valveContext)
        throws IOException, ServletException {

        // 驗證請求和響應對象類型是否有效
        if (!(request.getRequest() instanceof HttpServletRequest) ||
            !(response.getResponse() instanceof HttpServletResponse)) {
            return;     // NOTE - Not much else we can do generically
        }

        // 選擇一個Context來處理這個請求
        StandardHost host = (StandardHost) getContainer();
        Context context = (Context) host.map(request, true);
        if (context == null) {
            ((HttpServletResponse) response.getResponse()).sendError
                (HttpServletResponse.SC_INTERNAL_SERVER_ERROR,
                 sm.getString("standardHost.noContext"));
            return;
        }

      
        //將上Context的類加載器綁定到當前線程
        
        Thread.currentThread().setContextClassLoader
            (context.getLoader().getClassLoader());

        // 更新會話的最后訪問時間(如果有的話)
        HttpServletRequest hreq = (HttpServletRequest) request.getRequest();
        String sessionId = hreq.getRequestedSessionId();
        if (sessionId != null) {
            Manager manager = context.getManager();
            if (manager != null) {
                Session session = manager.findSession(sessionId);
                if ((session != null) && session.isValid())
                    session.access();
            }
        }

        // 請求此Context處理此請求
        context.invoke(request, response);

    }

注意:在獲取Context實例的時候有一個往復的過程,上面的map方法需要兩個參數,該方法定義在ContainerBase類中,ContainerBase類中的map方法會找到其子容器的映射器,在本例中是StandardHost實例,並調用映射器的 map方法,

然后invoke方法會獲取與該request對象相互關聯的session對象,並調用其access方法 access方法會修改session對象的最后訪問時間,下面是 org.apahce.catalina.session.StandardSession類中access方法的實現

1 public void access() {
2 
3         this.isNew = false;
4         this.lastAccessedTime = this.thisAccessedTime;
5         this.thisAccessedTime = System.currentTimeMillis();
6 
7     }

最后 invoke方法調用Context實例的invoke方法來處理HTTp請求。

 

  為什么必須要有一個Host容器

  在tomcat4 和 tomcat 5 中實際部署中,若一個Context實例使用ContextConfig對象進行配置,就必須要使用一個Host對象,原因如下:

  使用ContextConfig對象需要知道應用程序web.xml文件的位置,在其 applicationConfig方法中它會試圖打開web.xml文件,下面是 applicationConfig方法的片段;

    synchronized (webDigester) {
            try {
                URL url = servletContext.getResource(Constants.ApplicationWebXml);

                InputSource is = new InputSource(url.toExternalForm());
                is.setByteStream(stream);
                webDigester.setDebug(getDebug());
                if (context instanceof StandardContext) {
                    ((StandardContext) context).setReplaceWelcomeFiles(true);
                }
                webDigester.clear();
                webDigester.push(context);
                webDigester.parse(is);
            } catch (SAXParseException e) {

其中,Constants.ApplicationWebXml的值為

public static final String ApplicationWebXml = "/WEB-INF/web.xml";

web.xml文件的相對路徑,servletContext是一個 org.apache.catalina.core.ApplicationContext類型(實現了javax.servlet.servletContext接口)的對象;

下面是 ApplicationContext類的getResource方法的部分實現代碼

1 public URL getResource(String path)
2         throws MalformedURLException {
3 
4         DirContext resources = context.getResources();
5         if (resources != null) {
6             String fullPath = context.getName() + path;
7 
8             // this is the problem. Host must not be null
9             String hostName = context.getParent().getName();

注意最后一行到代碼 是需要 使用到Context對象的父容器的名字的,如果要使用ContexConfig實例來進行配置的話,Context實例必須有一個Host實例作為其父容器,簡單的來說,除非你自已實現一個ContextConfig類,替換掉配置StandardContext對象的ContextConfig對象,否則你必須使用一個Host容器。

咱們搞一個 實例 試一下 重點看下 怎么使用Host容器的 兩個類 一個 簡單的ContextConfig 的實現 SimpleContextConfig 主要是為了 在StandardContext在啟動時 將 其Configured屬性賦值為true,

第二 就是咱們啟動的 類 ,話不多說 搞起 

第一個簡單的ContextConfig類實現 simpleContextConfig

 1 package myex13.pyrmont.core;
 2 
 3 import org.apache.catalina.Context;
 4 import org.apache.catalina.Lifecycle;
 5 import org.apache.catalina.LifecycleEvent;
 6 import org.apache.catalina.LifecycleListener;
 7 
 8 /**
 9  * <p>
10  * <b>Title:SimpleContextConfig.java</b>
11  * </p>
12  * <p>
13  * Copyright:ChenDong 2018
14  * </p>
15  * <p>
16  * Company:僅學習時使用
17  * </p>
18  * <p>
19  * 類功能描述:簡單的ContextConfig配置類,本例中的功能只是為了在StandardContext
20  * 在start方法執行時,為了讓StandardContext可用,必須將其屬性 configured 正確配置標志
21  * 設置為true;StandContext才能被標記為可用且啟動成功
22  * </p>
23  * 
24  * @author 陳東
25  * @date 2018年12月16日 下午1:16:34
26  * @version 1.0
27  */
28 public class SimpleContextConfig implements LifecycleListener {
29 
30     /**
31      * 
32      * <p>
33      * Title: lifecycleEvent
34      * </p>
35      * 
36      * @date 2018年12月16日 下午1:16:34
37      * 
38      *       <p>
39      *       功能描述:
40      *       </p>
41      * 
42      * @param event
43      * 
44      */
45     @Override
46     public void lifecycleEvent(LifecycleEvent event) {
47         if (Lifecycle.START_EVENT.equals(event.getType())) {
48             Context context = (Context) event.getLifecycle();
49             context.setConfigured(true);
50         }
51 
52     }
53 
54 }

第二個 就是咱們的啟動類了

package myex13.pyrmont.startup;

import org.apache.catalina.Connector;
import org.apache.catalina.Context;
import org.apache.catalina.Host;
import org.apache.catalina.Lifecycle;
import org.apache.catalina.LifecycleEvent;
import org.apache.catalina.LifecycleListener;
import org.apache.catalina.Loader;
import org.apache.catalina.Wrapper;
import org.apache.catalina.connector.http.HttpConnector;
import org.apache.catalina.core.StandardContext;
import org.apache.catalina.core.StandardHost;
import org.apache.catalina.core.StandardWrapper;
import org.apache.catalina.loader.WebappLoader;

import myex13.pyrmont.core.SimpleContextConfig;

/**
 * <p>
 * <b>Title:Bootstrap1.java</b>
 * </p>
 * <p>
 * Copyright:ChenDong 2018
 * </p>
 * <p>
 * Company:僅學習時使用
 * </p>
 * <p>
 * 類功能描述: 該示例 重點在於說明Host容器最為頂層容器的使用方法,
 * </p>
 * 
 * @author 陳東
 * @date 2018年12月16日 下午1:27:11
 * @version 1.0
 */
public class Bootstrap1 {

    /**
     * 
     * <p>
     * Title: main
     * </p>
     * 
     * @date 2018年12月16日 下午1:27:11
     * 
     *       <p>
     *       功能描述:
     *       </p>
     * 
     * @param args
     * 
     */
    public static void main(String[] args) {
        // 設置系統屬性
        System.setProperty("catalina.base", System.getProperty("user.dir"));
        // 實例化一個連接器
        Connector connector = new HttpConnector();

        // ---------------------------實例化 對應Servlet的Wrapper
        // 對應 PrimitiveServlet
        Wrapper wrapper1 = new StandardWrapper();
        wrapper1.setName("Primitive");
        wrapper1.setServletClass("PrimitiveServlet");

        // 對應 ModernServlet

        Wrapper wrapper2 = new StandardWrapper();
        wrapper2.setName("Modern");
        wrapper2.setServletClass("ModernServlet");

        // -----------------------------實例化 Context 示例
        Context context = new StandardContext();
        context.setPath("/app1");
        context.setDocBase("app1");
        context.addChild(wrapper1);
        context.addChild(wrapper2);
        // 為Context配置一個監聽事件 配置器 在咱們這個例子中 不做任何配置 直接將 Context的configured屬性配置為ture
        LifecycleListener listener = new SimpleContextConfig();
        ((Lifecycle) context).addLifecycleListener(listener);

        // 重點來了。。。。重點來了,,,,,,,重點來了 ,,,,,期盼已經的Host 在下面即將登場
        Host host = new StandardHost();
        host.addChild(context);
        host.setName("localhost");
        host.setAppBase("webapps");
        Loader loader = new WebappLoader();
        context.setLoader(loader);
        // -------給context增加 servletMapping
        context.addServletMapping("/Primitive", "Primitive");
        context.addServletMapping("/Modern", "Modern");
        connector.setContainer(host);

        try {

            connector.initialize();
            ((Lifecycle) connector).start();
            ((Lifecycle) host).start();

            // 方便隨時停止
            System.in.read();
            ((Lifecycle) host).stop();
        } catch (Exception e) {
            e.printStackTrace();
        }

    }

}

注意 為了 Host 需要為其創建根路徑的 文件夾滴

看下我的文件結構

 

為Host容器 設置的根路徑就是 webapps,然后為其子容器Context 設置的根路徑為 app1.

 Engine接口

  Engine容器是org.apache.catalina.Engine接口的實例。Engine容器也就是Tomat的Servlet引擎,當部署Tomcat時要支持多個虛擬機的話,就需要使用Engine容器,事實上,一般情況下,部署的Tomcat都會使用一個Engine容器

 1 package org.apache.catalina;
 2 
 3 /**
 4  * 
 5  * <p>
 6  * <b>Title:Engine.java</b>
 7  * </p>
 8  * <p>
 9  * Copyright:ChenDong 2018
10  * </p>
11  * <p>
12  * Company:僅學習時使用
13  * </p>
14  * <p>
15  * 類功能描述:
16  * <h1>Engine</h1> 表示整個Container Servlet 引擎,標准實現是
17  * <code>org.apache.catalina.core.StandardEngine</code>
18  * </p>
19  * 
20  * @author 
21  * @date 2018年12月16日 下午3:30:08
22  * @version 1.0
23  */
24 public interface Engine extends Container {
25 
26     // ------------------------------------------------------------- Properties
27 
28     /**
29      * 
30      * 從引擎中返回一個默認的虛擬機容器名
31      */
32     public String getDefaultHost();
33 
34     /**
35      * 
36      * 為引擎設置一個默認的虛擬機容器名
37      *
38      * @param defaultHost
39      *            新的默認的虛擬機容器名
40      */
41     public void setDefaultHost(String defaultHost);
42 
43     /**
44      * 檢索此引擎的 {@code JvmRouteId}
45      */
46     public String getJvmRoute();
47 
48     /**
49      * Set the JvmRouteId for this engine. 設置此引擎的 {@code JvmRouteId}
50      *
51      * @param jvmRouteId
52      *            (新的)JVM路由ID(JvmRouteId)。集群中的每個引擎必須具有唯一的JVM路由ID(JvmRouteId)
53      */
54     public void setJvmRoute(String jvmRouteId);
55 
56     /**
57      * 
58      * 返回與我們相關聯的 <code>Service</code> (如果有的話)
59      */
60     public Service getService();
61 
62     /**
63      * 設置與該引擎相關的 <code>Service</code> ,如果有的話
64      *
65      * @param service
66      *            引擎擁有的服務
67      */
68     public void setService(Service service);
69 
70     /**
71      * 為新的web應用程序設置DefaultContext.
72      *
73      * @param defaultContext
74      *            新的 DefaultContext
75      */
76     public void addDefaultContext(DefaultContext defaultContext);
77 
78     /**
79      * 檢索新Web應用程序的DefaultContext。
80      */
81     public DefaultContext getDefaultContext();
82 
83     // --------------------------------------------------------- Public Methods
84 
85     /**
86      * 將DefaultContext配置導入到web應用程序Context文中。
87      *
88      * @param context
89      *            導入DefaultContext的web應用程序Context
90      */
91     public void importDefaultContext(Context context);
92 
93 }

  在Engine容器中,可以設置一個默認的Host容器或者一個默認的Context容器,注意,Engine容器可以與一個服務器實例相關聯,服務器咱們之后的文章再說。那么下面介紹一下 Catalina中 Engine的默認標准實現

StandardEngine

  org.apache.catalina.core.StandardEngine類時 Engine接口的標准實現,相比於StandardContext類和StandardHost類,StandardEngine類相對小一些,在實例化的時候,StandardEngine類會添加一個基礎閥,如其默認的構造器

 1     /**
 2      * 
 3      * 使用默認無參數構造器創建一個 設置了基礎閥{@link StandardEngineValve}的{@link StandardEngine}實例
 4      */
 5     public StandardEngine() {
 6 
 7         super();
 8         pipeline.setBasic(new StandardEngineValve());
 9 
10     }

  作為一個頂層容器,Engine容器可以由子容器,而它的子容器只能是Host容器,所以,若是給它設置了一個非Host類型的子容器,就會拋出異常,下面是StandardEngine類的addChild方法的實現代碼;

 1 /**
 2      * 
 3      * 添加一個子容器,但是它的子容器僅限為 Host類型的Container
 4      * 
 5      * @param child
 6      *           要被添加的子容器
 7      */
 8     public void addChild(Container child) {
 9         //子容器非Host直接拋出異常
10         if (!(child instanceof Host))
11             throw new IllegalArgumentException(sm.getString("standardEngine.notHost"));
12         super.addChild(child);
13 
14     }

  此外,因為Engine容器已經是頂層容器了 所有是不可能也不允許在擁有父容器了。如果調用StandardEngine類的setParent方法,為其添加一個父容器時,就會拋出異常

 1 /**
 2      *
 3      * 因為StandardEngine已經貴為頂層容器,不可能在有父容器了,所以若setParent方法被觸發 直接拋出錯誤
 4      *
 5      * @param container
 6      *            建議父容器
 7      */
 8     public void setParent(Container container) {
 9 
10         throw new IllegalArgumentException(sm.getString("standardEngine.notParent"));
11 
12     }

  StandardEngineValue

org.apache.catalina.core.StandardEngineValue類時StandardEngine容器的基礎閥,那么套路那還是那個老一套 ,這里就不啰嗦了,  直接看invoke方法

 1 public void invoke(Request request, Response response, ValveContext valveContext)
 2             throws IOException, ServletException {
 3         //看看 請求 和 響應 是不是有效滴
 4         if (!(request.getRequest() instanceof HttpServletRequest)
 5                 || !(response.getResponse() instanceof HttpServletResponse)) {
 6             return; // NOTE - Not much else we can do generically
 7         }
 8 
 9         // 驗證任何HTTP/1.1的請求 是否包括主機頭
10         HttpServletRequest hrequest = (HttpServletRequest) request;
11         if ("HTTP/1.1".equals(hrequest.getProtocol()) && (hrequest.getServerName() == null)) {
12             ((HttpServletResponse) response.getResponse()).sendError(HttpServletResponse.SC_BAD_REQUEST,
13                     sm.getString("standardEngine.noHostHeader", request.getRequest().getServerName()));
14             return;
15         }
16 
17         // 選擇用於此請求的host容器
18         StandardEngine engine = (StandardEngine) getContainer();
19         Host host = (Host) engine.map(request, true);
20         if (host == null) {
21             ((HttpServletResponse) response.getResponse()).sendError(HttpServletResponse.SC_BAD_REQUEST,
22                     sm.getString("standardEngine.noHost", request.getRequest().getServerName()));
23             return;
24         }
25 
26         //讓 host容器的invoke方法 處理請求
27         host.invoke(request, response);
28 
29     }

有點老生常談的感覺,老套路,在驗證了request和response對象的類型之后,invoke反方得到Host實例,用於處理請求,invoke方法會通過調用Engine的map方法獲取host對象,但是StandardEngine類沒有實現Map反方 所以該是 去ContainerBase的map方法

基礎閥invoke 調用 ContainerBase的map方法 ,然后ContainerBase的map方法 根據 請求的協議 找到對應映射器,所以下面咱們該看StandardEngineMapper的map方法了

 1 public Container map(Request request, boolean update) {
 2 
 3         int debug = engine.getDebug();
 4 
 5         // 提取請求的服務器名稱
 6         String server = request.getRequest().getServerName();
 7         if (server == null) {
 8             // 沒有就搞個默認的
 9             server = engine.getDefaultHost();
10             // 需要更新就讓request更新一下
11             if (update)
12                 request.setServerName(server);
13         }
14         // 沒有server 的不用玩了 直接返回null
15         if (server == null)
16             return (null);
17         server = server.toLowerCase();
18         if (debug >= 1)
19             engine.log("Mapping server name '" + server + "'");
20 
21         // 直接查找匹配的子子容器
22         if (debug >= 2)
23             engine.log(" Trying a direct match");
24         Host host = (Host) engine.findChild(server);
25 
26         // 通過別名查找匹配的host。
27         if (host == null) {
28             if (debug >= 2)
29                 engine.log(" Trying an alias match");
30             Container children[] = engine.findChildren();
31             for (int i = 0; i < children.length; i++) {
32                 String aliases[] = ((Host) children[i]).findAliases();
33                 for (int j = 0; j < aliases.length; j++) {
34                     if (server.equals(aliases[j])) {
35                         host = (Host) children[i];
36                         break;
37                     }
38                 }
39                 if (host != null)
40                     break;
41             }
42         }
43 
44         // 嘗試使用“默認”Host
45         if (host == null) {
46             if (debug >= 2)
47                 engine.log(" Trying the default host");
48             host = (Host) engine.findChild(engine.getDefaultHost());
49         }
50 
51         // Update the Request if requested, and return the selected Host
52         ; // No update to the Request is required
53         return (host);
54 
55     }

關於Engine容器 的一些重要的點 上面基本都展示l 下面來一個示例

  1 package myex13.pyrmont.startup;
  2 
  3 import org.apache.catalina.Connector;
  4 import org.apache.catalina.Context;
  5 import org.apache.catalina.Engine;
  6 import org.apache.catalina.Host;
  7 import org.apache.catalina.Lifecycle;
  8 import org.apache.catalina.LifecycleListener;
  9 import org.apache.catalina.Loader;
 10 import org.apache.catalina.Wrapper;
 11 import org.apache.catalina.connector.http.HttpConnector;
 12 import org.apache.catalina.core.StandardContext;
 13 import org.apache.catalina.core.StandardEngine;
 14 import org.apache.catalina.core.StandardHost;
 15 import org.apache.catalina.core.StandardWrapper;
 16 import org.apache.catalina.loader.WebappLoader;
 17 
 18 import ex20.pyrmont.standardmbeantest.StandardAgent;
 19 import myex13.pyrmont.core.SimpleContextConfig;
 20 
 21 /**
 22  * <p>
 23  * <b>Title:Bootstrap2.java</b>
 24  * </p>
 25  * <p>
 26  * Copyright:ChenDong 2018
 27  * </p>
 28  * <p>
 29  * Company:僅學習時使用
 30  * </p>
 31  * <p>
 32  * 類功能描述: 重點在於說明如何使用作為頂層容器Engine的實例,
 33  * </p>
 34  * 
 35  * @author 陳東
 36  * @date 2018年12月16日 下午4:10:27
 37  * @version 1.0
 38  */
 39 public class Bootstrap2 {
 40 
 41     /**
 42      * 
 43      * <p>
 44      * Title: main
 45      * </p>
 46      * 
 47      * @date 2018年12月16日 下午4:10:27
 48      * 
 49      *       <p>
 50      *       功能描述:
 51      *       </p>
 52      * 
 53      * @param args
 54      * 
 55      */
 56     public static void main(String[] args) {
 57         System.setProperty("catalina.base", System.getProperty("user.dir"));
 58         // -----------實例化連接器
 59         Connector connector = new HttpConnector();
 60 
 61         // 實例化Wrapepr
 62         Wrapper wrapper1 = new StandardWrapper();
 63         wrapper1.setName("Primitive");
 64         wrapper1.setServletClass("PrimitiveServlet");
 65 
 66         Wrapper wrapper2 = new StandardWrapper();
 67 
 68         wrapper2.setName("Modern");
 69         wrapper2.setServletClass("ModernServlet");
 70 
 71         // 實例化 Context
 72         Context context = new StandardContext();
 73         // 設置根路徑
 74         context.setPath("/app1");
 75         // 設置根文件夾
 76         context.setDocBase("app1");
 77         context.addChild(wrapper1);
 78         context.addChild(wrapper2);
 79         // 添加配置監聽器
 80         LifecycleListener listener = new SimpleContextConfig();
 81         ((Lifecycle) context).addLifecycleListener(listener);
 82 
 83         // 實例化一個host容器
 84         Host host = new StandardHost();
 85         host.addChild(context);
 86         host.setName("localhost");
 87         // 設置host的根路徑文件
 88         host.setAppBase("webapps");
 89         Loader loader = new WebappLoader();
 90         context.setLoader(loader);
 91 
 92         context.addServletMapping("/Primitive", "Primitive");
 93         context.addServletMapping("/Modern", "Modern");
 94 
 95         // 實例化一個 Engine容器
 96         Engine engine = new StandardEngine();
 97 
 98         engine.addChild(host);
 99         // 設置默認的Host容器 對應上文的 Host設置的名字
100         engine.setDefaultHost("localhost");
101 
102         connector.setContainer(engine);
103 
104         try {
105             connector.initialize();
106             ((Lifecycle) connector).start();
107             ((Lifecycle) engine).start();
108 
109             System.in.read();
110             ((Lifecycle) engine).stop();
111 
112         } catch (Exception e) {
113             e.printStackTrace();
114         }
115 
116     }
117 
118 }

  上面已經將Host  和 Engine容器 分別介紹了與其相關的類,也粉別展示了Host  和 Enginer容器作為 Tomcat中頂層容器的使用方法。那么就先搞這些吧


免責聲明!

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



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