要使用一个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方法

 /**
* 处理与该对象关联的Host对象的 声明周期监听事件
*
* @param event
* 发生的事件
*/
public void lifecycleEvent(LifecycleEvent event) { // 确定与我们关联的Host
try {
host = (Host) event.getLifecycle();
if (host instanceof StandardHost) {
int hostDebug = ((StandardHost) host).getDebug();
if (hostDebug > this.debug) {
this.debug = hostDebug;
}
// 设置 Host实例是否要部署一个Context实例的描述文件,默认情况 标志位true;
setDeployXML(((StandardHost) host).isDeployXML());
// 设置Host实例是否要周期性的检查一个新的部署的标志
setLiveDeploy(((StandardHost) host).getLiveDeploy());
// 设置 是否要将WAR文件形式的Web应用程序解压缩标志
setUnpackWARs(((StandardHost) host).isUnpackWARs());
}
} catch (ClassCastException e) {
log(sm.getString("hostConfig.cce", event.getLifecycle()), e);
return;
} // 处理已经发生的事件
if (event.getType().equals(Lifecycle.START_EVENT))
start();
else if (event.getType().equals(Lifecycle.STOP_EVENT))
stop(); }

如果变量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文件

 /**
* Deploy WAR files. 可以将Web应用程序以一个WAR形式的文件来部署,
*/
protected void deployWARs(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]);
// 如果这个文件是一个 war文件
if (files[i].toLowerCase().endsWith(".war")) {
// 将其添加到已经部署集合中
deployed.add(files[i]); // 计算上下文路径并确保其唯一
// 拼接一个context路径
String contextPath = "/" + files[i];
// 如果这个路径还有.
int period = contextPath.lastIndexOf(".");
if (period >= 0)
// 截取.之前的路径字符串
contextPath = contextPath.substring(0, period);
// 如果context路径等于/ROOT
if (contextPath.equals("/ROOT"))
contextPath = "";
// 如果这个host的子容器中已经存在这个路径的Context了 则不需要继续了
if (host.findChild(contextPath) != null)
continue;
// 如果允许将WAR问价 解压缩的话
if (isUnpackWARs()) { // 将此应用程序扩展并部署为目录
log(sm.getString("hostConfig.expand", files[i]));
try {
URL url = new URL("jar:file:" + dir.getCanonicalPath() + "!/");
// 解压缩后的目录文件名
String path = expand(url); url = new URL("file:" + path);
((Deployer) host).install(contextPath, url);
} catch (Throwable t) {
log(sm.getString("hostConfig.expand.error", files[i]), t);
} } else { // 如果不允许解压缩 则把WAR文件作为JAR文件部署
log(sm.getString("hostConfig.deployJar", files[i]));
try {
URL url = new URL("file", null, dir.getCanonicalPath());
url = new URL("jar:" + url.toString() + "!/");
((Deployer) host).install(contextPath, url);
} catch (Throwable t) {
log(sm.getString("hostConfig.deployJar.error", files[i]), t);
} } } } }

部署一个目录,可以直接将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文件是否已经被修改,

 /**
*
* 派生一个新的线程并调用run方法,run方法会定期检查是否有新的应用要部署,或已经部署的Web应用程序的web.xml是否有修改。
*
* @exception IllegalStateException
* 如果我们不能启动这个线程
*/
protected void threadStart() { // 这个后台线程是否已经被启动了
if (thread != null)
return; // 启动一个线程
if (debug >= 1)
log(" Starting background thread");
threadDone = false;
threadName = "HostConfig[" + host.getName() + "]";
thread = new Thread(this, threadName);
// 设置为守护线程
thread.setDaemon(true);
thread.start(); }

这个守护线程被启动后 会调用HostConfig的run方法

 /**
*
* 周期性检查Web应用程序自动部署和对web.xml配置的更改的后台线程。
*/
public void run() { if (debug >= 1)
log("BACKGROUND THREAD Starting"); // 循环直到终止变量被设置
while (!threadDone) { // 等待我们的检查间隔
threadSleep(); // 如果主机允许自动部署,则部署应用程序
deployApps(); // 检查web.xml文件的事假戳
checkWebXmlLastModified(); } if (debug >= 1)
log("BACKGROUND THREAD Stopping"); }

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应用程序可以部署到其中,或者从中取消部署、

 package org.apache.catalina;

 import java.io.IOException;
import java.net.URL; /**
*
* <p>
* <b>Title:Deployer.java</b>
* </p>
* <p>
* Copyright:ChenDong 2019
* </p>
* <p>
* Company:仅学习时使用
* </p>
* <p>
* 类功能描述: 部署程序是一个专门的容器,可以在其中部署和取消部署Web应用程序。这样的容器将为每个部署的应用程序创建和安装Context实例。
* 每个Web应用程序的唯一键是其附加到Context路径。
* </p>
*
* @author * @date 2019年1月2日 下午10:03:45
* @version 1.0
*/
/* public interface Deployer extends Container { */
public interface Deployer { // ----------------------------------------------------- Manifest Constants /**
* <code>install()</code>安装新应用程序时,在启动前发送的ContainerEvent事件类型。
*/
public static final String PRE_INSTALL_EVENT = "pre-install"; /**
* 启动新应用程序后,<code>install()</code>安装新应用程序时发送的ContainerEvent事件类型
*/
public static final String INSTALL_EVENT = "install"; /**
* <code>remove()</code>.移除现有应用程序时发送的ContainerEvent事件类型。
*/
public static final String REMOVE_EVENT = "remove"; // --------------------------------------------------------- Public Methods /**
* 返回与此部署程序关联的容器的名称
*/
public String getName(); /**
*
*
* <p>
* Title: install
* </p>
*
* @date 2019年1月2日 下午10:18:43
*
* <p>
* 功能描述:使用指定的Context 路径将其标识的 Web应用程序部署到 指定URL位置上。此容器的根应用程序应使用“”(
* 空字符串)的Context路径。否则,Context路径必须以斜线开头。
*
*
*
* 如果成功安装此应用程序,将向所有注册的侦听器发送install_event类型的containerEvent,
* 并将新创建的Context作为参数。
* </p>
*
* @param contextPath
* @param war
* @throws IOException
*/
public void install(String contextPath, URL war) throws IOException; /**
*
*
* <p>
* Title: install
* </p>
*
* @date 2019年1月2日 下午10:27:36
*
* <p>
* 功能描述:安装新的Web应用程序,Context 配置描述XML文件(由<context>元素组成) 部署到 指定URL。
*
*
*
* 如果成功安装此应用程序,将向所有注册的侦听器发送install_event类型的containerEvent,
* 并将新创建的Context作为参数
* </p>
*
* @param config
* @param war
* @throws IOException
*/
public void install(URL config, URL war) throws IOException; /**
*
*
* <p>
* Title: findDeployedApp
* </p>
*
* @date 2019年1月2日 下午10:30:03
*
* <p>
* 功能描述:返回与指定Context路径关联的已部署应用程序的Context(如果有);否则返回空值。
* </p>
*
* @param contextPath
* context路径
* @return
*/
public Context findDeployedApp(String contextPath); /**
*
*
* <p>
* Title: findDeployedApps
* </p>
*
* @date 2019年1月2日 下午10:30:55
*
* <p>
* 功能描述:返回此容器中所有已部署Web应用程序的Context 路径。如果没有部署的应用程序,则返回零长度数组。
* </p>
*
* @return
*/
public String[] findDeployedApps(); /**
*
*
* <p>
* Title: remove
* </p>
*
* @date 2019年1月2日 下午10:31:41
*
* <p>
* 功能描述:删除附加到指定Context 路径的现有Web应用程序。如果成功删除此应用程序,
* 将向所有注册的侦听器发送一个remove_event类型的containerEvent,并将删除的Context作为参数。
* </p>
*
* @param contextPath
* @throws IOException
*/
public void remove(String contextPath) throws IOException; /**
*
*
* <p>
* Title: start
* </p>
*
* @date 2019年1月2日 下午10:32:26
*
* <p>
* 功能描述:启动附加到指定Context路径的现有Web应用程序。仅当Web应用程序不运行时才启动它。
* </p>
*
* @param contextPath
* @throws IOException
*/
public void start(String contextPath) throws IOException; /**
*
*
* <p>
* Title: stop
* </p>
*
* @date 2019年1月2日 下午10:32:53
*
* <p>
* 功能描述:关闭附加到指定Context 路径的现有Web应用程序。仅在Web应用程序运行时才关闭。
* </p>
*
* @param contextPath
* @throws IOException
*/
public void stop(String contextPath) throws IOException; }

  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实例的具体实现。

最新文章

  1. Atitit 团队建设的知识管理
  2. Anaconda安装与使用
  3. dedecms内容页调用缩略图 缩略图多种用法(借鉴)
  4. 关于 Java 性能监控您不知道的 5 件事,第 1 部分
  5. devpress GridControl控件绑定RepositoryItemImageComboBox 作为下拉框使用 zt
  6. mac tips
  7. 武汉科技大学ACM:1009: 华科版C语言程序设计教程(第二版)习题5.12
  8. list append 总是复制前面的参数,而不复制最后一个参数
  9. spring-mvc关键点掌握 high level
  10. Java补码表和位移运算符
  11. Mybatis之插件拦截
  12. Turn the Rectangles 1008B
  13. [ES]elasticsearch章2 ES查询过程解析
  14. Xcode 9 Analyzing Crash Reports
  15. C#匿名函数与Lambda表达式
  16. KMP 理解
  17. CentOS 6.8 ftp服务安装配置 基于本地用户和虚拟用户
  18. Delphi第三方组件安装DCU.PAS.DPK.BPL.ActiveX控件
  19. WordPress翻译更新失败解决方法
  20. 基于t-io的MI工具实现

热门文章

  1. Java写入的常用技巧(二)
  2. 转载: Windows下两种iocp实现的差距
  3. laravel中orderBy使用
  4. shapefile 输出的地理处理注意事项
  5. 因OpenCV版本不一致所引发的报错
  6. AndroidStudio NDK环境3种搭建方式
  7. ceph对接openstack环境(4)
  8. TCP/IP 和 和 DoD 模型
  9. Array.ConvertAll&lt;TInput, TOutput&gt; 数组相互转化方法
  10. python实现在目录中查找指定文件的方法