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應用:
當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 uniqueString 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 itlog(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 uniqueString 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 directorylog(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 filelog(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 uniqueString contextPath = "/" + files[i];if (files[i].equals("ROOT"))contextPath = "";if (host.findChild(contextPath) != null)continue;// Deploy the application in this directorylog(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 setwhile (!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 argumentsif (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 fileif (war != null) {String url = war.toString();host.log(sm.getString("standardHost.installingWAR", url));// Calculate the WAR file absolute pathnameif (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);elsethrow new IllegalArgumentException(sm.getString("standardHost.warURL", url));}// Install the new web applicationthis.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 argumentsif (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 applicationhost.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);elsethrow new IllegalArgumentException(sm.getString("standardHost.warURL", url));// Install the new web applicationtry {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 argumentsif (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 argumentsif (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
創作挑戰賽新人創作獎勵來咯,堅持創作打卡瓜分現金大獎總結
以上是生活随笔為你收集整理的How Tomcat Works(二十)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Android开发5——文件读写
- 下一篇: makefile例子《一》