tomcat中各个组件的生命周期是由server控制的。那么server的生命周期由谁控制呢?
这个main方法首先会new一个Bootstrap对象,并且把这个对象放到一个static域中。
然后会对这个对象进行初始化,初始化方法如下:
我们之前提到过tomcat中server用来管理整个tomcat的生命周期
server start之后,catalina会创建一个 CatalinaShutdownHook 对象,然后将它添加到运行时的shutdownHook中。即注册一个虚拟机的shutdown hook,这样在我们不通过脚本关闭tomcat,而是直接杀死进程的时候,也能够执行catalina的stop方法,完成tomcat的关闭过程。
2、java虚拟机被终止,包括被用户中断,如:^C,或者系统级的事件:用户登出、系统关闭等。
发生上面的情况的时候,虚拟机就会调用所有的shutdown hook。
综上,tomcat启动过程实际就是bootstrap调用catalina的start方法,然后catalina调用server的start方法。关闭的时候,可以是bootstrap的stop方法调用catalina的stop方法,也可以是Hook调用catalina的stop方法,然后catalina的stop方法调用server的stop方法。
我们先来看下使用脚本启动tomcat的时候,首先会发生什么。
java应用要运行,需要一个main方法。tomcat启动的时候调用的是bootstrap中的main方法。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
|
/**
* Main method, used for testing only.
*
* @param args Command line arguments to be processed
*/
public
static
void
main(String args[]) {
if
(daemon ==
null
) {
// Don't set daemon until init() has completed
Bootstrap bootstrap =
new
Bootstrap();
try
{
bootstrap.init();
}
catch
(Throwable t) {
handleThrowable(t);
t.printStackTrace();
return
;
}
daemon = bootstrap;
}
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
if
(command.equals(
"configtest"
)) {
daemon.load(args);
if
(
null
==daemon.getServer()) {
System.exit(
1
);
}
System.exit(
0
);
}
else
{
log.warn(
"Bootstrap: command \""
+ command +
"\" does not exist."
);
}
}
catch
(Throwable t) {
// Unwrap the Exception for clearer error reporting
if
(t
instanceof
InvocationTargetException &&
t.getCause() !=
null
) {
t = t.getCause();
}
handleThrowable(t);
t.printStackTrace();
System.exit(
1
);
}
}
|
然后会对这个对象进行初始化,初始化方法如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
|
/**
* Initialize daemon.
*/
public
void
init()
throws
Exception
{
// Set Catalina path
setCatalinaHome();
setCatalinaBase();
initClassLoaders();
Thread.currentThread().setContextClassLoader(catalinaLoader);
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;
}
|
在这个init方法中,会new一个catalina对象。bootstrap的大多数操作,诸如start、stop、load等,实际上都是调用的这个catalina对象的相应方法。
在bootstrap的初始化完成之后,会根据用户输入的main函数的参数(start、stop等),判断执行什么操作。
如果是start参数,就表明是启动tomcat。会先后执行daemon.setAwait、daemon.load、daemon.start这三个方法。他们最终会分别采用反射的方式去调用之前已经初始化的catalina的对应方法setAwait、load、start。也就是说其实bootstrap就调用了catalina的方法,本身是没做什么额外动作的。
stop则表明是关闭tomcat。
1
2
3
4
|
/**
* Daemon object used by main.
*/
private
static
Bootstrap daemon =
null
;
|
这里需要注意一点,我们使用脚本启动和关闭tomcat的时候,实际上最终都是执行bootstrap的main方法,正因为daemon是static的,所以,我们start和stop的时候,实际上操作的是同一个bootstrap对象,才能对同一个tomcat的启动和关闭。
如上面所说,在启动过程中,daemon.load会调用catalina的load方法。catalina的load方法如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
|
/**
* Start a new server instance.
*/
public
void
load() {
long
t1 = System.nanoTime();
initDirs();
// Before digester - it may be needed
initNaming();
// Create and execute our Digester
Digester digester = createStartDigester();
InputSource inputSource =
null
;
InputStream inputStream =
null
;
File file =
null
;
try
{
file = configFile();
inputStream =
new
FileInputStream(file);
inputSource =
new
InputSource(
"file://"
+ file.getAbsolutePath());
}
catch
(Exception e) {
if
(log.isDebugEnabled()) {
log.debug(sm.getString(
"catalina.configFail"
, file), e);
}
}
if
(inputStream ==
null
) {
try
{
inputStream = getClass().getClassLoader()
.getResourceAsStream(getConfigFile());
inputSource =
new
InputSource
(getClass().getClassLoader()
.getResource(getConfigFile()).toString());
}
catch
(Exception e) {
if
(log.isDebugEnabled()) {
log.debug(sm.getString(
"catalina.configFail"
,
getConfigFile()), e);
}
}
}
// This should be included in catalina.jar
// Alternative: don't bother with xml, just create it manually.
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
(log.isDebugEnabled()) {
log.debug(sm.getString(
"catalina.configFail"
,
"server-embed.xml"
), e);
}
}
}
if
(inputStream ==
null
|| inputSource ==
null
) {
if
(file ==
null
) {
log.warn(sm.getString(
"catalina.configFail"
,
getConfigFile() +
"] or [server-embed.xml]"
));
}
else
{
log.warn(sm.getString(
"catalina.configFail"
,
file.getAbsolutePath()));
if
(file.exists() && !file.canRead()) {
log.warn(
"Permissions incorrect, read permission is not allowed on the file."
);
}
}
return
;
}
try
{
inputSource.setByteStream(inputStream);
digester.push(
this
);
digester.parse(inputSource);
}
catch
(SAXParseException spe) {
log.warn(
"Catalina.start using "
+ getConfigFile() +
": "
+
spe.getMessage());
return
;
}
catch
(Exception e) {
log.warn(
"Catalina.start using "
+ getConfigFile() +
": "
, e);
return
;
}
finally
{
try
{
inputStream.close();
}
catch
(IOException e) {
// Ignore
}
}
getServer().setCatalina(
this
);
// Stream redirection
initStreams();
// Start the new server
try
{
getServer().init();
}
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,生成tomcat的组件:server、service、connector、engine、host、context等。最后调用server的init方法。
我们之前提到过tomcat中server用来管理整个tomcat的生命周期
daemon.load方法执行之后,会执行daemon.start,这个方法调用catalina的start方法,catalina调用server的start方法,从而启动tomcat的各个组件。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
|
/**
* Start a new server instance.
*/
public
void
start() {
if
(getServer() ==
null
) {
load();
}
if
(getServer() ==
null
) {
log.fatal(
"Cannot start server. Server instance is not configured."
);
return
;
}
long
t1 = System.nanoTime();
// Start the new server
try
{
getServer().start();
}
catch
(LifecycleException e) {
log.error(
"Catalina.start: "
, e);
}
long
t2 = System.nanoTime();
if
(log.isInfoEnabled()) {
log.info(
"Server startup in "
+ ((t2 - t1) /
1000000
) +
" ms"
);
}
// Register shutdown hook
if
(useShutdownHook) {
if
(shutdownHook ==
null
) {
shutdownHook =
new
CatalinaShutdownHook();
}
Runtime.getRuntime().addShutdownHook(shutdownHook);
// If JULI is being used, disable JULI's shutdown hook since
// shutdown hooks run in parallel and log messages may be lost
// if JULI's hook completes before the CatalinaShutdownHook()
LogManager logManager = LogManager.getLogManager();
if
(logManager
instanceof
ClassLoaderLogManager) {
((ClassLoaderLogManager) logManager).setUseShutdownHook(
false
);
}
}
if
(await) {
await();
stop();
}
}
|
server start之后,catalina会创建一个 CatalinaShutdownHook 对象,然后将它添加到运行时的shutdownHook中。即注册一个虚拟机的shutdown hook,这样在我们不通过脚本关闭tomcat,而是直接杀死进程的时候,也能够执行catalina的stop方法,完成tomcat的关闭过程。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
/**
* Shutdown hook which will perform a clean shutdown of Catalina if needed.
*/
protected
class
CatalinaShutdownHook
extends
Thread {
@Override
public
void
run() {
try
{
if
(getServer() !=
null
) {
Catalina.
this
.stop();
}
}
catch
(Throwable ex) {
ExceptionUtils.handleThrowable(ex);
log.error(sm.getString(
"catalina.shutdownHookFail"
), ex);
}
finally
{
// If JULI is used, shut JULI down *after* the server shuts down
// so log messages aren't lost
LogManager logManager = LogManager.getLogManager();
if
(logManager
instanceof
ClassLoaderLogManager) {
((ClassLoaderLogManager) logManager).shutdown();
}
}
}
}
|
The Java virtual machine shuts down in response to two kinds of events:
1、程序正常退出,包括最后一个non-daemon线程退出或者Runtime.exit、System.exit方法被调用。
2、java虚拟机被终止,包括被用户中断,如:^C,或者系统级的事件:用户登出、系统关闭等。
发生上面的情况的时候,虚拟机就会调用所有的shutdown hook。
综上,tomcat启动过程实际就是bootstrap调用catalina的start方法,然后catalina调用server的start方法。关闭的时候,可以是bootstrap的stop方法调用catalina的stop方法,也可以是Hook调用catalina的stop方法,然后catalina的stop方法调用server的stop方法。