? ? 要使用一個(gè)web應(yīng)用程序,必須要將表示該應(yīng)用程序的Context實(shí)例部署到一個(gè)host實(shí)例中。在tomcat中,context實(shí)例可以用war文件的形式來部署,也可以將整個(gè)web應(yīng)用拷貝到Tomcat安裝目錄下的webapp下。對于部署的每個(gè)web應(yīng)用程序,可以在其中包含一個(gè)描述文件(該文件是可選的),該文件中包含了對context的配置選項(xiàng),是xml格式的文件。
注意,tomcat4和tomcat5使用兩個(gè)應(yīng)用程序來管理tomcat及其應(yīng)用的部署,分別是manager應(yīng)用程序和admin應(yīng)用程序。這里兩個(gè)應(yīng)用程序位于%CATALINA_HOME%/server/webapps目錄下,各自有一個(gè)描述文件,分別是manager.xml和admin.xml。
? ? 本文將討論使用一個(gè)部署器來部署web應(yīng)用程序,部署器是org.apache.catalina.Deployer接口的實(shí)例。部署器需要與一個(gè)host實(shí)例相關(guān)聯(lián),用于部署context實(shí)例。部署一個(gè)context到host,即創(chuàng)建一個(gè)StandardContext實(shí)例,并將該context實(shí)例添加到host實(shí)例中。創(chuàng)建的context實(shí)例會隨其父容器——host實(shí)例而啟動(容器的實(shí)例在啟動時(shí)總是會調(diào)用其子容器的start方法,除非該該container是一個(gè)wrapper實(shí)例)。
? ? 本文會先說明tomcat部署器如何部署一個(gè)web應(yīng)用程序,然后描述Deployer接口及其標(biāo)準(zhǔn)實(shí)現(xiàn)org.apache.catalina.core.StandardHostDeployer類的工作原理。
tomcat中在StandardHost中使用了一個(gè)生命周期監(jiān)聽器(lifecycle listener)org.apache.catalina.startup.HostConfig來部署應(yīng)用。
當(dāng)調(diào)用StandardHost實(shí)例的start方法時(shí),會觸發(fā)START事件,HostConfig實(shí)例會響應(yīng)該事件,調(diào)用其start方法,在該方法中會部署并安裝指定目錄中的所有的web應(yīng)用程序。
在 How Tomcat Works(十八) 中,描述了如何使用Digester對象來解析XML文檔的內(nèi)容,但并沒有涉及Digester對象中所有的規(guī)則,其中被忽略掉的一個(gè)主題就是部署器,也就是本文的主題
在Tomcat中,org.apache.catalina.startup.Catalina類是啟動類,使用Digester對象來解析server.xml文件,將其中的xml元素轉(zhuǎn)換為java對象。
Catalina類中定義了createStartDigester方法來添加規(guī)則到Digester中:
digester.addRuleSet(
new
HostRuleSet("Server/Service/Engine/"));
org.apache.catalina.startup.HostRuleSet類繼承自org.apache.commons.digester.RuleSetBase類,作為RuleSetBase的子類,HostRuleSet提供了addRuleInstances方法實(shí)現(xiàn),該方法定義了RuleSet中的規(guī)則(Rule)。
下面是HostRuleSet類的addRuleInstances方法的實(shí)現(xiàn)片段:
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"));
正如代碼中所示,當(dāng)出現(xiàn)模式Server/Service/Engine/Host時(shí),會創(chuàng)建一個(gè)org.apache.catalina.startup.HostConfig實(shí)例,并被添加到host,作為一個(gè)生命周期監(jiān)聽器。換句話說,HostConfig對象會處理StandardHost對象的start和stop方法觸發(fā)的事件。
下面的代碼是HostConfig的lifecycleEvent方法實(shí)現(xiàn):
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指向的對象是一個(gè)org.apache.catalina.core.StandardHost實(shí)例,會調(diào)用setDeployXML方法,setLiveDeploy方法和setUnpackWARs方法:
setDeployXML(((StandardHost) host).isDeployXML());
setLiveDeploy(((StandardHost) host).getLiveDeploy());
setUnpackWARs(((StandardHost) host).isUnpackWARs());
StandardHost類的isDeployXML方法指明host是否要部署一個(gè)描述文件,默認(rèn)為true。liveDeploy屬性指明host是否要周期性的檢查是否有新的應(yīng)用部署。unpackWARs屬性指明host是否要解壓縮war文件。
接收到START事件后,HostConfig的lifecycleEvent方法會調(diào)用start方法來部署web應(yīng)用:
protected void start() { if (debug >= 1 ) log(sm.getString( "hostConfig.start" )); if (host.getAutoDeploy()) { deployApps(); } if (isLiveDeploy ()) { threadStart(); } }
當(dāng)autoDeploy屬性值為true時(shí)(默認(rèn)為true),則start方法會調(diào)用deployApps方法。此外,若liveDeploy屬性為true(默認(rèn)為true),則該方法會開一個(gè)新線程調(diào)用threadStart方法。
deployApps方法從host中獲取appBase屬性值(默認(rèn)為webapps),該值定義于server.xml文件中。部署進(jìn)程會將%CATALINE_HOME%/webapps目錄下的所有目錄看做為Web應(yīng)用程序的目錄來執(zhí)行部署工作。此外,該目錄下找到的war文件和描述文件也會被部署。
deployApps方法實(shí)現(xiàn)如下:
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方法會調(diào)用其他三個(gè)方法,deployDescriptors,deployWARs和deployDirectories。對于所有方法,deployApps方法會傳入appBase對象和appBase下所有的文件名的數(shù)組形式。context實(shí)例是通過其路徑來標(biāo)識的,所有的context必須有其唯一路徑。已經(jīng)被部署的contex實(shí)例t會被添加到HostConfig對象中已經(jīng)部署的ArrayList中。因此,在部署一個(gè)context實(shí)例之前,deployDescriptors,deployWARs和deployDirectories方法必須確保已部署ArrayList中的沒有相同路徑的context實(shí)例。
注意,deployDescriptors,deployWARs和deployDirectories三個(gè)方法的調(diào)用順序是固定的
下面方法為部署描述符:
/** * 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應(yīng)用程序整個(gè)目錄復(fù)制到%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方法會調(diào)用threadStart()方法
if (isLiveDeploy()) { threadStart(); }
threadStart()方法會派生一個(gè)新線程并調(diào)用run()方法,run()方法會定期檢查是否有新應(yīng)用要部署,或已部署的Web應(yīng)用程序的web.xml是否有修改
下面的run()方法的實(shí)現(xiàn)(HostConfig類實(shí)現(xiàn)了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實(shí)現(xiàn)了 Deployer接口,因此,StandardHost也是一個(gè)部署器,它是一個(gè)容器,Web應(yīng)用可以部署到其中,或從中取消部署
下面是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類使用一個(gè)輔助類( org.apache.catalina.core.StandardHostDeployer,與StandardHost類都實(shí)現(xiàn)了Deployer接口 ) 來完成部署與安裝Web應(yīng)用程序的相關(guān)任務(wù),下面的代碼片段演示了StandardHost對象如何將部署任務(wù)委托給StandardHostDeployer實(shí)例來完成
/** * 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類是一個(gè)輔助類,幫助完成將Web應(yīng)用程序部署到StandardHost實(shí)例的工作。StandardHostDeployer實(shí)例由StandardHost對象調(diào)用,在其構(gòu)造函數(shù)中,會傳入StandardHost類的實(shí)例
public StandardHostDeployer(StandardHost host) { super (); this .host = host; }
下面的install()方法用于安裝描述符,當(dāng)HostConfig對象的deployDescriptors方法調(diào)用StandardHost實(shí)例的install()方法后, StandardHost實(shí)例調(diào)用該方法
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) { ; } } } }
第二個(gè)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實(shí)例:
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實(shí)例:
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系本人原創(chuàng)?
轉(zhuǎn)載請注明出處 博客園 刺猬的溫馴?
本人郵箱: ? chenying998179 # 163.com ( #改為@ )
更多文章、技術(shù)交流、商務(wù)合作、聯(lián)系博主
微信掃碼或搜索:z360901061

微信掃一掃加我為好友
QQ號聯(lián)系: 360901061
您的支持是博主寫作最大的動力,如果您喜歡我的文章,感覺我的文章對您有幫助,請用微信掃描下面二維碼支持博主2元、5元、10元、20元等您想捐的金額吧,狠狠點(diǎn)擊下面給點(diǎn)支持吧,站長非常感激您!手機(jī)微信長按不能支付解決辦法:請將微信支付二維碼保存到相冊,切換到微信,然后點(diǎn)擊微信右上角掃一掃功能,選擇支付二維碼完成支付。
【本文對您有幫助就好】元
