zoukankan      html  css  js  c++  java
  • 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实例的具体实现。

  • 相关阅读:
    实习日记7.28
    实习日记7.27
    实习总结(第三周)
    实习日记7.26
    实习日记7.25
    实习总结(第二周)
    实习总结(第一周)
    实习日记7.22
    实习日记7.21
    5月4下
  • 原文地址:https://www.cnblogs.com/ChenD/p/Deployer.html
Copyright © 2011-2022 走看看