这边文章主要介绍的是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中顶层容器的使用方法。那么就先搞这些吧