tomcat(18)部署器

【0】README

-1)先上干货:本文重点分析了tomcat 如何部署WAR文件的项目形式 以及 普通文件夹的项目形式;不管是WAR文件 还是 普通文件夹的项目形式,在tomcat中,它们都是Context容器;(Bingo)

0)本文部分文字描述转自“how tomcat works”,旨在学习“tomcat(18)部署器”的相关知识;

1)intro:要使用一个web 应用程序,必须要将表示该应用程序的 Context实例部署到一个Host 实例中;(干货——要使用一个web
应用程序,必须要将表示该应用程序的 Context实例部署到一个Host 实例中)

2)在tomcat中的部署方式:Context实例可以用WAR 文件的形式来部署,也可以将整个web 应用程序copy 到 tomcat安装目录下的 webapp 下;

Supplement) 在tomcat4 和 tomcat5中是,使用了两个应用程序来管理tomcat 和 部署tomcat中的 web应用程序,分别是 manager 和 admin 应用程序;

s1)这两个应用程序所使用到的类文件都位于 %CATALINA_HOME%/server/webapps 目录下,并且分别使用了两个描述符文件:manager.xml and admin.xml;(干货——描述符文件:manager.xml
and admin.xml)

s2)在tomcat4中, 在%CATALINA_HOME%/server/webapps 目录下,有3个描述符文件;

s3)在tomcat5中,分别位于 %CATALINA_HOME%/server/webapps/admin 目录 和 %CATALINA_HOME%/server/webapps/manager 目录下;

3)intro to 部署器:部署器是 org.apache.catalina.Deployer 接口的实例,部署器是与一个Host容器相关联, 用来安装 Context实例;

3.1)安装Context实例的意思是:创建一个 StandardContext实例,将该实例添加到Host实例中;创建的Context实例会随其父容器——Host实例一起启动。

3.2)部署器也可以单独地启动和关闭Context实例;

public final class Bootstrap {
  public static void main(String[] args) {
    System.setProperty("catalina.base", System.getProperty("user.dir"));
    Connector connector = new HttpConnector();

    Context context = new StandardContext();
    // StandardContext's start method adds a default mapper
    context.setPath("/app1");
    context.setDocBase("app1");
    LifecycleListener listener = new ContextConfig();
    ((Lifecycle) context).addLifecycleListener(listener);

    Host host = new StandardHost();
    host.addChild(context); //highlight line.将StandardContext实例添加到 Host实例中.
    host.setName("localhost");
    host.setAppBase("webapps");

    Loader loader = new WebappLoader();
    context.setLoader(loader);
    connector.setContainer(host);
    try {
      connector.initialize();
      ((Lifecycle) connector).start();
      ((Lifecycle) host).start();
      Container[] c = context.findChildren();
      int length = c.length;
      for (int i=0; i<length; i++) {
        Container child = c[i];
        System.out.println(child.getName());
      }

      // make the application wait until we press a key.
      System.in.read();
      ((Lifecycle) host).stop();
    }
    catch (Exception e) {
      e.printStackTrace();
    }
  }
}

【1】 部署一个web 应用程序

1)在实际环境中,如何将Context实例添加到 Host容器呢?答案在于:StandardHost实例中使用的org.apache.catalina.startup.HostConfig
类的 生命周期监听器;(干货——HostConfig 类似于StandardContext的ContextConfig监听器)

2)org.apache.catalina.startrup.Catalina类是启动类,使用Digester对象来解析server.xml文件,将其中的XML 元素 转换为 java对象:Catalina类定义了 createStartDigester()方法来为 Digester对象添加规则。createStartDigester()方法中的其中一行如下所示:

digester.addRuleSet(new HostRuleSet("Server/Service/Engine/"));

对以上代码的分析(Analysis):HostRuleSet extends RuleSetBase类,HostRuleSet必须提供addRuleInstance()方法的实现,需要在该方法中定义 RuleSet的规则;下面是 HostRuleSet.addRuleInstance()方法:(因为父类 RuleSetBase
实现了RuleSet接口,提供了getNamespaceURI的具体实现,而addRuleInstances()方法声明为了抽象方法,具体参见 tomcat(15)Digester库 中章节“【1.5】”)

public void addRuleInstances(Digester digester) { //org.apache.catalina.startup.HosRuleSet.addRuleInstances().
        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", // highlight line.
                          "hostConfigClass"));

        //......
    }

对以上代码的分析(Analysis):

A1)当 在 server.xml 文件中遇到符合 "Server/Service/Engine/Host" 模式的标签时:会创建 org.apache.catalina.startup.HostConfig 类的一个实例,并将其添加到 Host实例中,作为生命周期监听器;

A2)that‘s to say, HostConfig 类会处理 StandardHost.start()方法 和 stop()方法的触发事件;

<?xml version='1.0' encoding='utf-8'?> <!--conf/server.xml源码如下 -->
<!--
  Licensed to the Apache Software Foundation (ASF) under one or more
  contributor license agreements.  See the NOTICE file distributed with
  this work for additional information regarding copyright ownership.
  The ASF licenses this file to You under the Apache License, Version 2.0
  (the "License"); you may not use this file except in compliance with
  the License.  You may obtain a copy of the License at

      http://www.apache.org/licenses/LICENSE-2.0

  Unless required by applicable law or agreed to in writing, software
  distributed under the License is distributed on an "AS IS" BASIS,
  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  See the License for the specific language governing permissions and
  limitations under the License.
-->
<!-- Note:  A "Server" is not itself a "Container", so you may not
     define subcomponents such as "Valves" at this level.
     Documentation at /docs/config/server.html
 -->
<Server port="8005" shutdown="SHUTDOWN">
  <!-- Security listener. Documentation at /docs/config/listeners.html
  <Listener className="org.apache.catalina.security.SecurityListener" />
  -->
  <!--APR library loader. Documentation at /docs/apr.html -->
  <Listener className="org.apache.catalina.core.AprLifecycleListener" SSLEngine="on" />
  <!--Initialize Jasper prior to webapps are loaded. Documentation at /docs/jasper-howto.html -->
  <Listener className="org.apache.catalina.core.JasperListener" />
  <!-- Prevent memory leaks due to use of particular java/javax APIs-->
  <Listener className="org.apache.catalina.core.JreMemoryLeakPreventionListener" />
  <Listener className="org.apache.catalina.mbeans.GlobalResourcesLifecycleListener" />
  <Listener className="org.apache.catalina.core.ThreadLocalLeakPreventionListener" />

  <!-- Global JNDI resources
       Documentation at /docs/jndi-resources-howto.html
  -->
  <GlobalNamingResources>
    <!-- Editable user database that can also be used by
         UserDatabaseRealm to authenticate users
    -->
    <Resource name="UserDatabase" auth="Container"
              type="org.apache.catalina.UserDatabase"
              description="User database that can be updated and saved"
              factory="org.apache.catalina.users.MemoryUserDatabaseFactory"
              pathname="conf/tomcat-users.xml" />
  </GlobalNamingResources>

  <!-- A "Service" is a collection of one or more "Connectors" that share
       a single "Container" Note:  A "Service" is not itself a "Container",
       so you may not define subcomponents such as "Valves" at this level.
       Documentation at /docs/config/service.html
   -->
  <Service name="Catalina">

    <!--The connectors can use a shared executor, you can define one or more named thread pools-->
    <!--
    <Executor name="tomcatThreadPool" namePrefix="catalina-exec-"
        maxThreads="150" minSpareThreads="4"/>
    -->

    <!-- A "Connector" represents an endpoint by which requests are received
         and responses are returned. Documentation at :
         Java HTTP Connector: /docs/config/http.html (blocking & non-blocking)
         Java AJP  Connector: /docs/config/ajp.html
         APR (HTTP/AJP) Connector: /docs/apr.html
         Define a non-SSL HTTP/1.1 Connector on port 8080
    -->
    <Connector port="8888" protocol="HTTP/1.1"
               connectionTimeout="20000"
               redirectPort="8443" />
    <!-- A "Connector" using the shared thread pool-->
    <!--
    <Connector executor="tomcatThreadPool"
               port="8080" protocol="HTTP/1.1"
               connectionTimeout="20000"
               redirectPort="8443" />
    -->
    <!-- Define a SSL HTTP/1.1 Connector on port 8443
         This connector uses the JSSE configuration, when using APR, the
         connector should be using the OpenSSL style configuration
         described in the APR documentation -->
    <!--
    <Connector port="8443" protocol="HTTP/1.1" SSLEnabled="true"
               maxThreads="150" scheme="https" secure="true"
               clientAuth="false" sslProtocol="TLS" />
    -->

    <!-- Define an AJP 1.3 Connector on port 8009 -->
    <Connector port="8009" protocol="AJP/1.3" redirectPort="8443" />

    <!-- An Engine represents the entry point (within Catalina) that processes
         every request.  The Engine implementation for Tomcat stand alone
         analyzes the HTTP headers included with the request, and passes them
         on to the appropriate Host (virtual host).
         Documentation at /docs/config/engine.html -->

    <!-- You should set jvmRoute to support load-balancing via AJP ie :
    <Engine name="Catalina" defaultHost="localhost" jvmRoute="jvm1">
    -->
    <Engine name="Catalina" defaultHost="localhost">

      <!--For clustering, please take a look at documentation at:
          /docs/cluster-howto.html  (simple how to)
          /docs/config/cluster.html (reference documentation) -->
      <!--
      <Cluster className="org.apache.catalina.ha.tcp.SimpleTcpCluster"/>
      -->

      <!-- Use the LockOutRealm to prevent attempts to guess user passwords
           via a brute-force attack -->
      <Realm className="org.apache.catalina.realm.LockOutRealm">
        <!-- This Realm uses the UserDatabase configured in the global JNDI
             resources under the key "UserDatabase".  Any edits
             that are performed against this UserDatabase are immediately
             available for use by the Realm.  -->
        <Realm className="org.apache.catalina.realm.UserDatabaseRealm"
               resourceName="UserDatabase"/>
      </Realm>

      <Host name="localhost"  appBase="webapps"
            unpackWARs="true" autoDeploy="true">

        <!-- SingleSignOn valve, share authentication between web applications
             Documentation at: /docs/config/valve.html -->
        <!--
        <Valve className="org.apache.catalina.authenticator.SingleSignOn" />
        -->

        <!-- Access log processes all example.
             Documentation at: /docs/config/valve.html
             Note: The pattern used is equivalent to using pattern="common" -->
        <Valve className="org.apache.catalina.valves.AccessLogValve" directory="logs"
               prefix="localhost_access_log." suffix=".txt"
               pattern="%h %l %u %t "%r" %s %b" />

      </Host>
    </Engine>
  </Service>
</Server>

Attention)不要问我,为什么StandardHost的生命周期监听器是 HostConfig?详情参见 tomcat(17)启动tomcat,结合org.apache.catalina.startup.Catalina.createStartDigester()
所设定的规则 和 conf/server.xml 的代码,你就懂的。

3)HostConfig.lifecycleEvent()方法的源代码如下:因为HostConifg的实例是 StandardHost实例的监听器,每当StandardHost实例启动或关闭时,都会调用 lifecycleEvent()方法;

 public void lifecycleEvent(LifecycleEvent event) { //org.apache.catalina.startup.HostConfig.lifecycleEvent().
        // 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(); // highlight line.
        else if (event.getType().equals(Lifecycle.STOP_EVENT))
            stop(); // highlight line.
    }

对以上代码的分析(Analysis):如果变量host是 StandardHost的实例,则调用
setDeployXML方法,setLiveDeploy方法,setUnpackWARs方法;

A1)setDeployXML方法:指明了Host实例是否需要部署一个 Context实例的描述符文件;

 public void setDeployXML(boolean deployXML) {
        this.deployXML= deployXML;
    }

A2)setLiveDeploy方法:指明了Host实例 是否需要周期性检查一个新的 部署;

public void setLiveDeploy(boolean liveDeploy) {
        this.liveDeploy = liveDeploy;
    }

A3)setUnpackWARs方法:指定是要将WAR 文件形式的web 应用程序解压缩;

 public void setUnpackWARs(boolean unpackWARs) {
        this.unpackWARs = unpackWARs;
    }

4)lifecycleEvent方法 调用start()方法;

 protected void start() { //org.apache.catalina.startup.HostConfig.start().
        if (debug >= 1)
            log(sm.getString("hostConfig.start"));
        if (host.getAutoDeploy()) {
            deployApps();
        }
        if (isLiveDeploy()) {
            threadStart();
        }
    }
    protected void stop() {
        if (debug >= 1)
            log(sm.getString("hostConfig.stop"));
        threadStop();
        undeployApps();
    }

对以上代码的分析(Analysis):

A1)若autoDeploy为true时: start()方法调用deployApps()方法;

protected void deployApps() {
        if (!(host instanceof Deployer))
            return;
        if (debug >= 1)
            log(sm.getString("hostConfig.deploying"));
        File appBase = appBase(); // highlight line.
        if (!appBase.exists() || !appBase.isDirectory())
            return;
        String files[] = appBase.list();
        deployDescriptors(appBase, files);
        deployWARs(appBase, files);
        deployDirectories(appBase, files);
    }
 protected File appBase() {
        File file = new File(host.getAppBase());
        if (!file.isAbsolute())
            file = new File(System.getProperty("catalina.base"),
                            host.getAppBase());
        return (file);
    }

A2)若liveDeploy为true时:start()方法调用threadStart()方法;

5)deployApps方法源代码如下:

protected void deployApps() {  //org.apache.catalina.startup.HostConfig.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); //highlight line.
        deployWARs(appBase, files); //highlight line.
        deployDirectories(appBase, files); //highlight line.
    }

对以上代码的分析(Analysis):

A1)该方法会获取host的实例的appBase属性的值,默认为 webapps 的值(参见 server.xml);部署进程会将 %CATALINA_HOME%/webapps 目录下的所有目录都看做是 web 应用程序的目录来执行部署工作。此外,该目录中所有的WAR 文件和描述符文件也都会进行部署;(干货——想想以前总是要把项目打个war
包,放到webapps 目录下,我在这里找到了答案。)

A2)deployApps()方法会调用其他3个方法:deployDescriptors方法,deployWARs方法,deployDirectories方法;

A2.1)deployDescriptors方法:

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);
                }
            }
        }
    }

A2.2)deployWARs方法:

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 = ExpandWar.expand(host,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);
                    }
                }
            }
        }
    }

A2.3)deployDirectories方法:

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]);
                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);
                }
            }
        }
    }

【1.1】 部署一个描述符

1)可以编写一个  XML 文件来描述 Context对象;如,在tomcat4和tomcat5中的admin 和 manager 应用中就分别使用了如下两个XML 文件;(tomcat distribution list http://archive.apache.org/dist/tomcat/

<!-- tomcat4下的admin.xml 文件(\container\webapps\admin)-->
<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>
<!-- tomcat5下的 manager.xml 文件(\container\webapps\manager -->
<Context docBase="${catalina.home}/server/webapps/manager"
         privileged="true" antiResourceLocking="false" antiJARLocking="false" useHttpOnly="true">
  <!-- Link to the user database we will get roles from -->
  <ResourceLink name="users" global="UserDatabase"
                type="org.apache.catalina.UserDatabase"/>
</Context>

Attention)这两个描述符都有一个Context 元素。Context元素中的 docBase属性的值分别为 %CATALINA_HOME%/server/webapps/admin and %CATALINA_HOME%/server/webapps/manager,这表明,admin 应用程序和manager应用程序并没有部署到默认的地方;

2)HostConfig类使用了 deployDescriptor()方法来部署XML 文件。在tomcat4中, 这些文件位于 %CATALINA_HOME%/webapps 目录下;在tomcat5中, 位于 %CATALINA_HOME%/server/webapps 子目录下;

【1.2】部署一个WAR文件

1)intro:可以将web 应用程序以一个 WAR形式的文件来部署。HostConfig.deployWARs()方法 将位于 %CATALINA_HOME%/webapps 目录下的任何WAR文件进行部署;

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 = ExpandWar.expand(host,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);
                    }

                }

            }

        }
    }

【1.3】部署一个目录

1)intro:可以直接将web 应用程序的整个目录copy 到 %CATALINA_HOME%/webapps 目录下来完成web 应用程序的部署。HostConfig.deployDirectories()方法完成对这些web 应用程序的部署;

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);
                }

            }
        }
    }

Attention)上面两种图,我们分析了 HostConfig 如何部署WAR 文件和 普通文件夹,其实它们都是项目的打包形式,现在回想起以前部署 java web 项目到 tomcat的时候,我们为什么要那样操作(将项目文件夹编译后copy 到webapps dir下,也可以将项目打个WAR包进行部署),这两张图是不是给出了很好的诠释。(Bingo)

【1.4】动态部署

1)intro:StandardHost实例使用HostConfig对象作为生命周期监听器,当StandardHost对象启动时,它的start()方法会触发一个START事件;

2)为了响应START 事件:HostConfig.lifecycleEvent()方法和 HostConfig中的事件处理事件调用 start()方法;

3)在tomcat4中,在start()方法的最后一行,当isliveDeploy==true时(default case下,该属性为true),start()方法会调用 threadStart()方法:

protected void start() {  // org.apache.catalina.startup.HostConfig.start().
        if (debug >= 1)
            log(sm.getString("hostConfig.start"));
        if (host.getAutoDeploy()) {
            deployApps();
        }
        if (isLiveDeploy()) { //highlight line.
            threadStart();
        }
    }

4)threadStart()方法会派生一个新线程并调用run()方法。

protected void threadStart() {  // org.apache.catalina.startup.HostConfig.threadStart().
        // Has the background thread already been started?
        if (thread != null)
            return;
        // Start the background thread
        if (debug >= 1)
            log(" Starting background thread");
        threadDone = false;
        threadName = "HostConfig[" + host.getName() + "]";
        thread = new Thread(this, threadName);
        thread.setDaemon(true);
        thread.start();
    }
public void run() {  //org.apache.catalina.startup.HostConfig.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");
    }

对以上代码的分析(Analysis):threadSleep()方法:会使该线程休眠一段时间;

 protected void threadSleep() {
        try {
            Thread.sleep(checkInterval * 1000L);
        }  // ......
    }

5)在tomcat5中,HostConfig 类没有再使用专用线程来执行检查工作,而是由StandardHost.backgroundProcess()方法周期性地触发一个 "check"事件;

Attention)backgroundProcess()方法会由一个专门的线程来周期性地调用,用来执行容器中所有的后台处理工作;

public void backgroundProcess() {
    lifecycle.fireLifecycleEvent("check", null);
}
public void lifecycleEvent(LifecycleEvent event) { //org.apache.catalina.startup.HostConfig.lifecycleEvent().
        if (event.getType().equals(Lifecycle.PERIODIC_EVENT))
            check(); // highlight line.

        // Identify the host we are associated with
        try {
            host = (Host) event.getLifecycle();
            if (host instanceof StandardHost) {
                setDeployXML(((StandardHost) host).isDeployXML());
                setUnpackWARs(((StandardHost) host).isUnpackWARs());
                setXmlNamespaceAware(((StandardHost) host).getXmlNamespaceAware());
                setXmlValidation(((StandardHost) host).getXmlValidation());
            }
        } catch (ClassCastException e) {
            log.error(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();
    }
protected void check() {  //org.apache.catalina.startup.HostConfig.check().
        if (host.getAutoDeploy()) {
            // Check for resources modification to trigger redeployment
            DeployedApplication[] apps =
                (DeployedApplication[]) deployed.values().toArray(new DeployedApplication[0]);
            for (int i = 0; i < apps.length; i++) {
                if (!isServiced(apps[i].name))
                    checkResources(apps[i]);
            }
            // Hotdeploy applications
            deployApps();
            // ......
            checkContextLastModified();
        }
    }

对以上代码的分析(Analysis):

A1)check方法会调用 deployApps()方法,该方法都会完成web 应用程序的部署工作,该方法会调用 deployDescriptors方法,deployWARs方法,deployDirectories方法;

 protected void deployApps() {  //org.apache.catalina.startup.HostConfig.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);
    }

A2)check()方法还会调用checkContextLastModified方法,后者遍历所有已经部署的Context,检查web.xml 文件的 时间戳,以及每个Context中 WEB-INF 目录下的内容;如果某个检查的资源被修改了,会重新启动相应的Context实例。此外,该方法还会检查所有已经部署的WAR文件的时间戳,如果某个应用程序的WAR
文件被修改了,会重新对该应用程序进行部署;

【2】Deployer接口

1)intro:部署器是 org.apache.catalina.Deployer 接口的实例;

2)StandardHost类实现了 Deployer 接口 :所以,StandardHost 实例也是一个部署器,它也是一个容器,web 应用可以部署到其中,或从其中取消部署;

public class StandardHost  extends ContainerBase implements Deployer, Host {
//......

3)Deployer接口的源代码如下:

public interface Deployer  {  //org.apache.catalina.Deployer.
    public static final String PRE_INSTALL_EVENT = "pre-install";
    public static final String INSTALL_EVENT = "install";
    public static final String REMOVE_EVENT = "remove";
    public String getName();
    public void install(String contextPath, URL war) throws IOException;
    public void install(URL config, URL war) throws IOException;
    public Context findDeployedApp(String contextPath);
    public String[] findDeployedApps();
    public void remove(String contextPath) throws IOException;
    public void remove(String contextPath, boolean undeploy) throws IOException;
    public void start(String contextPath) throws IOException;
    public void stop(String contextPath) throws IOException;
}
// the follwing code is defined in org.apache.catalina.core.StandardHost
 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 remove(String contextPath, boolean undeploy) throws IOException {
        deployer.remove(contextPath,undeploy);
    }
    public void start(String contextPath) throws IOException {
        deployer.start(contextPath);
    }
    public void stop(String contextPath) throws IOException {
        deployer.stop(contextPath);
    }
    protected void addDefaultMapper(String mapperClass) {
        super.addDefaultMapper(this.mapperClass);
    }

【3】StandardHostDeployer类(org.apache.catalina.core.StandardHostDeployer)

1)intro to org.apache.catalina.core.StandardHostDeployer:该类是一个辅助类,帮助完成将web 应用程序部署到StandardHost 实例的工作。StandardHostDeployer实例由 StandardHost 对象来调用,在其构造函数中,会传入 StandardHost
类的一个实例:

public StandardHostDeployer(StandardHost host) {
        super();
        this.host = host;
    }

【3.1】安装一个描述符

1)intro to install()方法:当 HostConfig.deployDescriptors()方法调用了 StandardHost.install()方法后,StandardHost实例调用该 install()方法;然后再调用 StandardHostDeployer.install()方法;

 protected void deployDescriptors(File appBase, String[] files) { // org.apache.catalina.startup.HostConfig.deployDescriptors()
        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); // highlight line.
                } catch (Throwable t) {
                    log(sm.getString("hostConfig.deployDescriptor.error",
                                     files[i]), t);
                }
            }
        }
    }
public synchronized void install(URL config, URL war) throws IOException { // org.apache.catalina.core.StandardHost.install().
        deployer.install(config, war); // highlight line.
    }
 public synchronized void install(URL config, URL war) throws IOException { // org.apache.catalina.core.StandardHostDeployer.install().
        // 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) {
                    ;
                }
            }
        }
    }

【3.2】安装一个WAR 文件或目录

1)org.apache.catalina.core.StandardHostDeployer.install(String contextPath, URL war)方法: 接收一个表示上下文路径的字符串和一个表示WAR 文件的URL;

public synchronized void install(URL config, URL war) throws IOException { // org.apache.catalina.core.StandardHost.install().
        deployer.install(config, war); // highlight line.
    }
 public synchronized void install(URL config, URL war) throws IOException { // org.apache.catalina.core.StandardHostDeployer.install().
        // 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) {
                    ;
                }
            }
        }
    }
 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;
        boolean isWAR = false;
        if (url.startsWith("jar:")) {
            url = url.substring(4, url.length() - 2);
            if (!url.toLowerCase().endsWith(".war")) {
                throw new IllegalArgumentException
                    (sm.getString("standardHost.warURL", url));
            }
            isWAR = true;
        }
        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));
        // Determine if directory/war to install is in the host appBase
        boolean isAppBase = false;
        File appBase = new File(host.getAppBase());
        if (!appBase.isAbsolute())
            appBase = new File(System.getProperty("catalina.base"),
                            host.getAppBase());
        File contextFile = new File(docBase);
        File baseDir = contextFile.getParentFile();
        if (appBase.getCanonicalPath().equals(baseDir.getCanonicalPath())) {
            isAppBase = true;
        }
        // For security, if deployXML is false only allow directories
        // and war files from the hosts appBase
        if (!host.isDeployXML() && !isAppBase) {
            throw new IllegalArgumentException
                (sm.getString("standardHost.installBase", url));
        }
        // Make sure contextPath and directory/war names match when
        // installing from the host appBase
        if (isAppBase && (host.getAutoDeploy() || host.getLiveDeploy())) {
            String filename = contextFile.getName();
            if (isWAR) {
                filename = filename.substring(0,filename.length()-4);
            }
            if (contextPath.length() == 0) {
                if (!filename.equals("ROOT")) {
                    throw new IllegalArgumentException
                        (sm.getString("standardHost.pathMatch", "/", "ROOT"));
                }
            } else if (!filename.equals(contextPath.substring(1))) {
                throw new IllegalArgumentException
                    (sm.getString("standardHost.pathMatch", contextPath, filename));
            }
        }
        // Expand war file if host wants wars unpacked
        if (isWAR && host.isUnpackWARs()) {
            if (contextPath.equals("")) {
                docBase = ExpandWar.expand(host,war,"/ROOT");
            } else {
                docBase = ExpandWar.expand(host,war,contextPath);
            }
        }
        // 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());
        }
    }

Attention)当安装一个Context后,就会将其添加到 StandardHost实例中;

【3.3】启动Context实例

1)org.apache.catalina.core.StandardHostDeployer.start()方法:用于启动Context实例,以下代码给出了start()方法的实现;

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);
        }
    }

【3.4】停止一个Context实例

1)org.apache.catalina.core.StandardHostDeployer.stop()方法用于停止 Context实例,以下代码给出了stop()方法的实现;

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);
        }
    }

Conclusion)

C1)部署器:是用来部署和安装web 应用程序的组件,是org.apache.catalina.Deployer接口的实例;

C2)StandardHost:是Deployer接口的一个实例,使其成为一个 可以向其中部署web 应用程序的特殊容器;

C3)StandardHost类会将部署和安装web 应用程序的任务委托给其辅助类 org.apache.catalina.core.StandardHostDeployer类完成。

C4)StandardHostDeployer类:提供了部署和安装web 应用程序以及启动/ 关闭 Context实例的代码;

时间: 2024-10-06 10:50:51

tomcat(18)部署器的相关文章

tomcat(8)载入器

[0]README 0.0)本文部分描述转自"深入剖析tomcat",旨在学习 tomcat(8)载入器 的基础知识: 0.1)一个标准web 应用程序中的载入器:简单来说就是 tomcat中的载入器: 0.2)servlet容器需要实现一个自定义的载入器,而不能简单地使用系统的类载入器的原因:(干货--为什么servlet容器要实现一个自定义的载入器) 0.2.1)原因1:因为servlet容器不应该完全信任它正在运行的servlet类: 0.2.2)原因2:如果使用系统类的载入器载

tomcat源码解读(1)–tomcat热部署实现原理

tomcat的热部署实现原理:tomcat启动的时候会有启动一个线程每隔一段时间会去判断应用中加载的类是否发生变法(类总数的变化,类的修改),如果发生了变化就会把应用的启动的线程停止掉,清除引用,并且把加载该应用的WebappClassLoader设为null,然后创建一个新的WebappClassLoader来重新加载应用. tomcat中热部署发现类变法之后要做的一系列停止工作的时序图如下: 上面时序图中只把关键的流转步骤画出来了,还有一些细节的处理没有完全画出来,这部分代码的继承的结构还是

如何在tomcat安装部署php项目

java开发者都知道,tomcat是用来部署java web项目的.前几天老k偶然得知PHP/Java Bridge,通过它可以实现在jsp和php之间共享session,详见<如何实现jsp和php共享session>php教程,今天突发奇想,通过PHP/Java Bridge能不能把一个完全用php开发的项目部署到tomcat里,尽管意义不是很大,但对于那些需要在java项目里集成php开发的模块的话还是很有用的.说干就干,我马上去PHP/Java Bridge的官网看了,原来它还真可以用

tomcat的部署及session绑定反代

Tomcat是由Apache软件基金会下属的Jakarta项目开发的一个Servlet容器,按照 Sun Microsystems提供的技术规范,实现了对Servlet和JavaServer Page(JSP) 的支持,并提供了作为Web服务器的一些特有功能,如Tomcat管理和控制平台. 安全局管理和Tomcat阀等.由于Tomcat本身也内含了一个HTTP服务器,它也可 以被视作一个单独的Web服务器.但是,不能将Tomcat和Apache Web服务器混 淆,Apache Web Serv

Tomcat中部署网站和绑定域名

在安装的tomcat的文件夹下有个conf文件夹 下面有个server.xml文件, 1. 使用80端口 默认tomcat用的是8080端口. <Connector port="8080" protocol="HTTP/1.1"               connectionTimeout="20000"               redirectPort="8443" /> 把这个节点的8080修改成80端口

IDEA第二章----配置git、tomcat(热部署)、database,让你的项目跑起来

第一节:下载git客户端,整合idea 由于博主公司用的git版本管理,所以本系列都是基于git版本工具的,当然SVN与git配置类似.git同样支持安装版和解压版,支持各种操作系统,我这里下载的是Windows的解压版. 选择刚才解压后的文件夹,选择cmd文件夹下的git.exe,然后点击Test查看是否连接成功. 注:如果没有配置git客户端,用git地址导入项目会提示你找不到git.exe. 第二节:配置tomcat(热部署稍后会讲到) tomcat下载安装就不在累赘,博主用的是tomca

Tomcat内核之Tomcat的类加载器

跟其他主流的Java Web服务器一样,Tomcat也拥有不同的自定义类加载器,达到对各种资源库的控制.一般来说,Java Web服务器需要解决以下四个问题: ①   同一个Web服务器里,各个Web项目之间各自使用的Java类库要互相隔离. ②   同一个Web服务器里,各个Web项目之间可以提供共享的Java类库. ③   服务器为了不受Web项目的影响,应该使服务器的类库与应用程序的类库互相独立. ④   对于支持JSP的Web服务器,应该支持热插拔(hotswap)功能. 对于以上几个问

linux 环境下tomcat中部署jfinal项目

tomcat中部署jfinal项目 问题现象如下图 问题描述: 我在自己的windows7系统上tomcat下面跑这个项目没有任何问题吗,但是当我把项目上传到linux服务器上的tomcatwebapps目录下后,启动tomcat,服务器死活找不到工程目录. 然后我就郁闷了............. 分析运行环境: 本机: 系统  win7 64 tomcat 8.0.33 jdk版本 1.8.0_51 linux服务器: 系统   Linux version 2.6.32-431.el6.x8

在Tomcat中部署Web应用的方式

在Tomcat中部署Web应用的方式有以下几种: 利用Tomcat的自动部署 将一个Web应用复制到Tomcat的webapps下,系统将会把该应用部署到Tomcat中.这是最简单.最常用的方式. 利用控制台部署 启动Tomcat,在命令行执行窗口中,cd D:\Program Files\Java\apache-tomcat-8.0.23\bin,然后startup.bat 浏览器登录http://localhost:8080 进入Manager App控制台(需要在D:\Program Fi