本文重点关注启动tomcat时会用到的两个类,分别为Catalina类和Bootstrap类,它们都位于org.apachae.catalina.startup包下;Catalina类用于启动或关闭Server对象,并负责解析server.xml配置文件;Bootstrap类是一个入口点,负责创建Catalina实例,并调用其process()方法。
org.apachae.catalina.startup.Catalina类是启动类,它包含一个Digester对象,用于解析位于%CATALINE_HOME%/conf目录的server.xml文件
Catalina类还封装了一个Server对象,该对象持有一个Service对象(Service对象包含一个Servlet容器和一个或多个连接器)。可以使用Catalina类来启动/关闭Server对象
可以通过实例化Catalina类,并调用其process()方法来运行Tomcat,但在调用该方法时,需要传入适当的参数(如start或stop -help -config -debug -nonaming等)。
/**
* The instance main program.
*
*
@param
args Command line arguments
*/
public
void
process(String args[]) {
setCatalinaHome();
setCatalinaBase();
try
{
if
(arguments(args))
execute();
}
catch
(Exception e) {
e.printStackTrace(System.out);
}
}
上面方法会进一步调用execute()方法,execute()方法会根据传入参数调用start()方法或stop()方法
start方法会创建一个Digester对象来解析server.xml文件(Tomcat配置文件)。在解析server.xml文件之前,start方法会调用Digester对象的push方法,传入当前的Catalina对象为参数。这样,Catalina对象就成了Digester对象内部对象栈的第一个对象。解析server.xml文件后,会将变量server指向一个Server对象(默认是org.apache.catalina.core.StandardServer类型的对象)。然后,start方法会调用server的initialize和start方法。Catalina对象的start方法会调用Server对象的await方法,server对象会使用一个专用的线程来等待关闭命令。await方法会循环等待,知道接收到正确的关闭命令。当await方法返回时,Catalina对象的start方法会调用server对象的stop方法,从而关闭server对象和其他的组件。此外,start方法还会注册shutdown hook,确保服务器关闭时会执行Server对象的stop方法。
start方法的实现如下:
/**
* Start a new server instance.
*/
protected
void
start() {
//
Create and execute our Digester
Digester digester =
createStartDigester();
File file
=
configFile();
try
{
InputSource is
=
new
InputSource("file://" +
file.getAbsolutePath());
FileInputStream fis
=
new
FileInputStream(file);
is.setByteStream(fis);
digester.push(
this
);
digester.parse(is);
fis.close();
}
catch
(Exception e) {
System.out.println(
"Catalina.start: " +
e);
e.printStackTrace(System.out);
System.exit(
1
);
}
//
Setting additional variables
if
(!
useNaming) {
System.setProperty(
"catalina.useNaming", "false"
);
}
else
{
System.setProperty(
"catalina.useNaming", "true"
);
String value
= "org.apache.naming"
;
String oldValue
=
System.getProperty(javax.naming.Context.URL_PKG_PREFIXES);
if
(oldValue !=
null
) {
value
= value + ":" +
oldValue;
}
System.setProperty(javax.naming.Context.URL_PKG_PREFIXES, value);
value
=
System.getProperty
(javax.naming.Context.INITIAL_CONTEXT_FACTORY);
if
(value ==
null
) {
System.setProperty
(javax.naming.Context.INITIAL_CONTEXT_FACTORY,
"org.apache.naming.java.javaURLContextFactory"
);
}
}
//
If a SecurityManager is being used, set properties for
//
checkPackageAccess() and checkPackageDefinition
if
( System.getSecurityManager() !=
null
) {
String access
= Security.getProperty("package.access"
);
if
( access !=
null
&& access.length() > 0
)
access
+= ","
;
else
access
= "sun.,"
;
Security.setProperty(
"package.access"
,
access
+ "org.apache.catalina.,org.apache.jasper."
);
String definition
= Security.getProperty("package.definition"
);
if
( definition !=
null
&& definition.length() > 0
)
definition
+= ","
;
else
definition
= "sun.,"
;
Security.setProperty(
"package.definition"
,
//
FIX ME package "javax." was removed to prevent HotSpot
//
fatal internal errors
definition + "java.,org.apache.catalina.,org.apache.jasper."
);
}
//
Replace System.out and System.err with a custom PrintStream
SystemLogHandler log =
new
SystemLogHandler(System.out);
System.setOut(log);
System.setErr(log);
Thread shutdownHook
= new CatalinaShutdownHook();
//
Start the new server
if
(server
instanceof
Lifecycle) {
try
{
server.initialize();
((Lifecycle) server).start();
try
{
//
Register shutdown hook
Runtime.getRuntime().addShutdownHook(shutdownHook);
}
catch
(Throwable t) {
//
This will fail on JDK 1.2. Ignoring, as Tomcat can run
//
fine without the shutdown hook.
}
//
Wait for the server to be told to shut down
server.await();
}
catch
(LifecycleException e) {
System.out.println(
"Catalina.start: " +
e);
e.printStackTrace(System.out);
if
(e.getThrowable() !=
null
) {
System.out.println(
"----- Root Cause -----"
);
e.getThrowable().printStackTrace(System.out);
}
}
}
//
Shut down the server
if
(server
instanceof
Lifecycle) {
try
{
try
{
//
Remove the ShutdownHook first so that server.stop()
//
doesn't get invoked twice
Runtime.getRuntime().removeShutdownHook(shutdownHook);
}
catch
(Throwable t) {
//
This will fail on JDK 1.2. Ignoring, as Tomcat can run
//
fine without the shutdown hook.
}
((Lifecycle) server).stop();
}
catch
(LifecycleException e) {
System.out.println(
"Catalina.stop: " +
e);
e.printStackTrace(System.out);
if
(e.getThrowable() !=
null
) {
System.out.println(
"----- Root Cause -----"
);
e.getThrowable().printStackTrace(System.out);
}
}
}
}
其中的CatalinaShutdownHook类为关闭钩子
/**
* Shutdown hook which will perform a clean shutdown of Catalina if needed.
*/
protected
class
CatalinaShutdownHook
extends
Thread {
public
void
run() {
if
(server !=
null
) {
try
{
((Lifecycle) server).stop();
}
catch
(LifecycleException e) {
System.out.println(
"Catalina.stop: " +
e);
e.printStackTrace(System.out);
if
(e.getThrowable() !=
null
) {
System.out.println(
"----- Root Cause -----"
);
e.getThrowable().printStackTrace(System.out);
}
}
}
}
}
Catalina对象的stop方法会关闭Server对象,其实现如下:
/**
* Stop an existing server instance.
*/
protected
void
stop() {
//
Create and execute our Digester
Digester digester =
createStopDigester();
File file
=
configFile();
try
{
InputSource is
=
new
InputSource("file://" +
file.getAbsolutePath());
FileInputStream fis
=
new
FileInputStream(file);
is.setByteStream(fis);
digester.push(
this
); digester.parse(is);
fis.close();
}
catch
(Exception e) {
System.out.println(
"Catalina.stop: " +
e);
e.printStackTrace(System.out);
System.exit(
1
);
}
//
Stop the existing server
try
{
Socket socket
=
new
Socket("127.0.0.1"
, server.getPort());
OutputStream stream
=
socket.getOutputStream();
String shutdown
=
server.getShutdown();
for
(
int
i = 0; i < shutdown.length(); i++
)
stream.write(shutdown.charAt(i));
stream.flush();
stream.close();
socket.close();
}
catch
(IOException e) {
System.out.println(
"Catalina.stop: " +
e);
e.printStackTrace(System.out);
System.exit(
1
);
}
}
注意,stop方法通过调用createStopDigester方法创建一个Digester对象,然后将Catalina对象push到Digester对象的内部对象栈中。
createStartDigester方法创建了一个Digester对象,然后将规则添加到其中,解析server.xml文件。添加到Digester对象中的规则是理解tomcat配置的关键。
createStartDigester方法的实现如下:
/**
* Create and configure the Digester we will be using for startup.
*/
protected
Digester createStartDigester() {
//
Initialize the digester
Digester digester =
new
Digester();
if
(debug)
digester.setDebug(
999
);
digester.setValidating(
false
);
//
Configure the actions we will be using
digester.addObjectCreate("Server"
,
"org.apache.catalina.core.StandardServer"
,
"className"
);
digester.addSetProperties(
"Server"
);
digester.addSetNext(
"Server",
"setServer",
"org.apache.catalina.Server"
);
digester.addObjectCreate(
"Server/GlobalNamingResources"
,
"org.apache.catalina.deploy.NamingResources"
);
digester.addSetProperties(
"Server/GlobalNamingResources"
);
digester.addSetNext(
"Server/GlobalNamingResources"
,
"setGlobalNamingResources"
,
"org.apache.catalina.deploy.NamingResources"
);
digester.addObjectCreate(
"Server/Listener"
,
null
,
//
MUST be specified in the element
"className"
);
digester.addSetProperties(
"Server/Listener"
);
digester.addSetNext(
"Server/Listener"
,
"addLifecycleListener"
,
"org.apache.catalina.LifecycleListener"
);
digester.addObjectCreate(
"Server/Service"
,
"org.apache.catalina.core.StandardService"
,
"className"
);
digester.addSetProperties(
"Server/Service"
);
digester.addSetNext(
"Server/Service"
,
"addService"
,
"org.apache.catalina.Service"
);
digester.addObjectCreate(
"Server/Service/Listener"
,
null
,
//
MUST be specified in the element
"className"
);
digester.addSetProperties(
"Server/Service/Listener"
);
digester.addSetNext(
"Server/Service/Listener"
,
"addLifecycleListener"
,
"org.apache.catalina.LifecycleListener"
);
digester.addObjectCreate(
"Server/Service/Connector"
,
"org.apache.catalina.connector.http.HttpConnector"
,
"className"
);
digester.addSetProperties(
"Server/Service/Connector"
);
digester.addSetNext(
"Server/Service/Connector"
,
"addConnector"
,
"org.apache.catalina.Connector"
);
digester.addObjectCreate(
"Server/Service/Connector/Factory"
,
"org.apache.catalina.net.DefaultServerSocketFactory"
,
"className"
);
digester.addSetProperties(
"Server/Service/Connector/Factory"
);
digester.addSetNext(
"Server/Service/Connector/Factory"
,
"setFactory"
,
"org.apache.catalina.net.ServerSocketFactory"
);
digester.addObjectCreate(
"Server/Service/Connector/Listener"
,
null
,
//
MUST be specified in the element
"className"
);
digester.addSetProperties(
"Server/Service/Connector/Listener"
);
digester.addSetNext(
"Server/Service/Connector/Listener"
,
"addLifecycleListener"
,
"org.apache.catalina.LifecycleListener"
);
//
Add RuleSets for nested elements
digester.addRuleSet(
new
NamingRuleSet("Server/GlobalNamingResources/"
));
digester.addRuleSet(
new
EngineRuleSet("Server/Service/"
));
digester.addRuleSet(
new
HostRuleSet("Server/Service/Engine/"
));
digester.addRuleSet(
new
ContextRuleSet("Server/Service/Engine/Default"
));
digester.addRuleSet(
new
NamingRuleSet("Server/Service/Engine/DefaultContext/"
));
digester.addRuleSet(
new
ContextRuleSet("Server/Service/Engine/Host/Default"
));
digester.addRuleSet(
new
NamingRuleSet("Server/Service/Engine/Host/DefaultContext/"
));
digester.addRuleSet(
new
ContextRuleSet("Server/Service/Engine/Host/"
));
digester.addRuleSet(
new
NamingRuleSet("Server/Service/Engine/Host/Context/"
));
digester.addRule(
"Server/Service/Engine"
,
new
SetParentClassLoaderRule(digester,
parentClassLoader));
return
(digester);
}
这里需要注意的是 digester.addSetNext("Server", "setServer","org.apache.catalina.Server")方法,该方法将Server对象压入到Digester对象的内部栈中,并与栈中的下一个对象相关联;在这里,下一个对象是Catalina实例,调用其setServer()方法与Server对象相关联。
createStopDigester方法返回一个Digester对象来关闭Server对象。createStopDigester方法实现如下:
/**
* Create and configure the Digester we will be using for shutdown.
*/
protected
Digester createStopDigester() {
//
Initialize the digester
Digester digester =
new
Digester();
if
(debug)
digester.setDebug(
999
);
//
Configure the rules we need for shutting down
digester.addObjectCreate("Server"
,
"org.apache.catalina.core.StandardServer"
,
"className"
);
digester.addSetProperties(
"Server"
);
digester.addSetNext(
"Server",
"setServer",
"org.apache.catalina.Server");
return
(digester);
}
与启动Digester对象不同,关闭Digester对象只对XML文件的根元素感兴趣
org.apache.catalina.startup.Bootstrap类提供了启动tomcat的切入点(还有一些其他的类也有此功能)。当使用bat或sh启动tomcat时,实际上会调用该类的main方法。在main方法中会创建三个loader,并实例化Catalina对象,然后调用Catalina对象的process方法。
Bootstrap类的定义如下所示:
public
final
class
Bootstrap {
//
------------------------------------------------------- Static Variables
/**
* Debugging detail level for processing the startup.
*/
private
static
int
debug = 0
;
//
----------------------------------------------------------- Main Program
/**
* The main program for the bootstrap.
*
*
@param
args Command line arguments to be processed
*/
public
static
void
main(String args[]) {
//
Set the debug flag appropriately
for
(
int
i = 0; i < args.length; i++
) {
if
("-debug"
.equals(args[i]))
debug
= 1
;
}
//
Configure catalina.base from catalina.home if not yet set
if
(System.getProperty("catalina.base") ==
null
)
System.setProperty(
"catalina.base"
, getCatalinaHome());
//
Construct the class loaders we will need
ClassLoader commonLoader =
null
;
ClassLoader catalinaLoader
=
null
;
ClassLoader sharedLoader
=
null
;
try
{
File unpacked[]
=
new
File[1
];
File packed[]
=
new
File[1
];
File packed2[]
=
new
File[2
];
ClassLoaderFactory.setDebug(debug);
unpacked[
0] =
new
File(getCatalinaHome(),
"common" + File.separator + "classes"
);
packed2[
0] =
new
File(getCatalinaHome(),
"common" + File.separator + "endorsed"
);
packed2[
1] =
new
File(getCatalinaHome(),
"common" + File.separator + "lib"
);
commonLoader
=
ClassLoaderFactory.createClassLoader(unpacked, packed2,
null
);
unpacked[
0] =
new
File(getCatalinaHome(),
"server" + File.separator + "classes"
);
packed[
0] =
new
File(getCatalinaHome(),
"server" + File.separator + "lib"
);
catalinaLoader
=
ClassLoaderFactory.createClassLoader(unpacked, packed,
commonLoader);
unpacked[
0] =
new
File(getCatalinaBase(),
"shared" + File.separator + "classes"
);
packed[
0] =
new
File(getCatalinaBase(),
"shared" + File.separator + "lib"
);
sharedLoader
=
ClassLoaderFactory.createClassLoader(unpacked, packed,
commonLoader);
}
catch
(Throwable t) {
log(
"Class loader creation threw exception"
, t);
System.exit(
1
);
}
Thread.currentThread().setContextClassLoader(catalinaLoader);
//
Load our startup class and call its process() method
try
{
SecurityClassLoad.securityClassLoad(catalinaLoader);
//
Instantiate a startup class instance
if
(debug >= 1
)
log(
"Loading startup class"
);
Class startupClass
=
catalinaLoader.loadClass
(
"org.apache.catalina.startup.Catalina"
);
Object startupInstance
=
startupClass.newInstance();
//
Set the shared extensions class loader
if
(debug >= 1
)
log(
"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);
//
Call the process() method
if
(debug >= 1
)
log(
"Calling startup class process() method"
);
methodName
= "process"
;
paramTypes
=
new
Class[1
];
paramTypes[
0] =
args.getClass();
paramValues
=
new
Object[1
];
paramValues[
0] =
args;
method
=
startupInstance.getClass().getMethod(methodName, paramTypes);
method.invoke(startupInstance, paramValues);
}
catch
(Exception e) {
System.out.println(
"Exception during startup processing"
);
e.printStackTrace(System.out);
System.exit(
2
);
}
}
/**
* Get the value of the catalina.home environment variable.
*/
private
static
String getCatalinaHome() {
return
System.getProperty("catalina.home"
,
System.getProperty(
"user.dir"
));
}
/**
* Get the value of the catalina.base environment variable.
*/
private
static
String getCatalinaBase() {
return
System.getProperty("catalina.base"
, getCatalinaHome());
}
/**
* Log a debugging detail message.
*
*
@param
message The message to be logged
*/
private
static
void
log(String message) {
System.out.print(
"Bootstrap: "
);
System.out.println(message);
}
/**
* Log a debugging detail message with an exception.
*
*
@param
message The message to be logged
*
@param
exception The exception to be logged
*/
private
static
void
log(String message, Throwable exception) {
log(message);
exception.printStackTrace(System.out);
}
}
Bootstrap类定义了四个静态方法,其中getCatalinaHome返回catalina.home属性的值,若没有,则返回user.dir属性的值。getCatalinaBase方法与getCatalinaHome方法类似。
Bootstrap类的main方法构造了三个loader,之所以如此做是为了防止运行WEB-INF/classes和WEB-INF/lib目录外的类。
其中commonLoader允许从%CATALINA_HOME%/common/classes,%CATALINA_HOME%/common/endorsed和%CATALINA_HOME%/common/lib目录下载入类。
catalinaLoader负责载入servlet容器需要使用的类,它只会从%CATALINA_HOME%/server/classes和%CATALINA_HOME%/server/lib目录下查找。
sharedLoader会从%CATALINA_HOME%/shared/classes和%CATALJNA_HOME%/shared/lib目录,以及对commonLoader可用的目录下查找需要的类。
然后,将sharedLoader设置为每个web应用的类载入器的父类载入器。(注意,sharedLoader并不访问catalina的内部类,或CLASSPATH中的类)
在创建了三个loader之后,main方法会载入Catalina类,实例化,并将之赋值给startupInstance变量。然后调用setParentClassLoader方法。
最后,main方法调用Catalina对象的process对象。
---------------------------------------------------------------------------
本系列How Tomcat Works系本人原创
转载请注明出处 博客园 刺猬的温驯
本人邮箱: chenying998179 # 163.com ( #改为@ )

