要使用一个Web应用程序,必须要将表示该应用程序的Context实例部署到一个Host实例中,在Tomcat中,Context实例可以用WAR文件的形式来部署,也可以将整个WEB应用程序复制到Tomcat安装目录下的webapp下。对于部署的每个应用程序,可以在其中包含一个描述符文件,该文件包含Context实例的配置信息,描述符文件也采用XML文档格式。
下面会讨论如何使用部署器来部署一个web应用程序,在Tomcat 中,部署器 是org.apahce.catalina.Deployer接口的实例,部署器与一个Host实例相关联,用来安装Context实例,安装Context实例的意思是,创建一个StandardContext实例,并将该实例添加到Host实例中,创建的Context实例会随其父容器 ---Host实例一起启动,因此 除了Wrapper实例类,容器的实例总会是调用其子容器的start方法,但是 部署器也可以用来单独签订和关闭Context实例。
部署一个Web应用程序
在原来我们学习Host实例时,使用如下代码来实例化StandardHost实例,并将一个Context实例作为子容器添加到Host实例中。
Context context = new StandardContext(); context.setPath("/app1"); conntext.setDocBase("app1"); LifecycleListener listener = new ContextConfig(); (Lifecycle(context)).addLifecycleListener(listener); Host host = new StandardHost(); host.addChild(context);
这是之前部署应用程序的方法,但是在Tomcat中并没有这样部署应用程序,拿在实际部署环境中Context实例是如何被添加到Host容器中的呢?答案在于StandardHost实例中使用的org.apache.catalina.startup.HostConfig类型的声明周期监听器。
当调用StandardHost实例的start方法时,它会触发START事件,HostConfig实例会对该事件进行响应,并调用其自身的start方法,该方法会逐个部署并安装指定目录中的所有web应用程序,下面对其中的细节进行讲解。
回忆一下之前如何使用Digester对象来解析XML文档的内容,但是它并没有涉及Digester对象中的所有规则,其中被忽略掉的一个主题就是部署器。
在Tomcat中,org.apahce.catalina.startup.Catalina类时启动类,使用Digester对象来解析server.xml文件,将其中的XML元素转换为java对象,Catalina类定义了createStartDigester方法 来为Digester对象来添加规则,createStarDigester方法的器其中一行代码如下:
digester.addRuleSet(new HostRuleSet("Server/Service/Engine/"));
org.apahce.catalina.strtup.HostRuleSet类继承自org.apache.commons.digester.RuleSetBase类(该类在前面介绍过,)。作为RuleSetBase类的一个子类,HostRuleSet类必须提供addRuleInstances方法的实现,需要在该方法中定义RuleSet的规则,
digester.addObjectCreate(prefix + "Host", "org.apache.catalina.core.StandardHost", "className"); digester.addSetProperties(prefix + "Host"); digester.addRule(prefix + "Host", new CopyParentClassLoaderRule(digester)); digester.addRule(prefix + "Host", new LifecycleListenerRule (digester, "org.apache.catalina.startup.HostConfig", "hostConfigClass"));
当在Server.xml文件中遇到符合 Server/Service/Engine/Host 模式的标签时,会创建 org.apache.catalina.startup.HostConfig的一个实例。并将其添加到Host实例中,作为声明周期监听器,换句话说,HostConfig类会处理StandardHost实例的start方法和 stop方法触发的事件,
下面给出了HostConfig实例的 lifecycleEvent方法的实现,该方法是一个事件处理程序,因为HostConfig的实例是StandardHost对象的声明周期事件监听器,每当StandardHost实例启动或者关闭时,都会调用HostConfig的lifecycleEvent方法
1 /** 2 * 处理与该对象关联的Host对象的 声明周期监听事件 3 * 4 * @param event 5 * 发生的事件 6 */ 7 public void lifecycleEvent(LifecycleEvent event) { 8 9 // 确定与我们关联的Host 10 try { 11 host = (Host) event.getLifecycle(); 12 if (host instanceof StandardHost) { 13 int hostDebug = ((StandardHost) host).getDebug(); 14 if (hostDebug > this.debug) { 15 this.debug = hostDebug; 16 } 17 // 设置 Host实例是否要部署一个Context实例的描述文件,默认情况 标志位true; 18 setDeployXML(((StandardHost) host).isDeployXML()); 19 // 设置Host实例是否要周期性的检查一个新的部署的标志 20 setLiveDeploy(((StandardHost) host).getLiveDeploy()); 21 // 设置 是否要将WAR文件形式的Web应用程序解压缩标志 22 setUnpackWARs(((StandardHost) host).isUnpackWARs()); 23 } 24 } catch (ClassCastException e) { 25 log(sm.getString("hostConfig.cce", event.getLifecycle()), e); 26 return; 27 } 28 29 // 处理已经发生的事件 30 if (event.getType().equals(Lifecycle.START_EVENT)) 31 start(); 32 else if (event.getType().equals(Lifecycle.STOP_EVENT)) 33 stop(); 34 35 }
如果变量host指向的对象是org.apahce.catalina.core.StandardHost类的实例,就会调用,setDeployXML、setLiveDeploye、setUnpackWARS方法,,
StandardHost类的 isDeployXML方法指明了host实例是否需要部署一个Context实例的描述符文件,默认情况下 deployXML属性的值为true,liveDeploy属性指明了Host实例是否要周期性的检查一个新的部署。unpackWARS属性指明是要将WAR文件形式的Web应用程序解压缩,
在收到START事件通知后,HostConfig对象的lifecycleEvent方法会调用start方法来部署web应用程序,
/** * * 处理开始事件 */ protected void start() { if (debug >= 1) log(sm.getString("hostConfig.start")); // 获取host的是否需要自动部署Web应用程序的标志,默认值为true if (host.getAutoDeploy()) { // 获取Host实例的 appBase属性的值,默认为“webapps” // server.xml 中 /** * <Host name="localhost" debug="0" appBase="webapps" unpackWARs= * "true" autoDeploy="true"> * */ // 部署进程会将%CATALINA_HOME%/webapps目录下的所有目录都看做web应用程序的目录来执行部署工作,该目录中的所有WAR文件和描述符文件也都会进行部署 deployApps(); } /** * HostConfig作为一个声明周期监听器。当StandardHost对象启动时,它的start方法会触发start事件。 * 为了响应start事件。HostConfig中的LifcycleEvent 方法 和 HostConfig 的start方法。 * 当LiveDeploye的值为true时,会调用threadStart方法 */ // 如果 需要周期性的检查部署 动态部署 if (isLiveDeploy()) { threadStart(); } }
当autoDeploy的属性值是true时,默认情况下该值为true,start方法会调用deployApps方法,此外 如果 liveDeploy属性的值为true,它还会调用threadStart方法来派生一个新线程来动态部署web应用。
deployApps方法 会获取Host实例的appBase属性的值,默认为webapps的值(可以参考下Tomcat的server.xml文件),部署进程会将%CATALINA_HOME%/webapps目录下的所有目录都看作为Web应用程序的目录来执行部署工作,此外该目录中的所有的WAR文件和描述符文件也都会进行部署。
/** * 为在我们的“应用程序根目录”中找到的任何目录或war文件部署应用程序。 */ protected void deployApps() { if (!(host instanceof Deployer)) return; if (debug >= 1) log(sm.getString("hostConfig.deploying")); // 返回了host 的根目录File引用 File appBase = appBase(); // 如果这个目录不存在 或者 这个目录 不是一个文件夹引用 直接返回 if (!appBase.exists() || !appBase.isDirectory()) return; // 获取host根目录下的所有文件名 String files[] = appBase.list(); // 部署描述文件 deployDescriptors(appBase, files); deployWARs(appBase, files); deployDirectories(appBase, files); }
deployApps方法会调用其他三个方法,deployDescriptions、deployeWARs、deployDirectories方法,deployApps方法会将FIle类型的变量appBase和webApps目录下的所有的文件名数组形式传递给这3个方法,Context实例是通过它的路径来标识的,每个部署的Context是都必须有一条唯一的路径,已经部署的Context实例的文件名 也就是去掉 /的路径标识会添加到存储已经部署Context的文件名的ArrayList中,因此,在部署一个Context实例之前,deployDescriptions、deployWARs、deployDirectories方法必须保证已经部署的ArrayList中没有具有相同路径的Context实例,
HostConfig类使用deployDescriptions来部署XML文件
/** * Deploy XML context descriptors. */ protected void deployDescriptors(File appBase, String[] files) { // 如果部署描述器标志 为false 则直接返回 if (!deployXML) return; // 迭代 host根目录下的所有文件名 for (int i = 0; i < files.length; i++) { // 如果文件名为 "META-INF" 则不作处理 继续迭代 if (files[i].equalsIgnoreCase("META-INF")) continue; // 如果文件名为 "WEB-INF" 则不作处理 继续迭代 if (files[i].equalsIgnoreCase("WEB-INF")) continue; // 如果 存储部署好的 Context 里已经包含这个这个文件名了 则 不继续做处理 继续迭代 if (deployed.contains(files[i])) continue; // 拼接完整的 文件目录 并实例一个File对象 File dir = new File(appBase, files[i]); // 如果文件末尾以xml结尾 if (files[i].toLowerCase().endsWith(".xml")) { // 将文件名 添加到已经部署好的集合中 deployed.add(files[i]); // 将文件名 .xml去掉 例如:admin.xml file = admin String file = files[i].substring(0, files[i].length() - 4); // Context的路径 = /admin String contextPath = "/" + file; // 如果 file 名为ROOT 则 context 的路径为host的appBase路径 if (file.equals("ROOT")) { contextPath = ""; } // 如果 host中 有这个路径Context 就不作任何处理 继续迭代 if (host.findChild(contextPath) != null) { continue; } // 假设这是一个配置描述符,然后部署它 log(sm.getString("hostConfig.deployDescriptor", files[i])); try { URL config = new URL("file", null, dir.getCanonicalPath()); ((Deployer) host).install(config, null); } catch (Throwable t) { log(sm.getString("hostConfig.deployDescriptor.error", files[i]), t); } } } }
第二:部署一个WAR文件
1 /** 2 * Deploy WAR files. 可以将Web应用程序以一个WAR形式的文件来部署, 3 */ 4 protected void deployWARs(File appBase, String[] files) { 5 // 开始迭代 host根目录下的文件名 6 for (int i = 0; i < files.length; i++) { 7 // 如果文件名是 META-INF 则不作处理 8 if (files[i].equalsIgnoreCase("META-INF")) 9 continue; 10 // 如果文件名是WEB-INF 则不做处理 11 if (files[i].equalsIgnoreCase("WEB-INF")) 12 continue; 13 // 如果这个文件名 已经属于被被部署了 则不做处理 继续迭代 14 if (deployed.contains(files[i])) 15 continue; 16 // 将这个文件全名 拼接完成 并实例一个FIle、引用 17 File dir = new File(appBase, files[i]); 18 // 如果这个文件是一个 war文件 19 if (files[i].toLowerCase().endsWith(".war")) { 20 // 将其添加到已经部署集合中 21 deployed.add(files[i]); 22 23 // 计算上下文路径并确保其唯一 24 // 拼接一个context路径 25 String contextPath = "/" + files[i]; 26 // 如果这个路径还有. 27 int period = contextPath.lastIndexOf("."); 28 if (period >= 0) 29 // 截取.之前的路径字符串 30 contextPath = contextPath.substring(0, period); 31 // 如果context路径等于/ROOT 32 if (contextPath.equals("/ROOT")) 33 contextPath = ""; 34 // 如果这个host的子容器中已经存在这个路径的Context了 则不需要继续了 35 if (host.findChild(contextPath) != null) 36 continue; 37 // 如果允许将WAR问价 解压缩的话 38 if (isUnpackWARs()) { 39 40 // 将此应用程序扩展并部署为目录 41 log(sm.getString("hostConfig.expand", files[i])); 42 try { 43 URL url = new URL("jar:file:" + dir.getCanonicalPath() + "!/"); 44 // 解压缩后的目录文件名 45 String path = expand(url); 46 47 url = new URL("file:" + path); 48 ((Deployer) host).install(contextPath, url); 49 } catch (Throwable t) { 50 log(sm.getString("hostConfig.expand.error", files[i]), t); 51 } 52 53 } else { 54 55 // 如果不允许解压缩 则把WAR文件作为JAR文件部署 56 log(sm.getString("hostConfig.deployJar", files[i])); 57 try { 58 URL url = new URL("file", null, dir.getCanonicalPath()); 59 url = new URL("jar:" + url.toString() + "!/"); 60 ((Deployer) host).install(contextPath, url); 61 } catch (Throwable t) { 62 log(sm.getString("hostConfig.deployJar.error", files[i]), t); 63 } 64 65 } 66 67 } 68 69 } 70 71 }
部署一个目录,可以直接将Web应用程序的整个目录复制到%CATALINA_HOME%/webapps目录下来完成web应用程序的部署
/** * 部署一个目录 可以直接将Web应用程序的整个目录复制到 %CATALINA_HOME%webapps目录下来完成web应用程序的部署, */ protected void deployDirectories(File appBase, String[] files) { // 迭代host根目录下的所有文件名 for (int i = 0; i < files.length; i++) { // 如果 文件名为 META-INF则 不做任何处理 继续跌倒 if (files[i].equalsIgnoreCase("META-INF")) continue; // 如果文件名为 WEB-INF则不做任何处理继续迭代 if (files[i].equalsIgnoreCase("WEB-INF")) continue; // 如果这个文件已经被部署了 则继续迭代 不做任何处理了 if (deployed.contains(files[i])) continue; // 引用这个文件的FIle引用 File dir = new File(appBase, files[i]); // 如果这个文件是一个 文件夹 if (dir.isDirectory()) { // 将文件添加到已经部署好的集合了 deployed.add(files[i]); // 确保存在应用程序配置目录。如果上下文AppBase与Web服务器文档根目录相同,则需要此目录,以确保仅部署Web应用程序,而不部署Web空间目录。. // 只部署其web-inf目录 // 找到 dir目录下的 /WEB-INF文件夹 File webInf = new File(dir, "/WEB-INF"); // 如果不存在则 不做任何处理 继续迭代 if (!webInf.exists() || !webInf.isDirectory() || !webInf.canRead()) continue; // 将文件名前加上一个/ String contextPath = "/" + files[i]; // 如果 文件名为ROOT 则host的appBase的目录为 context的目录 if (files[i].equals("ROOT")) contextPath = ""; // 如果 host的子容器中已经存在这个路径的Context 则继续迭代 不继续处理了 if (host.findChild(contextPath) != null) continue; // 在此目录中部署应用程序 log(sm.getString("hostConfig.deployDir", files[i])); try { URL url = new URL("file", null, dir.getCanonicalPath()); ((Deployer) host).install(contextPath, url); } catch (Throwable t) { log(sm.getString("hostConfig.deployDir.error", files[i]), t); } } } }
动态部署,正如前面提到的 StandardHost实例会使用HostConfig对象作为一个生命周期事件监听器,当StandardHost对象被启动时,它的start方法会触发一个START事件。为了响应START事件,HostConfig中lifecycleEvent方法和HostConfig中的事件处理程序会调用start方法,在start方法的最后一行,当liveDeploy属性为true时,start方法会调用threadStart方法,
threadStart方法会派生一个新线程并调用run方法,(本身HostConfig对象就继承了Runnable接口)所以会吧当前HostConfig对象传入,HostConfig对象的run方法会定期检查是否有新的应用要部署,或者已经部署的Web应用程序的web.xml文件是否已经被修改,
1 /** 2 * 3 * 派生一个新的线程并调用run方法,run方法会定期检查是否有新的应用要部署,或已经部署的Web应用程序的web.xml是否有修改。 4 * 5 * @exception IllegalStateException 6 * 如果我们不能启动这个线程 7 */ 8 protected void threadStart() { 9 10 // 这个后台线程是否已经被启动了 11 if (thread != null) 12 return; 13 14 // 启动一个线程 15 if (debug >= 1) 16 log(" Starting background thread"); 17 threadDone = false; 18 threadName = "HostConfig[" + host.getName() + "]"; 19 thread = new Thread(this, threadName); 20 // 设置为守护线程 21 thread.setDaemon(true); 22 thread.start(); 23 24 }
这个守护线程被启动后 会调用HostConfig的run方法
1 /** 2 * 3 * 周期性检查Web应用程序自动部署和对web.xml配置的更改的后台线程。 4 */ 5 public void run() { 6 7 if (debug >= 1) 8 log("BACKGROUND THREAD Starting"); 9 10 // 循环直到终止变量被设置 11 while (!threadDone) { 12 13 // 等待我们的检查间隔 14 threadSleep(); 15 16 // 如果主机允许自动部署,则部署应用程序 17 deployApps(); 18 19 // 检查web.xml文件的事假戳 20 checkWebXmlLastModified(); 21 22 } 23 24 if (debug >= 1) 25 log("BACKGROUND THREAD Stopping"); 26 27 }
threadSleep方法会使该线程休眠一段时间,该时间的长短由属性checkInterval指定,该属性默认为15s,即每隔15秒检查一次,
看下检查web.xml文件的时间戳的方法
/** * 检查部署描述符的上次修改日期。 */ protected void checkWebXmlLastModified() { if (!(host instanceof Deployer)) return; Deployer deployer = (Deployer) host; String[] contextNames = deployer.findDeployedApps(); for (int i = 0; i < contextNames.length; i++) { String contextName = contextNames[i]; Context context = deployer.findDeployedApp(contextName); if (!(context instanceof Lifecycle)) continue; try { DirContext resources = context.getResources(); if (resources == null) { // This can happen if there was an error initializing // the context continue; } // 如果旧的时间戳 和新的 时间戳不一致 ResourceAttributes webXmlAttributes = (ResourceAttributes) resources.getAttributes("/WEB-INF/web.xml"); long newLastModified = webXmlAttributes.getLastModified(); Long lastModified = (Long) webXmlLastModified.get(contextName); if (lastModified == null) { webXmlLastModified.put(contextName, new Long(newLastModified)); } else { if (lastModified.longValue() != newLastModified) { webXmlLastModified.remove(contextName); // 先将cotext停止 ((Lifecycle) context).stop(); // Context 已停止,将引发生命周期异常,并且Context将不会重新启动。 // 在重新启动 更细 现有的web.xml进行重新配置 ((Lifecycle) context).start(); } } } catch (LifecycleException e) { ; // Ignore } catch (NamingException e) { ; // Ignore } } }
如果发现web.xml文件的时间戳已经发生改变则 将Context关闭 并重新启动,在重新启动时会重新读取web.xml的配置
下面来说一下 重中之重,部署器
Deployer接口
部署器是org.apache.catalina.Deployer接口的实例,StandardHost类实现了 Deployer接口,因此,StandardHost实例也是一个部署器,它是一个容器,web应用程序可以部署到其中,或者从中取消部署、
1 package org.apache.catalina; 2 3 import java.io.IOException; 4 import java.net.URL; 5 6 /** 7 * 8 * <p> 9 * <b>Title:Deployer.java</b> 10 * </p> 11 * <p> 12 * Copyright:ChenDong 2019 13 * </p> 14 * <p> 15 * Company:仅学习时使用 16 * </p> 17 * <p> 18 * 类功能描述: 部署程序是一个专门的容器,可以在其中部署和取消部署Web应用程序。这样的容器将为每个部署的应用程序创建和安装Context实例。 19 * 每个Web应用程序的唯一键是其附加到Context路径。 20 * </p> 21 * 22 * @author 23 * @date 2019年1月2日 下午10:03:45 24 * @version 1.0 25 */ 26 /* public interface Deployer extends Container { */ 27 public interface Deployer { 28 29 // ----------------------------------------------------- Manifest Constants 30 31 /** 32 * <code>install()</code>安装新应用程序时,在启动前发送的ContainerEvent事件类型。 33 */ 34 public static final String PRE_INSTALL_EVENT = "pre-install"; 35 36 /** 37 * 启动新应用程序后,<code>install()</code>安装新应用程序时发送的ContainerEvent事件类型 38 */ 39 public static final String INSTALL_EVENT = "install"; 40 41 /** 42 * <code>remove()</code>.移除现有应用程序时发送的ContainerEvent事件类型。 43 */ 44 public static final String REMOVE_EVENT = "remove"; 45 46 // --------------------------------------------------------- Public Methods 47 48 /** 49 * 返回与此部署程序关联的容器的名称 50 */ 51 public String getName(); 52 53 54 55 /** 56 * 57 * 58 * <p> 59 * Title: install 60 * </p> 61 * 62 * @date 2019年1月2日 下午10:18:43 63 * 64 * <p> 65 * 功能描述:使用指定的Context 路径将其标识的 Web应用程序部署到 指定URL位置上。此容器的根应用程序应使用“”( 66 * 空字符串)的Context路径。否则,Context路径必须以斜线开头。 67 * 68 * 69 * 70 * 如果成功安装此应用程序,将向所有注册的侦听器发送install_event类型的containerEvent, 71 * 并将新创建的Context作为参数。 72 * </p> 73 * 74 * @param contextPath 75 * @param war 76 * @throws IOException 77 */ 78 public void install(String contextPath, URL war) throws IOException; 79 80 81 /** 82 * 83 * 84 * <p> 85 * Title: install 86 * </p> 87 * 88 * @date 2019年1月2日 下午10:27:36 89 * 90 * <p> 91 * 功能描述:安装新的Web应用程序,Context 配置描述XML文件(由<context>元素组成) 部署到 指定URL。 92 * 93 * 94 * 95 * 如果成功安装此应用程序,将向所有注册的侦听器发送install_event类型的containerEvent, 96 * 并将新创建的Context作为参数 97 * </p> 98 * 99 * @param config 100 * @param war 101 * @throws IOException 102 */ 103 public void install(URL config, URL war) throws IOException; 104 105 /** 106 * 107 * 108 * <p> 109 * Title: findDeployedApp 110 * </p> 111 * 112 * @date 2019年1月2日 下午10:30:03 113 * 114 * <p> 115 * 功能描述:返回与指定Context路径关联的已部署应用程序的Context(如果有);否则返回空值。 116 * </p> 117 * 118 * @param contextPath 119 * context路径 120 * @return 121 */ 122 public Context findDeployedApp(String contextPath); 123 124 /** 125 * 126 * 127 * <p> 128 * Title: findDeployedApps 129 * </p> 130 * 131 * @date 2019年1月2日 下午10:30:55 132 * 133 * <p> 134 * 功能描述:返回此容器中所有已部署Web应用程序的Context 路径。如果没有部署的应用程序,则返回零长度数组。 135 * </p> 136 * 137 * @return 138 */ 139 public String[] findDeployedApps(); 140 141 /** 142 * 143 * 144 * <p> 145 * Title: remove 146 * </p> 147 * 148 * @date 2019年1月2日 下午10:31:41 149 * 150 * <p> 151 * 功能描述:删除附加到指定Context 路径的现有Web应用程序。如果成功删除此应用程序, 152 * 将向所有注册的侦听器发送一个remove_event类型的containerEvent,并将删除的Context作为参数。 153 * </p> 154 * 155 * @param contextPath 156 * @throws IOException 157 */ 158 public void remove(String contextPath) throws IOException; 159 160 /** 161 * 162 * 163 * <p> 164 * Title: start 165 * </p> 166 * 167 * @date 2019年1月2日 下午10:32:26 168 * 169 * <p> 170 * 功能描述:启动附加到指定Context路径的现有Web应用程序。仅当Web应用程序不运行时才启动它。 171 * </p> 172 * 173 * @param contextPath 174 * @throws IOException 175 */ 176 public void start(String contextPath) throws IOException; 177 178 179 /** 180 * 181 * 182 * <p> 183 * Title: stop 184 * </p> 185 * 186 * @date 2019年1月2日 下午10:32:53 187 * 188 * <p> 189 * 功能描述:关闭附加到指定Context 路径的现有Web应用程序。仅在Web应用程序运行时才关闭。 190 * </p> 191 * 192 * @param contextPath 193 * @throws IOException 194 */ 195 public void stop(String contextPath) throws IOException; 196 197 }
StandardHost类使用了一个辅助类(org.apahce.catalina.core.StandardHostDeployer)来完成部署与安装Web应用程序的相关任务,下面是StandardHost类的部分代码片段,展示了StandardHost对象是如何将部署任务委托给StandardHostDeployer实例来完成的,
/** * 我们将应用程序部署请求委托给的<code>Deployer</code> */ private Deployer deployer = new StandardHostDeployer(this);
public void install(String contextPath, URL war) throws IOException { deployer.install(contextPath, war); }
public synchronized void install(URL config, URL war) throws IOException { deployer.install(config, war); }
public Context findDeployedApp(String contextPath) { return (deployer.findDeployedApp(contextPath)); } public String[] findDeployedApps() { return (deployer.findDeployedApps()); }
public void start(String contextPath) throws IOException { deployer.start(contextPath); } public void stop(String contextPath) throws IOException { deployer.stop(contextPath); }
下面介绍一下StandardHostDeployer
StandardHostDeployer类
org.apahce.catalina.core.StandardHostDeployer类是一个辅助类,帮助完成将Web应用程序部署到StandardHost实例的工作,StandardHostDeployer实例由StandardHost对象调用,在其构造函数中,会传入StandardHost的一个实例对象
public StandardHostDeployer(StandardHost host) { super(); this.host = host; }
安装一个描述符
StandardHostDeployer类有两个install方法,下面介绍第一个install方法,它用来安装一个描述符,下面的代码给出的install方法用来安装描述符,当HostConfig对象的deployDescriptors方法调用了install方法后,StandardHost实例调用该install方法:
public synchronized void install(URL config, URL war) throws IOException { // 验证参数的格式和状态 if (config == null) throw new IllegalArgumentException(sm.getString("standardHost.configRequired")); if (!host.isDeployXML()) throw new IllegalArgumentException(sm.getString("standardHost.configNotAllowed")); // 计算新Web应用程序的文档库(如果需要) String docBase = null; // 配置文件中值的可选重写 if (war != null) { String url = war.toString(); host.log(sm.getString("standardHost.installingWAR", url)); // 计算war文件的绝对路径名 if (url.startsWith("jar:")) { url = url.substring(4, url.length() - 2); } if (url.startsWith("file://")) docBase = url.substring(7); else if (url.startsWith("file:")) docBase = url.substring(5); else throw new IllegalArgumentException(sm.getString("standardHost.warURL", url)); } // 安装新的Web应用程序 this.context = null; this.overrideDocBase = docBase; InputStream stream = null; try { stream = config.openStream(); // 老话常谈了 这个就是自定义了一些Rule 有空自己在单独看吧 Digester digester = createDigester(); digester.setDebug(host.getDebug()); digester.clear(); // Digester将 StandardHostDeployer放到 栈中的第一个位置 digester.push(this); digester.parse(stream); stream.close(); stream = null; } catch (Exception e) { host.log(sm.getString("standardHost.installError", docBase), e); throw new IOException(e.toString()); } finally { if (stream != null) { try { stream.close(); } catch (Throwable t) { ; } } } }
安装一个WAR文件或者目录
第二个 install方法接收一个表示 Context路径的字符串 和一个表示WAR文件的URL,代码清单如下:
public synchronized void install(String contextPath, URL war) throws IOException { // 验证参数的格式和状态 if (contextPath == null) throw new IllegalArgumentException(sm.getString("standardHost.pathRequired")); if (!contextPath.equals("") && !contextPath.startsWith("/")) throw new IllegalArgumentException(sm.getString("standardHost.pathFormat", contextPath)); // 先看看这个Context 是否已经被部署了 if (findDeployedApp(contextPath) != null) throw new IllegalStateException(sm.getString("standardHost.pathUsed", contextPath)); if (war == null) throw new IllegalArgumentException(sm.getString("standardHost.warRequired")); // 计算新Web应用程序的文档库 host.log(sm.getString("standardHost.installing", contextPath, war.toString())); String url = war.toString(); String docBase = null; if (url.startsWith("jar:")) { url = url.substring(4, url.length() - 2); } if (url.startsWith("file://")) docBase = url.substring(7); else if (url.startsWith("file:")) docBase = url.substring(5); else throw new IllegalArgumentException(sm.getString("standardHost.warURL", url)); // 安装新的Web应用程序 try { Class clazz = Class.forName(host.getContextClass()); Context context = (Context) clazz.newInstance(); context.setPath(contextPath); // 重点在TM这里 就是将web库设置给新实例化的Context context.setDocBase(docBase); if (context instanceof Lifecycle) { clazz = Class.forName(host.getConfigClass()); LifecycleListener listener = (LifecycleListener) clazz.newInstance(); ((Lifecycle) context).addLifecycleListener(listener); } host.fireContainerEvent(PRE_INSTALL_EVENT, context); //安装完这个Context之后 就会被添加到Host实例当中
host.addChild(context); host.fireContainerEvent(INSTALL_EVENT, context); } catch (Exception e) { host.log(sm.getString("standardHost.installError", contextPath), e); throw new IOException(e.toString()); } }
启动Context实例
StandardHostDeployer类中的start方法用于启动刚刚被安装的Context实例:
public void start(String contextPath) throws IOException { // 验证参数的有效性 if (contextPath == null) throw new IllegalArgumentException(sm.getString("standardHost.pathRequired")); if (!contextPath.equals("") && !contextPath.startsWith("/")) throw new IllegalArgumentException(sm.getString("standardHost.pathFormat", contextPath)); // 根据 contextpath 找到 被安装的Context实例 Context context = findDeployedApp(contextPath); if (context == null) throw new IllegalArgumentException(sm.getString("standardHost.pathMissing", contextPath)); host.log("standardHost.start " + contextPath); try { // 启动Context ((Lifecycle) context).start(); } catch (LifecycleException e) { host.log("standardHost.start " + contextPath + ": ", e); throw new IllegalStateException("standardHost.start " + contextPath + ": " + e); } }
那同样 有启动 就会有 停止
停止一个Context实例
StandardHostDeployer类的stop方法可以用来通知一个Context实例,代码清单如下:
public void stop(String contextPath) throws IOException { // 验证参数的有效性 if (contextPath == null) throw new IllegalArgumentException(sm.getString("standardHost.pathRequired")); if (!contextPath.equals("") && !contextPath.startsWith("/")) throw new IllegalArgumentException(sm.getString("standardHost.pathFormat", contextPath)); // 根据contextPath 来找到 被安装的Context实例 Context context = findDeployedApp(contextPath); if (context == null) throw new IllegalArgumentException(sm.getString("standardHost.pathMissing", contextPath)); host.log("standardHost.stop " + contextPath); // 关闭这个Context try { ((Lifecycle) context).stop(); } catch (LifecycleException e) { host.log("standardHost.stop " + contextPath + ": ", e); throw new IllegalStateException("standardHost.stop " + contextPath + ": " + e); } }
部署器是用来部署和安装Web应用程序的组件,是org.apache.catalina.core.Deployer接口的实例,StandHost类实现了Deployer接口,使其成为一个可以向其中部署web应用程序的特殊容器。StandardHost类会将部署和安装web应用程序的任务委托给StandardHostDeployer来完成,StandardHostDeployer类提供了部署和安装Web应用程序以及启动、关闭Context实例的具体实现。