Tomcat 的部署器

  要使用一个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实例的具体实现。

posted @ 2019-01-02 22:34  陈东的博客  阅读(455)  评论(0编辑  收藏  举报