【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实例的代码;