Tomcat啟動過程都幹了啥
Tomcat是一個Web容器,用於接收HTTP請求並作出響應。我們都知道它是使用ServerSocket、Socket使用TCP連結達到通訊的目的。但這個過程是如何做到的呢?我們在webapps下放的那些Web應用又是如何被監聽起來的呢?配置webApp時有多種配置方式,如何正確的使用它們呢?web.xml為什麼要那麼配置呢,我們是否可以自定義一些元素呢?
這些都是接下來,我要研究的課題。在此之前,我們還是先讓Tomcat能夠啟動起來吧。
在瞭解Tomcat啟動過程之前,最好還是上網查查Tomcat有哪些頂層的介面,也就是Tomcat的設計架構,不然暈著頭就去除錯程式碼,是看不出什麼來的。
通過網上的瞭解,知道了Tomcat主要的介面有:
·Server、Service、Container、Connector、Lifecycle、Executor、Engine
、Host、Context、Wrapper、Value以及他們之間的關係。
其他的:
Realm、MBean等等。
緊接著大致的瀏覽了一下這些頂級介面,以及他們之間的關係,畫了一張類圖,如下:
通過這個類圖,就可以快速的瞭解這些主要介面之間的關係了。
接下來,開始Tomcat啟動過程的原始碼除錯,如何啟動除錯,上一濃ky"http://www.it165.net/qq/" target="_blank" class="keylink">qqyqc7E0tG+rcu1tcS63Mfls/7By6GjPC9wPgo8cD6198rUyrGjrMjrv9rU2kJvb3RzdHJhcCNtYWluKCk8L3A+Cgo8aW1nIGlkPQ=="code_img_closed_4ad30836-8149-461d-8988-f83a0261708c" class="code_img_closed" src="http://www.it165.net/uploadfile/files/2014/0928/2014092818483481.gif" alt="" />
public static void main(String args[]) {
if (daemon == null) {
daemon = new Bootstrap();
try {
// 初始化守護程序,其實就是初始化類載入器
daemon.init();
} catch (Throwable t) {
t.printStackTrace();
return;
}
}
try {
String command = "start";
if (args.length > 0) {
command = args[args.length - 1];
}
if (command.equals("startd")) {
args[args.length - 1] = "start";
daemon.load(args);
daemon.start();
} else if (command.equals("stopd")) {
args[args.length - 1] = "stop";
daemon.stop();
} else if (command.equals("start")) {
daemon.setAwait(true);
// 載入相關配置檔案,初始化幾個主要的頂層介面例項
daemon.load(args);
// 啟動那些有生命週期的頂層例項,監聽使用者請求
daemon.start();
} else if (command.equals("stop")) {
daemon.stopServer(args);
} else {
log.warn("Bootstrap: command \"" + command + "\" does not exist.");
}
} catch (Throwable t) {
t.printStackTrace();
}
}
從這個方法看,Tomcat啟動過程可以簡化為3個步驟:
1)初始化守護程序,其實就是初始化類載入器
2)載入相關配置檔案,初始化幾個主要的頂層介面例項,簡單了說,就是伺服器初始化。
3)啟動那些有生命週期的頂層例項,監聽使用者請求,簡單了說,就是啟動伺服器。
接下來,就針對這三個過程分別說明:
1、初始化類載入器
public void init()
throws Exception
{
// Set Catalina path
// 根據環境變數CATALINA_HOME來初始化Tomcat的安裝路徑,相關配置檔案路徑
setCatalinaHome();
setCatalinaBase();
// 初始化類載入器,用於載入tomcat/lib目錄下的jar包和class檔案,或者從網路上載入class檔案。
initClassLoaders();
Thread.currentThread().setContextClassLoader(catalinaLoader);
// 從tomcat/lib目錄下載入org.apache.catalina.startup.Catalina類
// 用於開啟Tomcat的真正的啟動過程
SecurityClassLoad.securityClassLoad(catalinaLoader);
// Load our startup class and call its process() method
if (log.isDebugEnabled())
log.debug("Loading startup class");
Class startupClass =
catalinaLoader.loadClass
("org.apache.catalina.startup.Catalina");
Object startupInstance = startupClass.newInstance();
// Set the shared extensions class loader
if (log.isDebugEnabled())
log.debug("Setting startup class properties");
String methodName = "setParentClassLoader";
Class paramTypes[] = new Class[1];
paramTypes[0] = Class.forName("java.lang.ClassLoader");
Object paramValues[] = new Object[1];
paramValues[0] = sharedLoader;
Method method =
startupInstance.getClass().getMethod(methodName, paramTypes);
method.invoke(startupInstance, paramValues);
catalinaDaemon = startupInstance;
}
Tomcat官方描述這個過程
類載入器初始化過程到這裡也介紹完畢了,這裡貼出來Tomcat官方文件中是如何介紹這個過程的:
2、伺服器初始化
在除錯這個過程之前,最好先了解一下配置檔案server.xml如何配置,各個部分代表什麼,下面是講tomcat預設的server.xml寫下來了。
<?xml version='1.0' encoding='utf-8'?>
<!-- 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">
<!--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" />
<!-- JMX Support for the Tomcat server. Documentation at /docs/non-existent.html -->
<Listener className="org.apache.catalina.mbeans.ServerLifecycleListener" />
<Listener className="org.apache.catalina.mbeans.GlobalResourcesLifecycleListener" />
<!-- 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="8080" 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"/>
-->
<!-- The request dumper valve dumps useful debugging information about
the request and response data received and sent by Tomcat.
Documentation at: /docs/config/valve.html -->
<!--
<Valve className="org.apache.catalina.valves.RequestDumperValve"/>
-->
<!-- 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"/>
<!-- Define the default virtual host
Note: XML Schema validation will not work with Xerces 2.2.
-->
<Host name="localhost" appBase="webapps"
unpackWARs="true" autoDeploy="true"
xmlValidation="false" xmlNamespaceAware="false">
<!-- 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 -->
<!--
<Valve className="org.apache.catalina.valves.AccessLogValve" directory="logs"
prefix="localhost_access_log." suffix=".txt" pattern="common" resolveHosts="false"/>
-->
</Host>
</Engine>
</Service>
</Server>
程式碼除錯:
1)載入server.xml,並創建出那些頂層物件
public void load() {
// 設定初始化開始時間,用於最後統計初始化過程用了多長時間
long t1 = System.nanoTime();
// 這一步是再次初始化Tomcat的安裝目錄等
initDirs();
// Before digester - it may be needed
initNaming();
// 這是一個XML文件解析器,用於解析server.xml、server-embed.xml
// Create and execute our Digester
Digester digester = createStartDigester();
InputSource inputSource = null;
InputStream inputStream = null;
File file = null;
try {
// file就是server.xml
file = configFile();
inputStream = new FileInputStream(file);
inputSource = new InputSource("file://" + file.getAbsolutePath());
} catch (Exception e) {
;
}
if (inputStream == null) {
try {
inputStream = getClass().getClassLoader()
.getResourceAsStream(getConfigFile());
inputSource = new InputSource
(getClass().getClassLoader()
.getResource(getConfigFile()).toString());
} catch (Exception e) {
;
}
}
// This should be included in catalina.jar
// Alternative: don't bother with xml, just create it manually.
// 當tomcat作為一個獨立的應用伺服器是解析server.xml
// 當tomcat嵌入到其他應用中時,解析server-embed.xml
if( inputStream==null ) {
try {
inputStream = getClass().getClassLoader()
.getResourceAsStream("server-embed.xml");
inputSource = new InputSource
(getClass().getClassLoader()
.getResource("server-embed.xml").toString());
} catch (Exception e) {
;
}
}
if ((inputStream == null) && (file != null)) {
log.warn("Can't load server.xml from " + file.getAbsolutePath());
if (file.exists() && !file.canRead()) {
log.warn("Permissions incorrect, read permission is not allowed on the file.");
}
return;
}
// 配置檔案解析,解析的過程中,就會根據配置檔案中的<Server />配置建立一個Server物件。
try {
inputSource.setByteStream(inputStream);
digester.push(this);
digester.parse(inputSource);
inputStream.close();
} catch (Exception e) {
log.warn("Catalina.start using "
+ getConfigFile() + ": " , e);
return;
}
// Stream redirection
initStreams();
// Start the new server
// 初始化Server
if (getServer() instanceof Lifecycle) {
try {
getServer().initialize();
} catch (LifecycleException e) {
if (Boolean.getBoolean("org.apache.catalina.startup.EXIT_ON_INIT_FAILURE"))
throw new java.lang.Error(e);
else
log.error("Catalina.start", e);
}
}
long t2 = System.nanoTime();
if(log.isInfoEnabled())
log.info("Initialization processed in " + ((t2 - t1) / 1000000) + " ms");
}
例如server.xml中有<server />、<service />、<connector>、<Engine />、<Host />這個過程是使用digester解析server.xml,並創建出相應的頂層物件。
與這些元素相關的頂層物件都會被建立。這些物件建立完畢,並且也設定依賴完畢,但是Tomcat並不知道一樣,需要將他們都註冊一下。
2)初始化Server物件:註冊Server物件
接下來看看Server真正的初始化過程。
StandardServer#initialize():
public void initialize()
throws LifecycleException
{
if (initialized) {
log.info(sm.getString("standardServer.initialize.initialized"));
return;
}
lifecycle.fireLifecycleEvent(INIT_EVENT, null);
initialized = true;
// Server物件已經建立完畢了,現在將作為Tomcat元件其註冊
if( oname==null ) {
try {
oname=new ObjectName( "Catalina:type=Server");
Registry.getRegistry(null, null)
.registerComponent(this, oname, null );
} catch (Exception e) {
log.error("Error registering ",e);
}
}
// Register global String cache
try {
ObjectName oname2 =
new ObjectName(oname.getDomain() + ":type=StringCache");
Registry.getRegistry(null, null)
.registerComponent(new StringCache(), oname2, null );
} catch (Exception e) {
log.error("Error registering ",e);
}
// Initialize our defined Services
// 在Server下設定初始化多個service,這個依據server.xml中的<service />配置
for (int i = 0; i < services.length; i++) {
services[i].initialize();
}
}
3)初始化Service物件:註冊 Services、executors物件
StandardService#initialize():
public void initialize()
throws LifecycleException
{
// Service shouldn't be used with embeded, so it doesn't matter
if (initialized) {
if(log.isInfoEnabled())
log.info(sm.getString("standardService.initialize.initialized"));
return;
}
initialized = true;
if( oname==null ) {
try {
// Hack - Server should be deprecated...
Container engine=this.getContainer();
domain=engine.getName();
// 註冊Service
oname=new ObjectName(domain + ":type=Service,serviceName="+name);
this.controller=oname;
Registry.getRegistry(null, null)
.registerComponent(this, oname, null);
// 設定service的executors屬性並註冊executors
Executor[] executors = findExecutors();
for (int i = 0; i < executors.length; i++) {
ObjectName executorObjectName =
new ObjectName(domain + ":type=Executor,name=" + executors[i].getName());
Registry.getRegistry(null, null)
.registerComponent(executors[i], executorObjectName, null);
}
} catch (Exception e) {
log.error(sm.getString("standardService.register.failed",domain),e);
}
}
if( server==null ) {
// Register with the server
// HACK: ServerFactory should be removed...
ServerFactory.getServer().addService(this);
}
// Initialize our defined Connectors
// 在service下設定多個connector
// 這個要依據service.xml的<connector>
synchronized (connectors) {
for (int i = 0; i < connectors.length; i++) {
try {
connectors[i].initialize();
} catch (Exception e) {
String message = sm.getString(
"standardService.connector.initFailed",
connectors[i]);
log.error(message, e);
if (Boolean.getBoolean("org.apache.catalina.startup.EXIT_ON_INIT_FAILURE"))
throw new LifecycleException(message);
}
}
}
}
4)初始化Connector物件
public void initialize()
throws LifecycleException
{
if (initialized) {
if(log.isInfoEnabled())
log.info(sm.getString("coyoteConnector.alreadyInitialized"));
return;
}
this.initialized = true;
if( oname == null && (container instanceof StandardEngine)) {
try {
// 註冊聯結器Connector
StandardEngine cb=(StandardEngine)container;
oname = createObjectName(cb.getName(), "Connector");
Registry.getRegistry(null, null)
.registerComponent(this, oname, null);
controller=oname;
} catch (Exception e) {
log.error( "Error registering connector ", e);
}
if(log.isDebugEnabled())
log.debug("Creating name for connector " + oname);
}
// 為當前Connector建立一個CoyoteAdapter介面卡
adapter = new CoyoteAdapter(this);
protocolHandler.setAdapter(adapter);
// Make sure parseBodyMethodsSet has a default
if( null == parseBodyMethodsSet )
setParseBodyMethods(getParseBodyMethods());
IntrospectionUtils.setProperty(protocolHandler, "jkHome",
System.getProperty("catalina.base"));
try {
// 協議初始化
protocolHandler.init();
} catch (Exception e) {
throw new LifecycleException
(sm.getString
("coyoteConnector.protocolHandlerInitializationFailed", e));
}
}
5)ProtocolHandler初始化
協議初始化,根據<Connector>指定的協議型別來進行初始化。
public void init() throws Exception {
endpoint.setName(getName());
endpoint.setHandler(cHandler);
// Verify the validity of the configured socket factory
try {
if (isSSLEnabled()) {
sslImplementation =
SSLImplementation.getInstance(sslImplementationName);
socketFactory = sslImplementation.getServerSocketFactory();
endpoint.setServerSocketFactory(socketFactory);
} else if (socketFactoryName != null) {
socketFactory = (ServerSocketFactory) Class.forName(socketFactoryName).newInstance();
endpoint.setServerSocketFactory(socketFactory);
}
} catch (Exception ex) {
log.error(sm.getString("http11protocol.socketfactory.initerror"),
ex);
throw ex;
}
if (socketFactory!=null) {
Iterator<String> attE = attributes.keySet().iterator();
while( attE.hasNext() ) {
String key = attE.next();
Object v=attributes.get(key);
socketFactory.setAttribute(key, v);
}
}
// endpoint初始化,根據上面的類圖就知道,當協議確定時,Endpoint的型別也就確定了。
try {
endpoint.init();
} catch (Exception ex) {
log.error(sm.getString("http11protocol.endpoint.initerror"), ex);
throw ex;
}
if (log.isInfoEnabled())
log.info(sm.getString("http11protocol.init", getName()));
}
6)Endpoint初始化
public void init()
throws Exception {
if (initialized)
return;
// Initialize thread count defaults for acceptor
if (acceptorThreadCount == 0) {
acceptorThreadCount = 1;
}
if (serverSocketFactory == null) {
serverSocketFactory = ServerSocketFactory.getDefault();
}
if (serverSocket == null) {
try {
if (address == null) {
serverSocket = serverSocketFactory.createSocket(port, backlog);
} else {
serverSocket = serverSocketFactory.createSocket(port, backlog, address);
}
} catch (BindException orig) {
String msg;
if (address == null)
msg = orig.getMessage() + " <null>:" + port;
else
msg = orig.getMessage() + " " +
address.toString() + ":" + port;
BindException be = new BindException(msg);
be.initCause(orig);
throw be;
}
}
//if( serverTimeout >= 0 )
// serverSocket.setSoTimeout( serverTimeout );
initialized = true;
}
從這段程式碼很清楚的看出,Endpoint就代表了服務端的一個Socket端點,endpoint的初始化其實就是建立ServerSocket物件。
上述6個步驟中,不論採用什麼協議,前3步都是一樣的,至於5)、6)兩步要也是需要的,但是由於協議的不同,每一步的內部是如何實現的,這點並不一樣。
Tomcat官方描述這個過程
到這裡,伺服器初始化也就介紹的差不多了。這裡貼出來tomcat官方文件時如何描述這一過程的:
3、伺服器開啟
StandardService啟動過程說明
可以通過檢視StandardServer#start()來了解伺服器開啟的過程。
如果跟蹤程式碼就會發現:
Catalina#start()--àStandardServer#start()--àStandardService#start()
也就是Catalina物件執行start()期間,會呼叫StandardServer物件的start()方法。
StandardServer物件執行start()期間,會呼叫Server下的所有的StandardService物件的start()。
下面就看看StandardService物件的start方法:
public void start() throws LifecycleException {
// Validate and update our current component state
if (started) {
if (log.isInfoEnabled()) {
log.info(sm.getString("standardService.start.started"));
}
return;
}
if( ! initialized )
init();
// Notify our interested LifecycleListeners
lifecycle.fireLifecycleEvent(BEFORE_START_EVENT, null);
if(log.isInfoEnabled())
log.info(sm.getString("standardService.start.name", this.name));
lifecycle.fireLifecycleEvent(START_EVENT, null);
started = true;
// 啟動定義的所有的容器
// Engine、Host、Context都是容器,只要在server.xml中定義了,都會在這裡啟動
if (container != null) {
synchronized (container) {
if (container instanceof Lifecycle) {
((Lifecycle) container).start();
}
}
}
// 啟動執行器Executor
synchronized (executors) {
for ( int i=0; i<executors.size(); i++ ) {
executors.get(i).start();
}
}
// 啟動定義的聯結器Connector
synchronized (connectors) {
for (int i = 0; i < connectors.length; i++) {
try {
((Lifecycle) connectors[i]).start();
} catch (Exception e) {
log.error(sm.getString(
"standardService.connector.startFailed",
connectors[i]), e);
}
}
}
// Notify our interested LifecycleListeners
lifecycle.fireLifecycleEvent(AFTER_START_EVENT, null);
}
Service下的Container的啟動過程說明
Service下的容器有Engine,Engine下的容器有Host,Host下的容器有Context。
每個容器下都有Pipeline、Value、Realm等等配置。
接下來就看看如何啟動這些各個容器的。除錯程式碼的過程中,就會發現,每個容器都會使用到的一個方法是在ContainerBase中定義的start():
public synchronized void start() throws LifecycleException {
// Validate and update our current component state
if (started) {
if(log.isInfoEnabled())
log.info(sm.getString("containerBase.alreadyStarted", logName()));
return;
}
// Notify our interested LifecycleListeners
lifecycle.fireLifecycleEvent(BEFORE_START_EVENT, null);
started = true;
// Start our subordinate components, if any
if ((loader != null) && (loader instanceof Lifecycle))
((Lifecycle) loader).start();
logger = null;
getLogger();
if ((logger != null) && (logger instanceof Lifecycle))
((Lifecycle) logger).start();
if ((manager != null) && (manager instanceof Lifecycle))
((Lifecycle) manager).start();
if ((cluster != null) && (cluster instanceof Lifecycle))
((Lifecycle) cluster).start();
if ((realm != null) && (realm instanceof Lifecycle))
((Lifecycle) realm).start();
if ((resources != null) && (resources instanceof Lifecycle))
((Lifecycle) resources).start();
// Start our child containers, if any
// 啟動子容器,也就是說:
如果this是Engine物件,就會啟動Engine下的所有的host來啟動。
// 當host執行start()過程,也會執行這個方法,來啟動host下的context。
// 也就是說,這裡會引起遞迴呼叫
Container children[] = findChildren();
for (int i = 0; i < children.length; i++) {
if (children[i] instanceof Lifecycle)
((Lifecycle) children[i]).start();
}
// Start the Valves in our pipeline (including the basic), if any
// 啟動Container下的在pipeline中的value
if (pipeline instanceof Lifecycle)
((Lifecycle) pipeline).start();
// Notify our interested LifecycleListeners
lifecycle.fireLifecycleEvent(START_EVENT, null);
// 啟動守護執行緒,用於週期性的檢查Session是否超時
// Start our thread
threadStart();
// Notify our interested LifecycleListeners
lifecycle.fireLifecycleEvent(AFTER_START_EVENT, null);
}
1)StandardEngine啟動過程
public void start() throws LifecycleException {
if( started ) {
return;
}
// 在初始化階段,並沒有初始化容器,所以在啟動容器前要先初始化
if( !initialized ) {
// 容器的初始化和其他元件的初始化類似,都是將其註冊為MBean
init();
}
// Look for a realm - that may have been configured earlier.
// If the realm is added after context - it'll set itself.
if( realm == null ) {
ObjectName realmName=null;
try {
realmName=new ObjectName( domain + ":type=Realm");
if( mserver.isRegistered(realmName ) ) {
mserver.invoke(realmName, "init",
new Object[] {},
new String[] {}
);
}
} catch( Throwable t ) {
log.debug("No realm for this engine " + realmName);
}
}
// Log our server identification information
//System.out.println(ServerInfo.getServerInfo());
if(log.isInfoEnabled())
log.info( "Starting Servlet Engine: " + ServerInfo.getServerInfo());
if( mbeans != null ) {
try {
Registry.getRegistry(null, null)
.invoke(mbeans, "start", false);
} catch (Exception e) {
log.error("Error in start() for " + mbeansFile, e);
}
}
// Standard container startup
// 這才是真正的操作。而這個操作是在ContainerBase中定義的,也就是說,所有的Container都可以使用。這個方法上面已經說明。
super.start();
}
2)StandardHost啟動過程
public synchronized void start() throws LifecycleException {
if( started ) {
return;
}
// 初始化host
if( ! initialized )
init();
// Look for a realm - that may have been configured earlier.
// If the realm is added after context - it'll set itself.
if( realm == null ) {
ObjectName realmName=null;
try {
realmName=new ObjectName( domain + ":type=Realm,host=" + getName());
if( mserver.isRegistered(realmName ) ) {
mserver.invoke(realmName, "init",
new Object[] {},
new String[] {}
);
}
} catch( Throwable t ) {
log.debug("No realm for this host " + realmName);
}
}
// Set error report valve, 用於報請求HTML錯誤碼
if ((errorReportValveClass != null)
&& (!errorReportValveClass.equals(""))) {
try {
boolean found = false;
if(errorReportValveObjectName != null) {
ObjectName[] names =
// 新增Value,然後在下面的程式碼super.start()使用;也就是在呼叫ContainerBase#start()中使用。
((StandardPipeline)pipeline).getValveObjectNames();
for (int i=0; !found && i<names.length; i++)
if(errorReportValveObjectName.equals(names[i]))
found = true ;
}
if(!found) {
Valve valve = (Valve) Class.forName(errorReportValveClass)
.newInstance();
addValve(valve);
errorReportValveObjectName = ((ValveBase)valve).getObjectName() ;
}
} catch (Throwable t) {
log.error(sm.getString
("standardHost.invalidErrorReportValveClass",
errorReportValveClass), t);
}
}
if(log.isDebugEnabled()) {
if (xmlValidation)
log.debug(sm.getString("standardHost.validationEnabled"));
else
log.debug(sm.getString("standardHost.validationDisabled"));
}
super.start();
}
2.1)Host啟動過程中的管道pipeline啟動
上面說了容器啟動過程中都會執行的方法:ContainerBase#start(),程式碼在上面。程式碼中有一個pipeline.start()語句。
public synchronized void start() throws LifecycleException {
// Validate and update our current component state
if (started)
throw new LifecycleException
(sm.getString("standardPipeline.alreadyStarted"));
// Notify our interested LifecycleListeners
lifecycle.fireLifecycleEvent(BEFORE_START_EVENT, null);
started = true;
// Start the Valves in our pipeline (including the basic), if any
Valve current = first;
if (current == null) {
current = basic;
}
while (current != null) {
if (current instanceof Lifecycle)
((Lifecycle) current).start();
registerValve(current);
current = current.getNext();
}
// Notify our interested LifecycleListeners
lifecycle.fireLifecycleEvent(START_EVENT, null);
// Notify our interested LifecycleListeners
// 通知監聽器執行相關處理
lifecycle.fireLifecycleEvent(AFTER_START_EVENT, null);
}
2.2)通過事件處理進入到應用程式部署階段
在fireLifecycleEvent方法呼叫期間,會呼叫到HostConfig的處理。
從這裡就進入了應用程式部署的階段了。
這個方法中的setDeployXML用於設定部署時是否部署XML,
也就是否部署通過XML方式描述的上下文:
setUnpackWars()是設定是否部署webapps下的war包。
HostConfig#start():
部署Host下所有的Web應用。
2.3)開始部署應用程式
從這個方法來看,部署時:
1)穴ky"http://www.it165.net/qq/" target="_blank" class="keylink">qqyv8rwdG9tY2F0L2NvbmbPwrWxx7BFbmdpbmWjqGNhdGFsaW5ho6nPwrXEtbHHsGhvc3SjqGxvY2FsaG9zdKOpxL/CvM/CtcTL+dPQtcR4bWzOxLz+oaPV4sDvtcRYTUzOxLz+vs3Kx9K7uPZjb250ZXh0w+jK9rf7oaM8L3A+CjxwPjIpu+Gyv8rwdG9tY2F0L3dlYmFwcHPPwrXEd2FysPy6zcS/wryhozwvcD4KCjxwPrb4w7/W1rK7zazA4NDNtcTTptPDysfI57rOsr/K8LW9aG9zdM/CtcSjrNXiwO++zc/Isru/tMHLoaM8L3A+Cgo8cD7WwbTLo6xUb21jYXTG9LavtcTV+7j2wfezzKOsy+PKx8HLveK1xLLusru24MHLoaM8L3A+Cgo8aDM+VG9tY2F0udm3vcPoyvbV4rj2uf2zzDwvaDM+CjxwPtTa1eLA76Os0rLM+bP2wLRUb21jYXS52be9zsS1tcqxyOe6zsPoyvbV4tK7uf2zzLXEo7o8L3A+CjxwPiZuYnNwOzxpbWcgc3JjPQ=="http://www.it165.net/uploadfile/files/2014/0928/2014092818483590.png" alt="" />
我在除錯時寫的流程是和官方的文件是基本上吻合的。通過這個除錯,很容易就知道了Tomcat的各個元件的關係。