引言

Tomcat启动入口就在脚本startup.sh中,具体脚本可以看tomcat的源码,这个启动脚本主要用来判断环境,
找到catalina.sh脚本路径,将启动参数传递给catalina.sh执行。
catalina.sh start 最终会执行org.apache.catalina.startup.Bootstrap中的main方法,并把start参数传入。
以后分析Tomcat关闭的时候,也是一个套路,最终都会调用到org.apache.catalina.startup.Bootstrap的main方法,并把stop参数传入。

分析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
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
/**
* 通过提供的脚本启动Tomcat时的主方法和入口点。
*
* @param args 要处理的命令行参数
*/
public static void main(String args[]) {

//main使用的守护进程对象。
synchronized (daemonLock) {
//daemon是volatile修饰的Bootstrap对象
if (daemon == null) {
//在init()完成之前不要设置守护进程
Bootstrap bootstrap = new Bootstrap();
try {
//初始化守护进程。
bootstrap.init();
} catch (Throwable t) {
handleThrowable(t);
t.printStackTrace();
return;
}
daemon = bootstrap;
} else {
// W当作为服务运行时,要停止的调用将位于新线程上,
// 因此请确保使用了正确的类加载器,以防止出现一系列类未找到异常。直到init()完成
Thread.currentThread().setContextClassLoader(daemon.catalinaLoader);
}
}

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")) {
//启动操作
//通过反射调用守护进程引用的org.apache.catalina.startup.Catalina实例的setAwait方法
daemon.setAwait(true);
//调用Catalina实例的load方法
daemon.load(args);
//start方法
daemon.start();
//反射调用Catalina实例的getServer方法,返回的对象为空时,终止当前运行的Java虚拟机。
if (null == daemon.getServer()) {
System.exit(1);
}
} else if (command.equals("stop")) {
//通过反射调用Catalina的stopServer方法。
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、初始化守护进程,获取类加载器和反射实例化org.apache.catalina.startup.Catalina。
2、根据启动参数start,通过反射,依次执行Catalina实例的setAwait、load、start方法。

下面主要分析Catalina实例的setAwait、load、start方法:

1 setAwait

入参为true

1
2
3
4
5
6
7
8
/**
* Use await.
*/
protected boolean await = false;

public void setAwait(boolean b) {
await = b;
}

2 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
/**
* 启动一个新的服务器实例。
*/
public void load() {
//防止重复加载。
if (loaded) {
return;
}
loaded = true;
long t1 = System.nanoTime();
//创建java.io.tmpdir文件夹
initDirs();

// Before digester - it may be needed
//初始化jmx的环境变量
initNaming();

// Create and execute our Digester
//创建和配置将用于启动的Digester。
//配置解析server.xml中各个标签的解析类
Digester digester = createStartDigester();

InputSource inputSource = null;
InputStream inputStream = null;
File file = null;
try {

//下面一大段都是为了加载conf/server.xml配置文件,失败就加载server-embed.xml
...
try {
inputSource.setByteStream(inputStream);
//把Catalina作为一个顶级容器
digester.push(this);
//解析过程会实例化各个组件,比如Server、Container、Connector等
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 {
if (inputStream != null) {
try {
inputStream.close();
} catch (IOException e) {
// Ignore
}
}
}

//这里的server在解析xml之后就有值了,这是Server的Catalina信息
getServer().setCatalina(this);
getServer().setCatalinaHome(Bootstrap.getCatalinaHomeFile());
getServer().setCatalinaBase(Bootstrap.getCatalinaBaseFile());

// Stream redirection
initStreams();

// Start the new server
try {
//生命周期init方法
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");
}
}

主要流程:

  • 初始化JMX环境变量
  • 创建和配置Digester
  • 读取配置文件conf/server.xml配置文件,失败就加载server-embed.xml
  • 通过Digester解析配置文件,并将当前Catalina作为最顶层容器,解析过程会实例化各种组件
  • 设置Server组件的catalina信息
  • 调用Server组件的生命周期init方法

解析配置文件,注入catalina的各种组件后面分析。
下面看start方法:

3 start

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
/**
* 启动一个新的服务器实例。
*/
public void start() {
//如果Server组件不存在,则重新执行load方法
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 {
//调用Server的start方法
getServer().start();
} catch (LifecycleException e) {
log.fatal(sm.getString("catalina.serverStartFail"), e);
try {
//异常的时候调用Server的destroy方法
getServer().destroy();
} catch (LifecycleException e1) {
log.debug("destroy() failed for failed Server ", e1);
}
return;
}
long t2 = System.nanoTime();
if (log.isInfoEnabled()) {
log.info("Server startup in " + ((t2 - t1) / 1000000) + " ms");
}

// Register shutdown hook
//注册关闭钩子
if (useShutdownHook) {
if (shutdownHook == null) {
// Catalina.this.stop();
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);
}
}
// Bootstrap中会设置await为true,其目的在于让tomcat在shutdown端口阻塞监听关闭命令
if (await) {
//等待收到正确的关机命令,然后返回。
await();
//停止现有的服务器实例。
stop();
}
}

流程:

  • 调用Server组件的start方法,开启一个新的服务。
  • 注册关闭钩子
  • 让Tomcat在shutdown端口阻塞监听关闭命令

本篇目的就是了解整个Tomcat启动的主干流程,体现在代码层的就是依次执行Catalina实例的setAwait、load、start方法。
其中的load方法中的解析配置文件与注册组件、执行生命周期方法init;
start方法中的开启服务、注册关闭钩子、阻塞监听关闭指令等详细细节,将在后期慢慢分析。