How tomcat works——18 部署

概述

要使得一个 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>&lt;Context&gt;</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 类提供了部署和安装应用,以及启动和停止上下文容器的代码。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值