? ? 要使用一個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 ( #改為@ )
更多文章、技術交流、商務合作、聯系博主
微信掃碼或搜索:z360901061
微信掃一掃加我為好友
QQ號聯系: 360901061
您的支持是博主寫作最大的動力,如果您喜歡我的文章,感覺我的文章對您有幫助,請用微信掃描下面二維碼支持博主2元、5元、10元、20元等您想捐的金額吧,狠狠點擊下面給點支持吧,站長非常感激您!手機微信長按不能支付解決辦法:請將微信支付二維碼保存到相冊,切換到微信,然后點擊微信右上角掃一掃功能,選擇支付二維碼完成支付。
【本文對您有幫助就好】元

