• How Tomcat Works(二十)


        要使用一个web应用程序,必须要将表示该应用程序的Context实例部署到一个host实例中。在tomcat中,context实例可以用war文件的形式来部署,也可以将整个web应用拷贝到Tomcat安装目录下的webapp下。对于部署的每个web应用程序,可以在其中包含一个描述文件(该文件是可选的),该文件中包含了对context的配置选项,是xml格式的文件。
    注意,tomcat4和tomcat5使用两个应用程序来管理tomcat及其应用的部署,分别是manager应用程序和admin应用程序。这里两个应用程序位于%CATALINA_HOME%/server/webapps目录下,各自有一个描述文件,分别是manager.xml和admin.xml。
        本文将讨论使用一个部署器来部署web应用程序,部署器是org.apache.catalina.Deployer接口的实例。部署器需要与一个host实例相关联,用于部署context实例。部署一个context到host,即创建一个StandardContext实例,并将该context实例添加到host实例中。创建的context实例会随其父容器——host实例而启动(容器的实例在启动时总是会调用其子容器的start方法,除非该该container是一个wrapper实例)。
        本文会先说明tomcat部署器如何部署一个web应用程序,然后描述Deployer接口及其标准实现org.apache.catalina.core.StandardHostDeployer类的工作原理。

    tomcat中在StandardHost中使用了一个生命周期监听器(lifecycle listener)org.apache.catalina.startup.HostConfig来部署应用。
    当调用StandardHost实例的start方法时,会触发START事件,HostConfig实例会响应该事件,调用其start方法,在该方法中会部署并安装指定目录中的所有的web应用程序。

    How Tomcat Works(十八)中,描述了如何使用Digester对象来解析XML文档的内容,但并没有涉及Digester对象中所有的规则,其中被忽略掉的一个主题就是部署器,也就是本文的主题

    在Tomcat中,org.apache.catalina.startup.Catalina类是启动类,使用Digester对象来解析server.xml文件,将其中的xml元素转换为java对象。

    Catalina类中定义了createStartDigester方法来添加规则到Digester中:

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

    org.apache.catalina.startup.HostRuleSet类继承自org.apache.commons.digester.RuleSetBase类,作为RuleSetBase的子类,HostRuleSet提供了addRuleInstances方法实现,该方法定义了RuleSet中的规则(Rule)。

    下面是HostRuleSet类的addRuleInstances方法的实现片段:

    public void addRuleInstances(Digester digester) { 
       digester.addObjectCreate(prefix + "Host", "org.apache.catalina.core.StandardHost", "className"); 
       digester.addSetProperties(prefix + "Host"); 
       digester.addRule(prefix + "Host", new CopyParentClassLoaderRule(digester)); 
       digester.addRule(prefix + "Host", 
    new LifecycleListenerRule (digester, "org.apache.catalina.startup.HostConfig", "hostConfigClass"));

    正如代码中所示,当出现模式Server/Service/Engine/Host时,会创建一个org.apache.catalina.startup.HostConfig实例,并被添加到host,作为一个生命周期监听器。换句话说,HostConfig对象会处理StandardHost对象的start和stop方法触发的事件。

    下面的代码是HostConfig的lifecycleEvent方法实现:

    public void lifecycleEvent(LifecycleEvent event) { 
       // Identify the host we are associated with 
       try { 
         host = (Host) event.getLifecycle(); 
         if (host instanceof StandardHost) { 
           int hostDebug = ((StandardHost) host).getDebug(); 
           if (hostDebug > this.debug) { 
             this.debug = hostDebug; 
           } 
           setDeployXML(((StandardHost) host).isDeployXML()); 
           setLiveDeploy(((StandardHost) host).getLiveDeploy()); 
           setUnpackWARs(((StandardHost) host).isUnpackWARs()); 
         } 
       } catch (ClassCastException e) { 
         log(sm.getString("hostConfig.cce", event.getLifecycle()), e); 
         return; 
       } 
     
       // Process the event that has occurred 
       if (event.getType().equals(Lifecycle.START_EVENT)) 
         start (); 
       else if (event.getType().equals(Lifecycle.STOP_EVENT)) 
         stop(); 
    }

    如果变量host指向的对象是一个org.apache.catalina.core.StandardHost实例,会调用setDeployXML方法,setLiveDeploy方法和setUnpackWARs方法:

    setDeployXML(((StandardHost) host).isDeployXML()); 
    setLiveDeploy(((StandardHost) host).getLiveDeploy());  
    setUnpackWARs(((StandardHost) host).isUnpackWARs());

    StandardHost类的isDeployXML方法指明host是否要部署一个描述文件,默认为true。liveDeploy属性指明host是否要周期性的检查是否有新的应用部署。unpackWARs属性指明host是否要解压缩war文件。
    接收到START事件后,HostConfig的lifecycleEvent方法会调用start方法来部署web应用:

    protected void start() { 
       if (debug >= 1) 
         log(sm.getString("hostConfig.start")); 
       if (host.getAutoDeploy()) { 
         deployApps(); 
       } 
       if (isLiveDeploy ()) { 
         threadStart(); 
       } 
    }

    当autoDeploy属性值为true时(默认为true),则start方法会调用deployApps方法。此外,若liveDeploy属性为true(默认为true),则该方法会开一个新线程调用threadStart方法。
    deployApps方法从host中获取appBase属性值(默认为webapps),该值定义于server.xml文件中。部署进程会将%CATALINE_HOME%/webapps目录下的所有目录看做为Web应用程序的目录来执行部署工作。此外,该目录下找到的war文件和描述文件也会被部署。

    deployApps方法实现如下:

    protected void deployApps() { 
       if (!(host instanceof Deployer))      
    return; 
       if (debug >= 1) 
         log(sm.getString("hostConfig.deploying")); 
       File appBase = appBase(); 
       if (!appBase.exists() || !appBase.isDirectory()) 
         return; 
       String files[] = appBase.list(); 
       deployDescriptors(appBase, files); 
       deployWARs(appBase, files); 
       deployDirectories(appBase, files); 
    }

    deployApps方法会调用其他三个方法,deployDescriptors,deployWARs和deployDirectories。对于所有方法,deployApps方法会传入appBase对象和appBase下所有的文件名的数组形式。context实例是通过其路径来标识的,所有的context必须有其唯一路径。已经被部署的contex实例t会被添加到HostConfig对象中已经部署的ArrayList中。因此,在部署一个context实例之前,deployDescriptors,deployWARs和deployDirectories方法必须确保已部署ArrayList中的没有相同路径的context实例。
    注意,deployDescriptors,deployWARs和deployDirectories三个方法的调用顺序是固定的

    下面方法为部署描述符:

    /**
         * Deploy XML context descriptors.
         */
        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);
                    }
    
                }
    
            }
    
        }

    部署WAR文件:

    /**
         * Deploy WAR files.
         */
        protected void deployWARs(File appBase, String[] files) {
    
            for (int i = 0; i < files.length; i++) {
    
                if (files[i].equalsIgnoreCase("META-INF"))
                    continue;
                if (files[i].equalsIgnoreCase("WEB-INF"))
                    continue;
                if (deployed.contains(files[i]))
                    continue;
                File dir = new File(appBase, files[i]);
                if (files[i].toLowerCase().endsWith(".war")) {
    
                    deployed.add(files[i]);
    
                    // Calculate the context path and make sure it is unique
                    String contextPath = "/" + files[i];
                    int period = contextPath.lastIndexOf(".");
                    if (period >= 0)
                        contextPath = contextPath.substring(0, period);
                    if (contextPath.equals("/ROOT"))
                        contextPath = "";
                    if (host.findChild(contextPath) != null)
                        continue;
    
                    if (isUnpackWARs()) {
    
                        // Expand and deploy this application as a directory
                        log(sm.getString("hostConfig.expand", files[i]));
                        try {
                            URL url = new URL("jar:file:" +
                                              dir.getCanonicalPath() + "!/");
                            String path = expand(url);
                            url = new URL("file:" + path);
                            ((Deployer) host).install(contextPath, url);
                        } catch (Throwable t) {
                            log(sm.getString("hostConfig.expand.error", files[i]),
                                t);
                        }
    
                    } else {
    
                        // Deploy the application in this WAR file
                        log(sm.getString("hostConfig.deployJar", files[i]));
                        try {
                            URL url = new URL("file", null,
                                              dir.getCanonicalPath());
                            url = new URL("jar:" + url.toString() + "!/");
                            ((Deployer) host).install(contextPath, url);
                        } catch (Throwable t) {
                            log(sm.getString("hostConfig.deployJar.error",
                                             files[i]), t);
                        }
    
                    }
    
                }
    
            }
    
        }

    也可以直接将Web应用程序整个目录复制到%CATALINA_HOME%/webapps目录下,部署目录:

    /**
         * Deploy directories.
         */
        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);
                    }
    
                }
    
            }
    
        }

    正如前面描述的, 如果变量liveDeploy的值为true,start方法会调用threadStart()方法

    if (isLiveDeploy()) {
                threadStart();
            }

    threadStart()方法会派生一个新线程并调用run()方法,run()方法会定期检查是否有新应用要部署,或已部署的Web应用程序的web.xml是否有修改

    下面的run()方法的实现(HostConfig类实现了java.lang.Runnable接口)

    /**
         * The background thread that checks for web application autoDeploy
         * and changes to the web.xml config.
         */
        public void run() {
    
            if (debug >= 1)
                log("BACKGROUND THREAD Starting");
    
            // Loop until the termination semaphore is set
            while (!threadDone) {
    
                // Wait for our check interval
                threadSleep();
    
                // Deploy apps if the Host allows auto deploying
                deployApps();
    
                // Check for web.xml modification
                checkWebXmlLastModified();
    
            }
    
            if (debug >= 1)
                log("BACKGROUND THREAD Stopping");
    
        }

    部署器用org.apache.catalina.Deployer接口表示,StandardHost实现了 Deployer接口,因此,StandardHost也是一个部署器,它是一个容器,Web应用可以部署到其中,或从中取消部署

    下面是Deployer接口的定义:

    /* public interface Deployer extends Container { */
    public interface 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 start(String contextPath) throws IOException;
        
        public void stop(String contextPath) throws IOException;
    
    }

    StandardHost类使用一个辅助类(org.apache.catalina.core.StandardHostDeployer,与StandardHost类都实现了Deployer接口) 来完成部署与安装Web应用程序的相关任务,下面的代码片段演示了StandardHost对象如何将部署任务委托给StandardHostDeployer实例来完成

    /**
    * The <code>Deployer</code> to whom we delegate application
    * deployment requests.
    */
    private Deployer deployer = new StandardHostDeployer(this);
    public void install(String contextPath, URL war) throws IOException {
        deployer.install(contextPath, war);
    }
    public synchronized void install(URL config, URL war) throws
    IOException {
        deployer.install(config, war);
    }
    public Context findDeployedApp(String contextPath) {
        return (deployer.findDeployedApp(contextPath));
    }
    public String[] findDeployedApps() {
        return (deployer.findDeployedApps());
    }
    public void remove(String contextPath) throws IOException { deployer.remove(contextPath); } public void start(String contextPath) throws IOException { deployer.start(contextPath); } public void stop(String contextPath) throws IOException { deployer.stop(contextPath); }

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

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

    下面的install()方法用于安装描述符,当HostConfig对象的deployDescriptors方法调用StandardHost实例的install()方法后, StandardHost实例调用该方法

    public synchronized void install(URL config, URL war) throws IOException {
    
            // Validate the format and state of our arguments
            if (config == null)
                throw new IllegalArgumentException
                    (sm.getString("standardHost.configRequired"));
    
            if (!host.isDeployXML())
                throw new IllegalArgumentException
                    (sm.getString("standardHost.configNotAllowed"));
    
            // Calculate the document base for the new web application (if needed)
            String docBase = null; // Optional override for value in config file
            if (war != null) {
                String url = war.toString();
                host.log(sm.getString("standardHost.installingWAR", url));
                // Calculate the WAR file absolute pathname
                if (url.startsWith("jar:")) {
                    url = url.substring(4, url.length() - 2);
                }
                if (url.startsWith("file://"))
                    docBase = url.substring(7);
                else if (url.startsWith("file:"))
                    docBase = url.substring(5);
                else
                    throw new IllegalArgumentException
                        (sm.getString("standardHost.warURL", url));
    
            }
    
            // Install the new web application
            this.context = null;
            this.overrideDocBase = docBase;
            InputStream stream = null;
            try {
                stream = config.openStream();
                Digester digester = createDigester();
                digester.setDebug(host.getDebug());
                digester.clear();
                digester.push(this);
                digester.parse(stream);
                stream.close();
                stream = null;
            } catch (Exception e) {
                host.log
                    (sm.getString("standardHost.installError", docBase), e);
                throw new IOException(e.toString());
            } finally {
                if (stream != null) {
                    try {
                        stream.close();
                    } catch (Throwable t) {
                        ;
                    }
                }
            }
    
        }

    第二个install()方法用于安装WAR文件或目录

    public synchronized void install(String contextPath, URL war)
            throws IOException {
    
            // Validate the format and state of our arguments
            if (contextPath == null)
                throw new IllegalArgumentException
                    (sm.getString("standardHost.pathRequired"));
            if (!contextPath.equals("") && !contextPath.startsWith("/"))
                throw new IllegalArgumentException
                    (sm.getString("standardHost.pathFormat", contextPath));
            if (findDeployedApp(contextPath) != null)
                throw new IllegalStateException
                    (sm.getString("standardHost.pathUsed", contextPath));
            if (war == null)
                throw new IllegalArgumentException
                    (sm.getString("standardHost.warRequired"));
    
            // Calculate the document base for the new web application
            host.log(sm.getString("standardHost.installing",
                                  contextPath, war.toString()));
            String url = war.toString();
            String docBase = null;
            if (url.startsWith("jar:")) {
                url = url.substring(4, url.length() - 2);
            }
            if (url.startsWith("file://"))
                docBase = url.substring(7);
            else if (url.startsWith("file:"))
                docBase = url.substring(5);
            else
                throw new IllegalArgumentException
                    (sm.getString("standardHost.warURL", url));
    
            // Install the new web application
            try {
                Class clazz = Class.forName(host.getContextClass());
                Context context = (Context) clazz.newInstance();
                context.setPath(contextPath);
                
                context.setDocBase(docBase);
                if (context instanceof Lifecycle) {
                    clazz = Class.forName(host.getConfigClass());
                    LifecycleListener listener =
                        (LifecycleListener) clazz.newInstance();
                    ((Lifecycle) context).addLifecycleListener(listener);
                }
                host.fireContainerEvent(PRE_INSTALL_EVENT, context);
                host.addChild(context);
                host.fireContainerEvent(INSTALL_EVENT, context);
            } catch (Exception e) {
                host.log(sm.getString("standardHost.installError", contextPath),
                         e);
                throw new IOException(e.toString());
            }
    
        }

    start()方法用于启动Context实例:

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

    stop()方法用于停止Context实例:

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

    --------------------------------------------------------------------------- 

    本系列How Tomcat Works系本人原创 

    转载请注明出处 博客园 刺猬的温驯 

    本人邮箱: chenying998179#163.com (#改为@

    本文链接http://www.cnblogs.com/chenying99/p/3250908.html

  • 相关阅读:
    nc之二:nc命令详解
    memcache redundancy机制分析及思考
    memcache和redis区别
    java操作mongodb
    Memcache缓存与Mongodb数据库的优势和应用
    memcache 存储单个KEY,数据量过大的时候性能慢!以及简单的memcache不适合用到的场景
    pkill详解
    修改linux用户密码
    Mysql函数INSTR、LOCATE、POSITION VS LIKE
    Servlet3.0之九:web模块化
  • 原文地址:https://www.cnblogs.com/chenying99/p/3250908.html
Copyright © 2020-2023  润新知