概述
要使得一个 web 应用可以访问,则应用上下文(Context)必须先部署在主机(Host)上。在 Tomcat 中,一个应用上下文可以以 WAR 文件的形式部署,也可以直接将整个应用程序部署在Tomcat 安装目录的 wabapp 目录下面。部署的每个应用,都可以有一个配置描述文件用来配置该上下文,配置文件以 XML 文档的形式存在。
注意:在 Tomcat4 和 5 中有两个已经部署好的应用:manager 和 admin。它们相关文件都在%CATALINA_HOME%/server/webapps 目录下面。它们的配置文件分别是manager.xml 和 admin.xml。在 Tomcat4 中,配置文件在%CATALINA_HOME%/webapps 下面,而 Tomcat5 中,它们在相应的应用目录下面,即:%CATALINA_HOME%/server/webapps/admin 以及%CATALINA_HOME%/server/webapps/manager。
本章介绍使用部署器来部署一个 web 应用,部署器用org.apache.catalina.Deployer 接口表示。部署器跟主机Host相关联,用于安装子容器Context。往主机安装上下文意味着创建 StandardContext 实例并将其添加到主机(Host)。当主机启动时,子上下文也会启动(因为父容器的 start()方法总是启动子容器的start()方法,包装器wrapper除外)。然而,使用部署器也可以独立地开始和停止每个上下文。
在本章中,我们将首先学习Tomcat如何部署Web应用程序到一个主机。然后介绍Deployer接口及其标准实现类org.apache.catalina.core.StandardHostDeployer。
18.1 部署一个WEB应用
在第 15 章中,我们使用如下代码初始化一个 StandardHost 并将上下文对象作为其子容器添加到上面:
Context context = new StandardContext();
context.setPath("/app1");
context.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 应用程序。下面是具体的细节。
回忆第 15 章中的内容,它解释了如何使用 Digester 来解析一个 XML 文件。但是并没有讨论 Digester 对象的所有规则。它略过的一个主题就是部署器,这也正是本章要介绍的内容。
org.apache.catalina.startup.Catalina 是一个启动类,它使用Digester将 server.xml 文档中的 XML 元素转换为 Java 对象。Catalina 类定义了createStartDigester()方法用于往Digester 对象中添加规则。如下代码:
digester.addRuleSet(new HostRuleSet("Server/Service/Engine/"));
org.apache.catalina.startup.HostRuleSet 类继承了org.apache.commons.digester.RuleSetBase 类(第15章中有讨论)。作为RuleSetBase 类的子类,HostRuleSet类必须提供addRuleInstances()方法的实现,该方法用于为RuleSet定义规则。下面是 HostRuleSet 类的 addRuleInstances() 方法的代码片段:
public void addRuleInstances(Digester digester) {
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/Service/Engine/Host 模式时创建一org.apache.catalina.startup.HostConfig 类的对象,并将其作为一个生命周期监听器添加到主机上。换句话说,HostConfig 处理 StandardHost 的 start()和 stop()方法所触发的事件。
HostConfig 类的 lifecycleEvent()方法如 Listing18.1 所示。该方法用于处理相应事件,因为 HostConfig 是 StandardHost 实例的监听器,每次调用 StandardHost的start()或stop()时,都会触发 lifecycleEvent()方法。
Listing 18.1: The lifecycleEvent method of the HostConfig class.
public void lifecycleEvent(LifecycleEvent event) {
// Identify the host we are associated with
try {
host = (Host) event.getLifecycle();
if (host instanceof StandardHost) {
int hostDebug = ((StandardHost) host).getDebug();
if (hostDebug > this.debug) {
this.debug = hostDebug;
}
setDeployXML(((StandardHost) host).isDeployXML());
setLiveDeploy(((StandardHost) host).getLiveDeploy());
setUnpackWARs(((StandardHost) host).isUnpackWARs());
}
}catch (ClassCastException e) {
log(sm.getString("hostConfig.cce", event.getLifecycle()), e);
return;
}
// Process the event that has occurred
if (event.getType().equals(Lifecycle.START_EVENT))
start ();
else if (event.getType().equals(Lifecycle.STOP_EVENT))
stop();
}
如果主机是 org.apache.catalina.core.StandardHost 的一个实例,将会调用setDeployXML(),setLiveDeploy(),setUnpackWARs()方法:
setDeployXML(((StandardHost) host).isDeployXML());
setLiveDeploy(((StandardHost) host).getLiveDeploy());
setUnpackWARs(((StandardHost) host).isUnpackWARs());
StandardHost 的 isDeployXML()方法标志该主机是否部署一个上下文部署文件。deployXML 属性的默认值是 true。liveDeploy属性的值标志是否需要周期性地检查重新部署;unpackWARs 属性定义了是否需要解压所部署的 WAR 文件。
根据接收到的 START 事件,HostConfig 对象的 lifecycleEvent()方法调用 start()方法来部署应用程序,该方法如 Listing18.2。
Listing 18.2: The start method of the HostConfig class
protected void start() {
if (debug >= 1)
log(sm.getString("hostConfig.start"));
if (host.getAutoDeploy()) {
deployApps();
}
if (isLiveDeploy ()) {
threadStart();
}
}
如果 autoDeploy 属性为true,start()方法则调用 deployApps()方法。另外如果liveDeploy 为true,它还调用 threadStart()方法启动一个新线程。实时部署(Live deploy) 将在后面会介绍。
deployApps ()方法获得主机的 appBase属性,appBase 默认值为 webapps(见server.xml文件)。部署所有位于%CATALINE_HOME%/webapps 目录下面的应用程序。另外在该目录下面的 WAR 文件和描述文件也会被部署。deployApps()方法如 Listing18.3。
Listing 18.3: The deployApps method
protected void deployApps() {
if (!(host instanceof Deployer))
return;
if (debug >= 1)
log(sm.getString("hostConfig.deploying"));
File appBase = appBase();
if (!appBase.exists() || !appBase.isDirectory())
return;
String files[] = appBase.list();
deployDescriptors(appBase, files);
deployWARs(appBase, files);
deployDirectories(appBase, files);
}
deployApps()方法调用了其它3个方法: deployDescriptors(), deployWARs()和deployDirectories()。所有的方法中,都传递 appBase和webapps目录下的文件数组。一个上下文通过它的路径来鉴别,所有的上下文都有唯一的路径。部署的上下文被添加到 HostConfig 对象中的已部署列表上面。因此在部署一个上下文时, deployDescriptors, deployWARs和deployDirectories 要确定在已部署列表上面不包含该路径。
接下来我们依次看看这3个部署方法,看完下面的三小节后我们应该能明白此3个方法的调用顺序的重要性。
18.1.1描述文件方式部署
可以通过 XML文件来描述一个上下文对象。例如,在 Tomcat4/5中的admin和manager 应用的部署文件如 Listing18.4 和 Listing18.5所示。
Listing 18.4: The descriptor for the admin application (admin.xml)
<Context path="/admin" docBase="../server/webapps/admin" debug="0" privileged="true">
<!-- Uncomment this Valve to limit access to the Admin app to
localhost for obvious security reasons. Allow may be a comma-
separated list of hosts (or even regular expressions).
<Valve className="org.apache.catalina.valves.RemoteAddrValve" allow="127.0.0.1"/>
-->
<Logger className="org.apache.catalina.logger.FileLogger" prefix="localhost_admin_log." suffix=".txt" timestamp="true"/>
</Context>
Listing 18.5: The descriptor for the manager application (manager.xml)
<Context path="/manager" docBase="../server/webapps/manager" debug="0" privileged="true">
<!-- Link to the user database we will get roles from -->
<ResourceLink name="users" global="UserDatabase" type="org.apache.catalina.UserDatabase"/>
</Context>
注意这两个文件都有 Context 元素和分别指向%CATALINA_HOME%/server/webapps/admin
和%CATALINA_HOME%/server/webapps/manager的docBase 属性,这说明 admin 和 manager 应用没有部署在普通地点。
HostConfig 类使用如 Listing18.6 所示的 deployDecriptors()方法来部署位于%CATALINA_HOME%/webapps(Tomcat4)或%CATALINA_HOME%/server/webapps/ (Tomcat5)下面所有的 XML 文件。
Listing 18.6: The deployDescriptors method in HostConfig
protected void deployDescriptors(File appBase, String[] files) {
if (!deployXML)
return;
for (int i = 0; i < files.length; i++) {
if (files[i].equalsIgnoreCase("META-INF"))
continue;
if (files[i].equalsIgnoreCase("WEB-INF"))
continue;
if (deployed.contains(files[i]))
continue;
File dir = new File(appBase, files[i]);
if (files[i].toLowerCase().endsWith(".xml")) {
deployed.add(files[i]);
// Calculate the context path and make sure it is unique
String file = files[i].substring(0, files[i].length() - 4);
String contextPath = "/" + file;
if (file.equals("ROOT")) {
contextPath = "";
}
if (host.findChild(contextPath) != null) {
continue;
}
// Assume this is a configuration descriptor and deploy it
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);
}
}
}
}
18.1.2 WAR文件方式部署
可以以war 文件形式部署web应用。HostConfig 使用了如 Listing18.7 所示的deployWARs()方法来部署%CATALINA_HOME%/webapps目录下的 WAR 文件。
Listing 18.7: The deployWARs method in HostConfig
protected void deployWARs(File appBase, String[] files) {
for (int i = 0; i < files.length; i++) {
if (files[i].equalsIgnoreCase("META-INF"))
continue;
if (files[i].equalsIgnoreCase("WEB-INF"))
continue;
if (deployed.contains(files[i]))
continue;
File dir = new File(appBase, files[i]);
if (files[i].toLowerCase().endsWith(".war")) {
deployed.add(files[i]);
// Calculate the context path and make sure it is unique
String contextPath = "/" + files[i];
int period = contextPath.lastIndexOf(".");
if (period >= 0)
contextPath = contextPath.substring(0, period);
if (contextPath.equals("/ROOT"))
contextPath = "";
if (host.findChild(contextPath) != null)
continue;
if (isUnpackWARs()) {
// Expand and deploy this application as a directory
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 {
// Deploy the application in this WAR file
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);
}
}
}
}
}
18.1.3目录方式部署
另外也可以将整个目录拷贝到%CATALINA_HOME%/webapps 目录下来部署一个应用。HostConfig 使用如 Listing18.8 所示的 deployDirectories()方法来部署目录。
Listing 18.8: The deployDirectories method in HostConfig
protected void deployDirectories(File appBase, String[] files) {
for (int i = 0; i < files.length; i++) {
if (files[i].equalsIgnoreCase("META-INF"))
continue;
if (files[i].equalsIgnoreCase("WEB-INF"))
continue;
if (deployed.contains(files[i]))
continue;
File dir = new File(appBase, files[i]);
if (dir.isDirectory()) {
deployed.add(files[i]);
// Make sure there is an application configuration directory
// This is needed if the Context appBase is the same as the
// web server document root to make sure only web applications
// are deployed and not directories for web space.
File webInf = new File(dir, "/WEB-INF");
if (!webInf.exists() || !webInf.isDirectory() ||
!webInf.canRead())
continue;
// Calculate the context path and make sure it is unique
String contextPath = "/" + files[i];
if (files[i].equals("ROOT"))
contextPath = "";
if (host.findChild(contextPath) != null)
continue;
// Deploy the application in this directory
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);
}
}
}
}
18.1.4实时部署
如前面提到的StandardHost实例使用HostConfig对象作为一个生命周期监听器。当 StandardHost 对象开始时,它的start()方法会触发一个 START 事件。作为该事件的响应,HostConfig 中的 lifecycleEvent()方法作为它的事件处理器,会调用它的start()方法。在 Tomcat4 中,start()方法的最后一行,如果 liveDeploy 属性值为true的话(默认为true)则调用 threadStart()方法:
if (isLiveDeploy()) {
threadStart();
}
threadStart()方法分配一个新线程并调用其 run()方法,run()方法周期性地检查在web.xml 文件中的已存在部署是否有改变。该方法如 Listing18.9 所示:
Listing 18.9: The run method in HostConfig in Tomcat 4
/**
* The background thread that checks for web application autoDeploy
* and changes to the web.xml config.
*/
public void run() {
if (debug >= 1)
log("BACKGROUND THREAD Starting");
// Loop until the termination semaphore is set
while (!threadDone) {
// Wait for our check interval
threadSleep();
// Deploy apps if the Host allows auto deploying
deployApps();
// Check for web.xml modification
checkWebXmlLastModified();
}
if (debug >= 1)
log("BACKGROUND THREAD Stopping");
}
threadSleep()方法让线程休眠checkInterval秒时间(默认值是15s),这意味着检查每 15 秒进行一次。
在 Tomcat5 中,HostConfig 没有使用独立线程而是通过StandardHost类中 backgroundProcess()方法周期性地触发检查事件。
public void backgroundProcess() {
lifecycle.fireLifecycleEvent("check", null);
}
注意:backgroundProcess()方法由为该容器中的所有后台处理提供服务的特殊线程定期调用。
在收到一个”check”事件后,生命周期对象 HostConfig 对象调用它的 check()方法进行检查工作:
public void lifecycleEvent(LifecycleEvent event) {
if (event.getType().equals("check"))
check();
...
Tomcat5 中的 HostConfig 的 check()方法如 Listing18.10所示:
Listing 18.10: The check method in HostConfig in Tomcat 5
protected void check() {
if (host.getAutoDeploy()) {
// Deploy apps if the Host allows auto deploying
deployApps();
// Check for web.xml modification
checkContextLastModified();
}
}
如大家所见,在check()方法中调用了 deployApps()方法,deployApps()方法在 Tomcat4/5中都是部署一个 web 应用程序,如 Listing18.3 所示。如前面所讨论的,该方法调用deployDescriptors(), deployWARs()和 deployDirectories()。
Tomcat5 中的 check()方法调用了 checkCOntextLastModified()方法,迭代所有的部署上下文并检查 web.xml 以及每个上下文 WEB-INF 目录下面内容的时间戳。如果检查到改变,就重启该上下文。另外,checkContextLastModified()方法还检查部署的 WAR 文件的时间戳,如果有改变就进行重新部署。
在 Tomcat4 中,后台线程的run()方法调用 checkWebXmlLastModified()跟 Tomcat5中的 checkContextLastModified()方法完成相同任务。
18.2 Deployer接口
部署器由 org.apache.catalina.Deployer 接口表示。StandardHost 类实现了 Deployer 接口,因此,一个 StandardHost 实例既是一个容器也是一个部署器。Deployer 接口如 Listing18.11 所示。
Listing 18.11: The Deployer interface
package org.apache.catalina;
import java.io.IOException;
import java.net.URL;
/**
* A <b>Deployer</b> is a specialized Container into which web
* applications can be deployed and undeployed. Such a Container
* will create and install child Context instances for each deployed
* application. The unique key for each web application will be the
* context path to which it is attached.
*
* @author Craig R. McClanahan
* @version $Revision: 1.6 $ $Date: 2002/04/09 23:48:21 $
*/
public interface Deployer {
/**
* The ContainerEvent event type sent when a new application is
* being installed by <code>install()</code>, before it has been
* started.
*/
public static final String PRE_INSTALL_EVENT = "pre-install";
/**
* The ContainerEvent event type sent when a new application is
* installed by <code>install()</code>, after it has been started.
*/
public static final String INSTALL_EVENT = "install";
/**
* The ContainerEvent event type sent when an existing application is
* removed by <code>remove()</code>.
*/
public static final String REMOVE_EVENT = "remove";
/**
* Return the name of the Container with which this Deployer is
* associated.
*/
public String getName();
/**
* Install a new web application, whose web application archive is at
* the specified URL, into this container with the specified context.
* path. A context path of "" (the empty string) should be used for
* the root application for this container. Otherwise, the context
* path must start with a slash.
* <p>
* If this application is successfully installed, a ContainerEvent of
* type <code>INSTALL_EVENT</code> will be sent to all registered
* listeners,
* with the newly created <code>Context</code> as an argument.
*
* @param contextPath The context path to which this application
* should be installed (must be unique)
* @param war A URL of type "jar:" that points to a WAR file, or type
* "file:" that points to an unpacked directory structure containing
* the web application to be installed
*
* @exception IllegalArgumentException if the specified context path
* is malformed (it must be "" or start with a slash)
* @exception IllegalStateException if the specified context path
* is already attached to an existing web application
* @exception IOException if an input/output error was encountered
* during installation
*/
public void install(String contextPath, URL war) throws IOException;
/**
* <p>Install a new web application, whose context configuration file
* (consisting of a <code><Context></code> element) and web
* application archive are at the specified URLs.</p>
*
* <p>If this application is successfully installed, a ContainerEvent
* of type <code>INSTALL_EVENT</code> will be sent to all registered
* listeners, with the newly created <code>Context</code> as an
* argument.
* </p>
*
* @param config A URL that points to the context configuration file
* to be used for configuring the new Context
* @param war A URL of type "jar:" that points to a WAR file, or type
* "file:" that points to an unpacked directory structure containing
* the web application to be installed
*
* @exception IllegalArgumentException if one of the specified URLs
* is null
* @exception IllegalStateException if the context path specified in
* the context configuration file is already attached to an existing
* web application
* @exception IOException if an input/output error was encountered
* during installation
*/
public void install(URL config, URL war) throws IOException;
/**
* Return the Context for the deployed application that is associated
* with the specified context path (if any); otherwise return
* <code>null</code>.
*
* @param contextPath The context path of the requested web
* application
*/
public Context findDeployedApp(String contextPath);
/**
* Return the context paths of all deployed web applications in this
* Container. If there are no deployed applications, a zero-length
* array is returned.
*/
public String[] findDeployedApps();
/**
* Remove an existing web application, attached to the specified
* context path. If this application is successfully removed, a
* ContainerEvent of type <code>REMOVE_EVENT</code> will be sent to
* all registered listeners, with the removed <code>Context</code> as
* an argument.
*
* @param contextPath The context path of the application to be
* removed
*
* @exception IllegalArgumentException if the specified context path
* is malformed (it must be "" or start with a slash)
* @exception IllegalArgumentException if the specified context path
* does not identify a currently installed web application
* @exception IOException if an input/output error occurs during
* removal
*/
public void remove(String contextPath) throws IOException;
/**
* Start an existing web application, attached to the specified
* context path. Only starts a web application if it is not running.
*
* @param contextPath The context path of the application to be
* started
* @exception IllegalArgumentException if the specified context path
* is malformed (it must be "" or start with a slash)
* @exception IllegalArgumentException if the specified context path
* does not identify a currently installed web application
* @exception IOException if an input/output error occurs during
* startup
*/
public void start(String contextPath) throws IOException;
/**
* Stop an existing web application, attached to the specified
* context path. Only stops a web application if it is running.
*
* @param contextPath The context path of the application to be
* stopped
* @exception IllegalArgumentException if the specified context path
* is malformed (it must be "" or start with a slash)
* @exception IllegalArgumentException if the specified context path
* does not identify a currently installed web application
* @exception IOException if an input/output error occurs while
* stopping the web application
*/
public void stop(String contextPath) throws IOException;
}
StandardHost 使用一org.apache.catalina.core.StandardHostDeployer 类型的帮助类来部署和安装 web 应用程序。下面代码片段展示了StandardHost 如何使用 StandardDeployer 实例来部署和安装 web 应用程序:
/**
* The <code>Deployer</code> to whom we delegate application
* deployment requests.
*/
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 remove(String contextPath) throws IOException {
deployer.remove(contextPath);
}
public void start(String contextPath) throws IOException {
deployer.start(contextPath);
}
public void stop(String contextPath) throws IOException {
deployer.stop(contextPath);
}
StandardHostDeployer 将会在下一节讨论。
18.3 StandardHostDeployer类
org.apache.catalina.core.StandardHostDeployer 类是 StandardHost 的一个辅助类,用来部署和安装 web 应用程序。StandardHostDeployer 被设计成由StandardHost 使用,它的构造函数接受一个 StandardHost 实例:
public StandardHostDeployer(StandardHost host) {
super();
this.host = host;
}
该类的方法将在下面的子章节中介绍。
18.3.1 装载描述符
StandardHostDeployer 类有两个 install()方法。第一个也是本小节介绍的用于安装描述符(descriptor)。第二个在下一小节介绍用于安装 WAR 文件和目录。
用于安装描述符的 install() 方法如 Listing18.12 所示。StandardHost 实例在HostConfig 通过 deployDecriptors()来调用 install() 方法。
Listing 18.12: The install method for installing descriptors
public synchronized void install(URL config, URL war) throws IOException {
// Validate the format and state of our arguments
if (config == null)
throw new IllegalArgumentException
(sm.getString("standardHost.configRequired"));
if (!host.isDeployXML())
throw new IllegalArgumentException
(sm.getString("standardHost.configNotAllowed"));
// Calculate the document base for the new web application (if needed)
String docBase = null; // Optional override for value in config file
if (war != null) {
String url = war.toString();
host.log(sm.getString("standardHost.installingWAR", url));
// Calculate the WAR file absolute pathname
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));
}
// Install the new web application
this.context = null;
this.overrideDocBase = docBase;
InputStream stream = null;
try {
stream = config.openStream();
Digester digester = createDigester();
digester.setDebug(host.getDebug());
digester.clear();
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) {
;
}
}
}
}
18.3.2 装载WAR和目录
第二个install()方法接收一字符串表示的上下文路径和一个URL来表示的WAR 文件。该 install() 方法如 Listing18.13 所示。
Listing 18.13: The install method for installing a WAR file or a directory
public synchronized void install(String contextPath, URL war)
throws IOException {
// Validate the format and state of our arguments
if (contextPath == null)
throw new IllegalArgumentException
(sm.getString("standardHost.pathRequired"));
if (!contextPath.equals("") && !contextPath.startsWith("/"))
throw new IllegalArgumentException
(sm.getString("standardHost.pathFormat", contextPath));
if (findDeployedApp(contextPath) != null)
throw new IllegalStateException
(sm.getString("standardHost.pathUsed", contextPath));
if (war == null)
throw new IllegalArgumentException
(sm.getString("standardHost.warRequired"));
// Calculate the document base for the new web application
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));
// Install the new web application
try {
Class clazz = Class.forName(host.getContextClass());
Context context = (Context) clazz.newInstance();
context.setPath(contextPath);
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);
host.addChild(context);
host.fireContainerEvent(INSTALL_EVENT, context);
} catch (Exception e) {
host.log(sm.getString("standardHost.installError", contextPath),
e);
throw new IOException(e.toString());
}
}
注意一旦一个上下文被安装,它将会被添加到 StandardHost。
18.3.3 启动上下文
StandardHostDeployer 的 start()方法用于启动一个上下文。它如 Listing18.14所示。
Listing 18.14: The start method of the StandardHostDeployer class
public void start(String contextPath) throws IOException {
// Validate the format and state of our arguments
if (contextPath == null)
throw new IllegalArgumentException
(sm.getString("standardHost.pathRequired"));
if (!contextPath.equals("") && !contextPath.startsWith("/"))
throw new IllegalArgumentException
(sm.getString("standardHost.pathFormat", contextPath));
Context context = findDeployedApp(contextPath);
if (context == null)
throw new IllegalArgumentException
(sm.getString("standardHost.pathMissing", contextPath));
host.log("standardHost.start " + contextPath);
try {
((Lifecycle) context).start();
} catch (LifecycleException e) {
host.log("standardHost.start " + contextPath + ": ", e);
throw new IllegalStateException
("standardHost.start " + contextPath + ": " + e);
}
}
18.3.4 停止上下文
要停止一个上下文,可以使用如Listing18.15所示StandardHostDeployer类中的 stop() 方法。
Listing 18.15: The stop method in the StandardHostDeployer class
public void stop(String contextPath) throws IOException {
// Validate the format and state of our arguments
if (contextPath == null)
throw new IllegalArgumentException
(sm.getString("standardHost.pathRequired"));
if (!contextPath.equals("") && !contextPath.startsWith("/"))
throw new IllegalArgumentException
(sm.getString("standardHost.pathFormat", contextPath));
Context context = findDeployedApp(contextPath);
if (context == null)
throw new IllegalArgumentException
(sm.getString("standardHost.pathMissing", contextPath));
host.log("standardHost.stop " + contextPath);
try {
((Lifecycle) context).stop();
} catch (LifecycleException e) {
host.log("standardHost.stop " + contextPath + ": ", e);
throw new IllegalStateException
("standardHost.stop " + contextPath + ": " + e);
}
}
18.4 小结
部署器用于部署和安装 web 应用,由 org.apache.catalina.Deployer 表示。StandardHost 类实现了 Deployer接口,这样它就是一个可以部署 web 应用的特殊容器。StandardHost 使用一个帮助类org.apache.catalina.core.StandardHostDeployer来完成 web 应用的部署和安装。StandardHostDeployer 类提供了部署和安装应用,以及启动和停止上下文容器的代码。