這邊文章主要介紹的是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中頂層容器的使用方法。那么就先搞這些吧