Tomcat(二)启动过程

Tomcat组件生命周期管理

Tomcat中ServerServiceConnectorEngineHostContext的继承关系图,你会发现它们都实现org.apache.catalina.Lifecycle接口,而org.apache.catalina.util.LifecycleBase采用模板方法模式来对所有支持生命周期管理的组件的生命周期各个阶段进行总体管理,每个需要生命周期管理的组件只需要继承这个基类,然后覆盖对应的钩子方法即可完成相应的生命周期阶段性的管理工作。
下面我们首先来看看org.apache.catalina.Lifecycle接口的定义,它的类图如下图所示。

从上图我们可以清楚的看到LifeCycle中主要有四个生命周期阶段,它们分别是init()初始化start()启动stop()停止destory()销毁。知道这四个生命周期阶段以后,看看org.apache.catalina.util.LifecycleBase是如何实现模板方法模式的。
那接下来我们就来看看org.apache.catalina.util.LifecycleBase类的定义,它的类图如下所示。

上图中用红色标注的四个方法就是模板方法模式中的钩子方法,子类可以通过实现钩子方法来纳入到基类已经流程化好的生命周期管理中。
上面我们对LifeCycleLifeCycleBase有一个总体的认识,接下来,我们通过查看org.apache.catalina.util.LifecycleBase的源代码来具体的分析一下。

org.apache.catalina.util.LifecycleBase#init

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@Override
public final synchronized void init() throws LifecycleException {
// 1
if (!state.equals(LifecycleState.NEW)) {
invalidTransition(Lifecycle.BEFORE_INIT_EVENT);
}
setStateInternal(LifecycleState.INITIALIZING, null, false);
try {
// 2
initInternal();
} catch (Throwable t) {
ExceptionUtils.handleThrowable(t);
setStateInternal(LifecycleState.FAILED, null, false);
throw new LifecycleException(
sm.getString("lifecycleBase.initFail",toString()), t);
}
// 3
setStateInternal(LifecycleState.INITIALIZED, null, false);
}

下面我们逐一来分析一下上述代码中标注数字的地方。

  • 标注1的代码:首先检测当前组件的状态是不是LifecycleState.NEW(新建),如果不是就调用 org.apache.catalina.util.LifecycleBase#invalidTransition()来将当前的状态转换过程终止,而invalidTransition的实现是抛出org.apache.catalina.LifecycleException异常。接着调用setStateInternal()将状态设置为INITIALIZING(正在初始化)
  • 标注2的代码:就是init()模板方法的钩子,子类可以通过实现protected abstract void initInternal() throws LifecycleException;来纳入初始化的流程。
  • 标注3的代码:将组件的状态改为LifecycleState.INITIALIZED(已初始化)。

上面我们分析init()模板方法,接下来我们再看看start()具体做什么事情。
org.apache.catalina.util.LifecycleBase#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
/**
* {@inheritDoc}
*/
@Override
public final synchronized void start() throws LifecycleException {
// 1
if (LifecycleState.STARTING_PREP.equals(state) ||
LifecycleState.STARTING.equals(state) ||
LifecycleState.STARTED.equals(state)) {
if (log.isDebugEnabled()) {
Exception e = new LifecycleException();
log.debug(sm.getString("lifecycleBase.alreadyStarted",
toString()), e);
} else if (log.isInfoEnabled()) {
log.info(sm.getString("lifecycleBase.alreadyStarted",
toString()));
}
return;
}
// 2
if (state.equals(LifecycleState.NEW)) {
init();
} else if (state.equals(LifecycleState.FAILED)){
stop();
} else if (!state.equals(LifecycleState.INITIALIZED) &&
!state.equals(LifecycleState.STOPPED)) {
invalidTransition(Lifecycle.BEFORE_START_EVENT);
}
// 3
setStateInternal(LifecycleState.STARTING_PREP, null, false);
try {
// 4
startInternal();
} catch (Throwable t) {
ExceptionUtils.handleThrowable(t);
setStateInternal(LifecycleState.FAILED, null, false);
throw new LifecycleException(
sm.getString("lifecycleBase.startFail",toString()), t);
}
// 5
if (state.equals(LifecycleState.FAILED) ||
state.equals(LifecycleState.MUST_STOP)) {
stop();
} else {
// Shouldn't be necessary but acts as a check that sub-classes are
// doing what they are supposed to.
if (!state.equals(LifecycleState.STARTING)) {
invalidTransition(Lifecycle.AFTER_START_EVENT);
}
setStateInternal(LifecycleState.STARTED, null, false);
}
}

下面我们逐一来分析一下上述代码中标注数字的地方:

  • 标注1的代码:检测当前组件的状态是不是LifecycleState.STARTING_PREP(准备启动),LifecycleState.STARTING(正在启动),LifecycleState.STARTED(已启动)。如果是这三个状态中的任何一个,则抛出LifecycleException
  • 标注2的代码:检查其实主要是为保证组件状态的完整性,在正常启动的流程中,应该是不会出现没有初始化就启动,或者还没启动就已经失败的情况。
  • 标注3的代码:设置组件的状态为LifecycleState.STARTING_PREP(准备启动状态)。
  • 标注4的代码:start()模板方法的钩子方法,子类通过实现 org.apache.catalina.util.LifecycleBase#startInternal()这个方法来纳入到组件启动的流程中来。
  • 标注5的代码:做一些状态检查,然后最终将组件的状态设置为LifecycleState.STARTED(已启动)。

上面我们分析init()start()的流程,对于stop()destroy()的总体过程是类似的,通过上面的分析,我们可以得出生命周期方法的总体的骨架,如果用伪代码来表示可以简化为如下。
org.apache.catalina.util.LifecycleBase#lifeCycleMethod()

1
2
3
4
5
6
7
8
public final synchronized void lfieCycleMethod() throws LifecycleException {
stateCheck();//状态检查
//设置为进入相应的生命周期之前的状态
setStateInternal(LifecycleState.BEFORE_STATE, null, false);
lfieCycleMethodInternal();//钩子方法
//进入相应的生命周期之后的状态
setStateInternal(LifecycleState.AFTER_STATE, null, false);
}

Tomcat启动的总过程

通过上面的介绍,清楚各个组件的生命周期的各个阶段具体都是如何运作的。接下来我们就来看看,Tomcat具体是如何一步步启动起来的。我们都知道任何Java程序都有一个main()入口,Tomcat中的main()入口是 org.apache.catalina.startup.Bootstrap#main(),下面我们就来分析一下它的代码。
org.apache.catalina.startup.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
61
62
63
64
65
66
/**
* Main method and entry point when starting Tomcat via the provided
* scripts.
*
* @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
// 1
Bootstrap bootstrap = new Bootstrap();
try {
// 2
bootstrap.init();
} catch (Throwable t) {
handleThrowable(t);
t.printStackTrace();
return;
}
// 3
daemon = bootstrap;
} else {
// When running as a service the call to stop will be on a new
// thread so make sure the correct class loader is used to prevent
// a range of class not found exceptions.
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")) {
// 4
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的代码:对BootStrap实例进行初始化。
  • 标注3的代码:将实例赋值给daemon
  • 标注4的代码:首先调用BootStrap#load(),然后调用start()

分别分析一下BootStrap的init()load()start()具体做哪些工作。

BootStrap#init()

org.apache.catalina.startup.Bootstrap#init()

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
/**
* 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");
// 1
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);
// 2
method.invoke(startupInstance, paramValues);
// 3
catalinaDaemon = startupInstance;
}

下面我们重点逐一来分析一下上述代码中标注数字的地方:

  • 标注1的代码:通过反射实例化org.apache.catalina.startup.Catalina类的实例;
  • 标注2的代码:调用Catalina实例的setParentClassLoader()设置父亲ClassLoader,对于ClassLoader方面的内容,在本系列的后续文章再来看看。
  • 标注3的代码:将Catalina实例赋值给Bootstrap实例的catalinaDaemon

BootStrap#load()

接下来我们再来看看org.apache.catalina.startup.Bootstrap#load()
org.apache.catalina.startup.Bootstrap#load()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
/**
* Load daemon.
*/
private void load(String[] arguments) throws Exception {
// Call the load() method
String methodName = "load";
Object param[];
Class<?> paramTypes[];
if (arguments==null || arguments.length==0) {
paramTypes = null;
param = null;
} else {
paramTypes = new Class[1];
paramTypes[0] = arguments.getClass();
param = new Object[1];
param[0] = arguments;
}
Method method = catalinaDaemon.getClass().getMethod(methodName, paramTypes);
if (log.isDebugEnabled())
log.debug("Calling startup class " + method);
method.invoke(catalinaDaemon, param);
}

通过查看源代码,知道此方法通过反射调用org.apache.catalina.startup.Catalina#load(),那我们就来看看Catalina#load()Catalina#load()代码如下。
org.apache.catalina.startup.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
/**
* Start a new server instance.
*/
public void load() {
long t1 = System.nanoTime();
initDirs();
// Before digester - it may be needed
initNaming();
// 1
// 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.toURI().toURL().toString());
} 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 {
// 2
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");
}
}

下面重点逐一来分析一下上述代码中标注数字的地方:

  • 标注1的代码:创建Digester实例解析conf/server.xml文件。
  • 标注2的代码:最终调用StandardServer#init()
    自行查看下源代码,我们会发现如下的一个调用流程:
    1
    2
    3
    4
    org.apache.catalina.core.StandardServer#init
    ->org.apache.catalina.core.StandardService#init
    -->org.apache.catalina.connector.Connector#init
    -->org.apache.catalina.core.StandardEngine#init

因为StandardServiceConnectorStandardEngine实现LifeCycle接口,因此符合我们上文所获的生命周期的管理,最终都是通过他们自己实现的initInternal()进行初始化。
以为StandardEngine#init()会调用StandardHost#init(),但是当查看StandardEngine#init()的时候,发现并没有进行StandardHost的初始化,它到底做什么呢?来具体分析一下,首先拿StandardEngine的继承关系图来看下。

通过上图以及前面说的LifeCyecle的模板方法模式,知道StandardEngine的初始化钩子方法initInternal()最终调用ContainerBase#initInternal(),那我们拿ContainerBase#initInternal()的代码看看。
org.apache.catalina.core.ContainerBase#initInternal()

1
2
3
4
5
6
7
8
9
10
11
@Override
protected void initInternal() throws LifecycleException {
BlockingQueue<Runnable> startStopQueue = new LinkedBlockingQueue<Runnable>();
startStopExecutor = new ThreadPoolExecutor(
getStartStopThreadsInternal(),
getStartStopThreadsInternal(), 10, TimeUnit.SECONDS,
startStopQueue,
new StartStopThreadFactory(getName() + "-startStop-"));
startStopExecutor.allowCoreThreadTimeOut(true);
super.initInternal();
}

我们可以看到StandardEngine的初始化仅仅是创建一个ThreadPoolExecutorStandardEngine#init()竟然没有调用StandardHost#init(),那么StandardHost#init()是什么时候被调用的呢?遇到这种不知道到底方法怎么调用的时候怎么办呢?现在需要知道StandardHost#init()何时被调用的,知道init()最终会调用钩子的initInternal(),因此这个时候,可以在StandardHostoverride initInternal(),增加实现方法以后,有两种方法可以用,一种就是设置个断点debug一下就可以看出线程调用栈,另外一种就是在新增的方法中打印出调用栈。
这里采用第二种方法,增加如下的initInternal()StandardHost中。
org.apache.catalina.core.StandardHost#initInternal()

1
2
3
4
5
6
7
8
9
10
11
12
13
protected void initInternal() throws LifecycleException {
Throwable ex = new Throwable();
StackTraceElement[] stackElements = ex.getStackTrace();
if (stackElements != null) {
for (int i = stackElements.length - 1; i >= 0; i--) {
System.out.print(stackElements[i].getClassName() + "\t");
System.out.print(stackElements[i].getMethodName() + "\t");
System.out.print(stackElements[i].getFileName() + "\t");
System.out.println(stackElements[i].getLineNumber());
}
}
super.initInternal();
}

上面的代码将会打印出方法调用堆栈,对于调试非常有用,上面的方法运行以后在控制台打印出如下的堆栈信息。

1
2
3
4
5
6
7
8
9
10
java.lang.Thread    run  Thread.java   680
java.util.concurrent.ThreadPoolExecutor$Worker run ThreadPoolExecutor.java 918
java.util.concurrent.ThreadPoolExecutor$Worker runTask ThreadPoolExecutor.java 895
java.util.concurrent.FutureTask run FutureTask.java 138
java.util.concurrent.FutureTask$Sync innerRun FutureTask.java 303
org.apache.catalina.core.ContainerBase$StartChild call ContainerBase.java 1549
org.apache.catalina.core.ContainerBase$StartChild call ContainerBase.java 1559
org.apache.catalina.util.LifecycleBase start LifecycleBase.java 139
org.apache.catalina.util.LifecycleBase init LifecycleBase.java 102
org.apache.catalina.core.StandardHost initInternal StandardHost.java 794

通过控制台的信息,我们看到是StartChild#call()调用的,而我们查看StartChild#call()其实是在StandardEngine#startInternal()中通过异步线程池去初始化子容器。
org.apache.catalina.core. StandardEngine#startInternal()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/**
* Start this component and implement the requirements
* of {@link org.apache.catalina.util.LifecycleBase#startInternal()}.
*
* @exception LifecycleException if this component detects a fatal error
* that prevents this component from being used
*/
@Override
protected synchronized void startInternal() throws LifecycleException {
// Log our server identification information
if(log.isInfoEnabled())
log.info( "Starting Servlet Engine: " + ServerInfo.getServerInfo());
// Standard container startup
super.startInternal();
}

这里StandardEngine#startInternal()调用父类的startInternal()
org.apache.catalina.core.ContainerBase#startInternal()

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
/**
* Start this component and implement the requirements
* of {@link org.apache.catalina.util.LifecycleBase#startInternal()}.
*
* @exception LifecycleException if this component detects a fatal error
* that prevents this component from being used
*/
@Override
protected synchronized void startInternal() throws LifecycleException {
// Start our subordinate components, if any
if ((loader != null) && (loader instanceof Lifecycle))
((Lifecycle) loader).start();
logger = null;
getLogger();
if ((manager != null) && (manager instanceof Lifecycle))
((Lifecycle) manager).start();
if ((cluster != null) && (cluster instanceof Lifecycle))
((Lifecycle) cluster).start();
Realm realm = getRealmInternal();
if ((realm != null) && (realm instanceof Lifecycle))
((Lifecycle) realm).start();
if ((resources != null) && (resources instanceof Lifecycle))
((Lifecycle) resources).start();
// Start our child containers, if any
Container children[] = findChildren();
List<Future<Void>> results = new ArrayList<Future<Void>>();
for (int i = 0; i < children.length; i++) {
results.add(startStopExecutor.submit(new StartChild(children[i])));
}
boolean fail = false;
for (Future<Void> result : results) {
try {
result.get();
} catch (Exception e) {
log.error(sm.getString("containerBase.threadedStartFailed"), e);
fail = true;
}
}
if (fail) {
throw new LifecycleException(
sm.getString("containerBase.threadedStartFailed"));
}
// Start the Valves in our pipeline (including the basic), if any
if (pipeline instanceof Lifecycle)
((Lifecycle) pipeline).start();
setState(LifecycleState.STARTING);
// Start our thread
threadStart();
}

因此到这里我们就理清楚,StarndardHost#init()是在调用start()的时候被初始化。那么接下来我们就来看看,start()的整体调用流程。
BootStrap#start()
采用分析load()一样的方法,经过对BootStrap#start()的分析,最终可以得到得到如下的调用链。
org.apache.catalina.startup.Bootstrap#start call stack

1
2
3
4
5
6
7
org.apache.catalina.startup.Bootstrap#start
->org.apache.catalina.startup.Catalina#start 通过反射调用
-->org.apache.catalina.core.StandardServer#start
--->org.apache.catalina.core.StandardService#start
---->org.apache.catalina.core.StandardEngine#start
---->org.apache.catalina.Executor#start
---->org.apache.catalina.connector.Connector#start

综合上文的描述总体得到如下的调用链。

1
org.apache.catalina.startup.Bootstrap#main call stack

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
org.apache.catalina.startup.Bootstrap#main
->org.apache.catalina.startup.Bootstrap#init
->org.apache.catalina.startup.Bootstrap#load
-->org.apache.catalina.startup.Catalina#load
--->org.apache.catalina.core.StandardServer#init
---->org.apache.catalina.core.StandardService#init
----->org.apache.catalina.connector.Connector#init
----->org.apache.catalina.core.StandardEngine#init
->org.apache.catalina.startup.Bootstrap#start
-->org.apache.catalina.startup.Catalina#start 通过反射调用
--->org.apache.catalina.core.StandardServer#start
---->org.apache.catalina.core.StandardService#start
----->org.apache.catalina.core.StandardEngine#start
----->org.apache.catalina.Executor#start
----->org.apache.catalina.connector.Connector#start

Tomcat启动过程关键步骤分析

Connector#init()

首先来看一下org.apache.catalina.connector.Connector#init(),知道Connector的生命周期也是通过LifeCycle的模板方法模式来管理的,那么只需要查看一下它的initInternal()即可知道它是如何初始化的。
接下来就来看一下initInternal(),代码如下。
org.apache.catalina.connector.Connector#initInternal()

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
@Override
protected void initInternal() throws LifecycleException {
super.initInternal();
// Initialize adapter
adapter = new CoyoteAdapter(this);
protocolHandler.setAdapter(adapter);
// Make sure parseBodyMethodsSet has a default
if( null == parseBodyMethodsSet ) {
setParseBodyMethods(getParseBodyMethods());
}
if (protocolHandler.isAprRequired() &&
!AprLifecycleListener.isAprAvailable()) {
throw new LifecycleException(
sm.getString("coyoteConnector.protocolHandlerNoApr",
getProtocolHandlerClassName()));
}
try {
// 1
protocolHandler.init();
} catch (Exception e) {
throw new LifecycleException
(sm.getString("coyoteConnector.protocolHandlerInitializationFailed"), e);
}
// Initialize mapper listener
mapperListener.init();
}

上面代码中,本文最关心的是标注1的地方,这个地方调用org.apache.coyote.ProtocolHandler#init(),而ProtocolHandler是在Connector的构造函数中初始化,而Connector的构造函数又是Digester类解析conf/server.xml的时候调用的,在来具体看看Connector构造函数中调用的一个核心的方法setProtocol(),下面是其代码。
org.apache.catalina.connector.Connector#Connector()

1
2
3
4
5
6
7
8
9
10
11
public Connector(String protocol) {
setProtocol(protocol);
// Instantiate protocol handler
try {
Class<?> clazz = Class.forName(protocolHandlerClassName);
this.protocolHandler = (ProtocolHandler) clazz.newInstance();
} catch (Exception e) {
log.error(sm.getString(
"coyoteConnector.protocolHandlerInstantiationFailed"), e);
}
}

org.apache.catalina.connector.Connector#setProtocol()

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
/**
* Set the Coyote protocol which will be used by the connector.
*
* @param protocol The Coyote protocol name
*/
public void setProtocol(String protocol) {
if (AprLifecycleListener.isAprAvailable()) {
if ("HTTP/1.1".equals(protocol)) {
setProtocolHandlerClassName("org.apache.coyote.http11.Http11AprProtocol");
} else if ("AJP/1.3".equals(protocol)) {
setProtocolHandlerClassName("org.apache.coyote.ajp.AjpAprProtocol");
} else if (protocol != null) {
setProtocolHandlerClassName(protocol);
} else {
setProtocolHandlerClassName("org.apache.coyote.http11.Http11AprProtocol");
}
} else {
// 1
if ("HTTP/1.1".equals(protocol)) {
setProtocolHandlerClassName("org.apache.coyote.http11.Http11Protocol");
} else if ("AJP/1.3".equals(protocol)) {
setProtocolHandlerClassName("org.apache.coyote.ajp.AjpProtocol");
} else if (protocol != null) {
setProtocolHandlerClassName(protocol);
}
}
}

AJP方式:通过AJP协议进行通讯,AJP主要用于Apache的HTTP服务器和Servlet Web容器之间通讯,它是Packet_Oriented的,换句话说,它发送给浏览器(其他Web Server)的数据是Packet(s),得到Servlet容器的响应也是Packet(s),这里有特殊情况,如果Servlet 容器发送的数据是二进制的,则直接发送给浏览器。此外,AJP还可以重用和Servlet容器之间的Socket连接(Socket Connection),降低创建开销。
从setProtocol的代码中,可以看出主要逻辑分为两块,一种情况是使用APR(Apache Portable Runtime),另外一种是不使用APR的情况。缺省情况下不采用APR库,这样的话,代码会走到标注1的代码分支,这里通过协议的不同,最终初始化不同的类。如果是http1.1协议就采用org.apache.coyote.http11.Http11Protocol,如果是AJP(Apache Jserv Protocol)协议,就采用org.apache.coyote.ajp.AjpProtocol类,下面来看一下Http11Protocol和AjpProtocol的继承关系图如下。


通过上图可以看到它们都继承公共的基类org.apache.coyote.AbstractProtocol,而它们自己的init()最终其实都是调用AbstractProtocol#init(),通过查看AbstractProtocol#init()代码,我们可以看到最终是调用org.apache.tomcat.util.net.AbstractEndpoint#init()
org.apache.coyote.AbstractProtocol#init()

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
/*
* NOTE: There is no maintenance of state or checking for valid transitions
* within this class. It is expected that the connector will maintain state
* and prevent invalid state transitions.
*/
@Override
public void init() throws Exception {
if (getLog().isInfoEnabled())
getLog().info(sm.getString("abstractProtocolHandler.init",
getName()));
if (oname == null) {
// Component not pre-registered so register it
oname = createObjectName();
if (oname != null) {
Registry.getRegistry(null, null).registerComponent(this, oname,
null);
}
}
if (this.domain != null) {
try {
tpOname = new ObjectName(domain + ":" +
"type=ThreadPool,name=" + getName());
Registry.getRegistry(null, null).registerComponent(endpoint,
tpOname, null);
} catch (Exception e) {
getLog().error(sm.getString(
"abstractProtocolHandler.mbeanRegistrationFailed",
tpOname, getName()), e);
}
rgOname=new ObjectName(domain +":type=GlobalRequestProcessor,name=" + getName());
Registry.getRegistry(null, null).registerComponent(
getHandler().getGlobal(), rgOname, null );
}
String endpointName = getName();
endpoint.setName(endpointName.substring(1, endpointName.length()-1));
try {
endpoint.init();
} catch (Exception ex) {
getLog().error(sm.getString("abstractProtocolHandler.initError",
getName()), ex);
throw ex;
}
}

org.apache.tomcat.util.net.AbstractEndpoint#init

1
2
3
4
5
6
 public final void init() throws Exception {
if (bindOnInit) {
bind();
bindState = BindState.BOUND_ON_INIT;
}
}

AbstractEndpoint的实例化操作是在实例化AjpProtocolHttp11Protocol的时候在其构造函数中实例化的,而AjpProtocolHttp11Protocol构造函数中,其实都是初始化org.apache.tomcat.util.net.JIoEndpoint类,只不过根据是http协议还是AJP协议,它们具有不同的连接处理类。
org.apache.coyote.ajp.AjpProtocol()

1
2
3
4
5
6
7
8
public AjpProtocol() {
endpoint = new JIoEndpoint();
cHandler = new AjpConnectionHandler(this);
((JIoEndpoint) endpoint).setHandler(cHandler);
setSoLinger(Constants.DEFAULT_CONNECTION_LINGER);
setSoTimeout(Constants.DEFAULT_CONNECTION_TIMEOUT);
setTcpNoDelay(Constants.DEFAULT_TCP_NO_DELAY);
}

org.apache.coyote.http11.Http11Protocol

1
2
3
4
5
6
7
8
public Http11Protocol() {
endpoint = new JIoEndpoint();
cHandler = new Http11ConnectionHandler(this);
((JIoEndpoint) endpoint).setHandler(cHandler);
setSoLinger(Constants.DEFAULT_CONNECTION_LINGER);
setSoTimeout(Constants.DEFAULT_CONNECTION_TIMEOUT);
setTcpNoDelay(Constants.DEFAULT_TCP_NO_DELAY);
}

其中Http11Protocol的连接处理类为org.apache.coyote.http11.Http11Protocol.Http11ConnectionHandler,而连接处理类为org.apache.coyote.ajp.AjpProtocol.AjpConnectionHandler,因此到这里我们基本清楚Connector的初始化流程,总结如下。
Connect init采用APR的情况

1
2
3
4
5
6
7
8
9
10
11
12
13
//1 HTTP/1.1协议连接器
org.apache.catalina.connector.Connector#init
->org.apache.coyote.http11.Http11AprProtocol#init
-->org.apache.tomcat.util.net.AprEndpoint#init
(org.apache.coyote.http11.Http11AprProtocol.Http11ConnectionHandler)



// 2 AJP/1.3协议连接器
org.apache.catalina.connector.Connector#init
->org.apache.coyote.ajp.AjpAprProtocol#init
-->org.apache.tomcat.util.net.AprEndpoint#init
(org.apache.coyote.ajp.AjpAprProtocol.AjpConnectionHandler)

Connector init 不采用APR的情况

1
2
3
4
5
6
7
8
9
10
11
12
13
// 1 HTTP/1.1协议连接器
org.apache.catalina.connector.Connector#init
->org.apache.coyote.http11.Http11Protocol#init
-->org.apache.tomcat.util.net.JIoEndpoint#init
(org.apache.coyote.http11.Http11Protocol.Http11ConnectionHandler)



// 2 AJP/1.3协议连接器
org.apache.catalina.connector.Connector#init
->org.apache.coyote.ajp.AjpProtocol#init
-->org.apache.tomcat.util.net.JIoEndpoint#init
(org.apache.coyote.ajp.AjpProtocol.AjpConnectionHandler)

这里需要注意,除JIoEndpoint外,还有NIoEndpoint,对于Tomcat7.0.24的代码,并没有采用NIOEndPointNIOEndpoint采用NIO的方式进行Socket的处理。
最后,再来看看org.apache.tomcat.util.net.JIoEndpoint#init()的初始化过程,首先来看一下JIoEndpoint的继承关系图如下。

通过上图知道JIoEndpoint继承AbstractEndpoint,而通过查看源码可知,JIoEndpoint没有实现自己的init(),它默认采用父类的init(),那么我们就来看看AbstractEndpoint#init(),它的代码如下。
org.apache.tomcat.util.net.AbstractEndpoint#init()

1
2
3
4
5
6
public final void init() throws Exception {
if (bindOnInit) {
bind();
bindState = BindState.BOUND_ON_INIT;
}
}

通过查看上面的代码可知,因为bindOnInit默认是true,所以init()调用bind(),而bind()是抽象方法,最终由JIoEndpoint来实现,代码如下。
org.apache.tomcat.util.net.JIoEndpoint#bind()

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
@Override
public void bind() throws Exception {
// Initialize thread count defaults for acceptor
if (acceptorThreadCount == 0) {
acceptorThreadCount = 1;
}
// Initialize maxConnections
if (getMaxConnections() == 0) {
// User hasn't set a value - use the default
setMaxConnections(getMaxThreadsExecutor(true));
}
if (serverSocketFactory == null) {
if (isSSLEnabled()) {
serverSocketFactory =
handler.getSslImplementation().getServerSocketFactory(this);
} else {
serverSocketFactory = new DefaultServerSocketFactory(this);
}
}
if (serverSocket == null) {
try {
if (getAddress() == null) {
serverSocket = serverSocketFactory.createSocket(getPort(),
getBacklog());
} else {
serverSocket = serverSocketFactory.createSocket(getPort(),
getBacklog(), getAddress());
}
} catch (BindException orig) {
String msg;
if (getAddress() == null)
msg = orig.getMessage() + " <null>:" + getPort();
else
msg = orig.getMessage() + " " +
getAddress().toString() + ":" + getPort();
BindException be = new BindException(msg);
be.initCause(orig);
throw be;
}
}
}

通过上面代码可以看出,最终是调用org.apache.tomcat.util.net.ServerSocketFactory#createSocket()创建一个java.net.ServerSocket,并绑定在conf/server.xml中Connector中配置的端口。
结论:Connector#init()的时候,无论是AJP还是HTTP最终其实是调用JioEndpoint的初始化,默认情况在初始化的时候就会创建java.net.ServerSocket绑到到配置的端口上。

Connector#start()

接着再来分析一下Connector#start,因为Connector符合LifeCycle模板方法生命周期管理的机制,因此它的start最终会调用startInternal()org.apache.catalina.connector.Connector#startInternal()代码如下。
org.apache.catalina.connector. Connector#startInternal()

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
/**
* Begin processing requests via this Connector.
*
* @exception LifecycleException if a fatal startup error occurs
*/
@Override
protected void startInternal() throws LifecycleException {
// Validate settings before starting
if (getPort() < 0) {
throw new LifecycleException(sm.getString(
"coyoteConnector.invalidPort", Integer.valueOf(getPort())));
}
setState(LifecycleState.STARTING);
try {
protocolHandler.start();
} catch (Exception e) {
String errPrefix = "";
if(this.service != null) {
errPrefix += "service.getName(): \"" + this.service.getName() + "\"; ";
}
throw new LifecycleException
(errPrefix + " " + sm.getString
("coyoteConnector.protocolHandlerStartFailed"), e);
}
mapperListener.start();
}

通过上面的代码,可以清晰的看到最终调用protocolHandler#start(),而根据Connector#init()流程的分析,这里会分是否采用APR,默认是不采用APR的,这里会根据不同的协议(AJP,HTTP)来调用对应的org.apache.coyote.ProtocolHandler#start()。 其中AJP会采用org.apache.coyote.ajp.AjpProtocol,HTTP协议采用org.apache.coyote.http11.Http11Protocol,而无论是AjpProtocol还是Http11Protocol都会调用JIoEndpoint的方法,那么接下来我们就来看看JioEndpoint#start()JioEndpoint没有实现start(),调用它父类的方法。它的代码如下。
org.apache.tomcat.util.net.AbstractEndpoint#start()

1
2
3
4
5
6
7
public final void start() throws Exception {
if (bindState == BindState.UNBOUND) {
bind();
bindState = BindState.BOUND_ON_START;
}
startInternal();
}

startInternal()JioEndpoint中实现。
org.apache.tomcat.util.net.JioEndpoint#startInternal()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@Override
public void startInternal() throws Exception {
if (!running) {
running = true;
paused = false;
// Create worker collection
if (getExecutor() == null) {
createExecutor();
}
initializeConnectionLatch();
startAcceptorThreads();
// Start async timeout thread
Thread timeoutThread = new Thread(new AsyncTimeout(),
getName() + "-AsyncTimeout");
timeoutThread.setPriority(threadPriority);
timeoutThread.setDaemon(true);
timeoutThread.start();
}
}

从上面的代码可以看出,启动Acceptor线程和AsyncTimeout线程,首先来看看Acceptor线程,我们再来看看startAcceptorThreads(),代码如下。
org.apache.tomcat.util.net.AbstractEndpoint#startAcceptorThreads()

1
2
3
4
5
6
7
8
9
10
11
12
13
protected final void startAcceptorThreads() {
int count = getAcceptorThreadCount();
acceptors = new Acceptor[count];
for (int i = 0; i < count; i++) {
acceptors[i] = createAcceptor();
String threadName = getName() + "-Acceptor-" + i;
acceptors[i].setThreadName(threadName);
Thread t = new Thread(acceptors[i], threadName);
t.setPriority(getAcceptorThreadPriority());
t.setDaemon(getDaemon());
t.start();
}
}

通过上面的代码,可以看出其实是通过org.apache.tomcat.util.net.AbstractEndpoint.Acceptor这个Runable接口的实现类来启动线程,接下来就来看看Acceptor#run(),通过查看run(),它里面其实就是调用java.net.ServerSocket#accept()来接受一个Socket连接。
org.apache.tomcat.util.net.JioEndpoint#Acceptor()

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
protected class Acceptor extends AbstractEndpoint.Acceptor {
@Override
public void run() {
int errorDelay = 0;
// Loop until we receive a shutdown command
while (running) {
// Loop if endpoint is paused
while (paused && running) {
state = AcceptorState.PAUSED;
try {
Thread.sleep(50);
} catch (InterruptedException e) {
// Ignore
}
}
if (!running) {
break;
}
state = AcceptorState.RUNNING;
try {
//if we have reached max connections, wait
countUpOrAwaitConnection();
Socket socket = null;
try {
// Accept the next incoming connection from the server
// socket
socket = serverSocketFactory.acceptSocket(serverSocket);
} catch (IOException ioe) {
countDownConnection();
// Introduce delay if necessary
errorDelay = handleExceptionWithDelay(errorDelay);
// re-throw
throw ioe;
}
// Successful accept, reset the error delay
errorDelay = 0;
// Configure the socket
if (running && !paused && setSocketOptions(socket)) {
// Hand this socket off to an appropriate processor
if (!processSocket(socket)) {
countDownConnection();
// Close socket right away
closeSocket(socket);
}
} else {
countDownConnection();
// Close socket right away
closeSocket(socket);
}
} catch (IOException x) {
if (running) {
log.error(sm.getString("endpoint.accept.fail"), x);
}
} catch (NullPointerException npe) {
if (running) {
log.error(sm.getString("endpoint.accept.fail"), npe);
}
} catch (Throwable t) {
ExceptionUtils.handleThrowable(t);
log.error(sm.getString("endpoint.accept.fail"), t);
}
}
state = AcceptorState.ENDED;
}
}

启动完Acceptor线程以后,接着就会启动AsyncTimeout线程,而这里面需要注意的时候,无论是Acceptor还是AsyncTimeout线程,它们都是Daemon线程,而设置为Daemon的原因,会在下篇Tomcat的关闭中进行说明。

StandardEngine#start()

从本文上面的分析中,得知StandardEngine继承ContainerBase,而StandardEngine#startInternal()钩子方法也仅仅是调用父类ContainerBase#startInternal(),那接下来分析一下ContainerBase#startInternal(),代码如下。
org.apache.catalina.core.ContainerBase#startInternal()

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
/**
* Start this component and implement the requirements
* of {@link org.apache.catalina.util.LifecycleBase#startInternal()}.
*
* @exception LifecycleException if this component detects a fatal error
* that prevents this component from being used
*/
@Override
protected synchronized void startInternal() throws LifecycleException {
// Start our subordinate components, if any
if ((loader != null) && (loader instanceof Lifecycle))
((Lifecycle) loader).start();
logger = null;
getLogger();
if ((manager != null) && (manager instanceof Lifecycle))
((Lifecycle) manager).start();
if ((cluster != null) && (cluster instanceof Lifecycle))
((Lifecycle) cluster).start();
Realm realm = getRealmInternal();
if ((realm != null) && (realm instanceof Lifecycle))
((Lifecycle) realm).start();
if ((resources != null) && (resources instanceof Lifecycle))
((Lifecycle) resources).start();
// Start our child containers, if any
Container children[] = findChildren();
List<Future<Void>> results = new ArrayList<Future<Void>>();
for (int i = 0; i < children.length; i++) {
results.add(startStopExecutor.submit(new StartChild(children[i])));
}
boolean fail = false;
for (Future<Void> result : results) {
try {
result.get();
} catch (Exception e) {
log.error(sm.getString("containerBase.threadedStartFailed"), e);
fail = true;
}
}
if (fail) {
throw new LifecycleException(
sm.getString("containerBase.threadedStartFailed"));
}
// Start the Valves in our pipeline (including the basic), if any
if (pipeline instanceof Lifecycle)
((Lifecycle) pipeline).start();
setState(LifecycleState.STARTING);
// Start our thread
threadStart();
}

可以看到通过startStopExecutor异步的对子容器进行启动,然后设置状态为LifecycleState.STARTING的状态。而startStopExecutor是在容器的initInternal()中进行初始化好的。
org.apache.catalina.core.ContainerBase#initInternal()

1
2
3
4
5
6
7
8
9
10
11
12
@Override
protected void initInternal() throws LifecycleException {
BlockingQueue<Runnable> startStopQueue =
new LinkedBlockingQueue<Runnable>();
startStopExecutor = new ThreadPoolExecutor(
getStartStopThreadsInternal(),
getStartStopThreadsInternal(), 10, TimeUnit.SECONDS,
startStopQueue,
new StartStopThreadFactory(getName() + "-startStop-"));
startStopExecutor.allowCoreThreadTimeOut(true);
super.initInternal();
}

接下来我们就来看看StartChild,StardChild的代码如下。
org.apache.catalina.core.ContainerBase.StartChild

1
2
3
4
5
6
7
8
9
10
11
12
13
private static class StartChild implements Callable<Void> {

private Container child;
public StartChild(Container child) {
this.child = child;
}

@Override
public Void call() throws LifecycleException {
child.start();
return null;
}
}

通过上面的代码,可以看到StartChild实现Callable接口,实现这个接口的类可以将其放到对应的executor中执行,StartChild在运行的时候就会调用到子容器的start(),而此时的父容器是StandardEngine,子容器就是StandardHost,接下来就来看看StandardHost的启动过程。
通过前面对于init()流程的分析,知道StandardHost不是在StandardEngine#init的时候初始化,因此在执行StandardHost#start()的时候,要首先进行init()的调用,init()调用在LifecycleBase#start()里。具体的代码如下。
org.apache.catalina.util.LifecycleBase#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
/**
* {@inheritDoc}
*/
@Override
public final synchronized void start() throws LifecycleException {

if (LifecycleState.STARTING_PREP.equals(state) ||
LifecycleState.STARTING.equals(state) ||
LifecycleState.STARTED.equals(state)) {
if (log.isDebugEnabled()) {
Exception e = new LifecycleException();
log.debug(sm.getString("lifecycleBase.alreadyStarted",
toString()), e);
} else if (log.isInfoEnabled()) {
log.info(sm.getString("lifecycleBase.alreadyStarted",
toString()));
}
return;
}
//因为此时的StandardHost还没有初始化,因此会走到这一步代码
if (state.equals(LifecycleState.NEW)) {
init();
} else if (state.equals(LifecycleState.FAILED)){
stop();
} else if (!state.equals(LifecycleState.INITIALIZED) &&
!state.equals(LifecycleState.STOPPED)) {
invalidTransition(Lifecycle.BEFORE_START_EVENT);
}
setStateInternal(LifecycleState.STARTING_PREP, null, false);
try {
startInternal();
} catch (Throwable t) {
ExceptionUtils.handleThrowable(t);
setStateInternal(LifecycleState.FAILED, null, false);
throw new LifecycleException(
sm.getString("lifecycleBase.startFail",toString()), t);
}
if (state.equals(LifecycleState.FAILED) ||
state.equals(LifecycleState.MUST_STOP)) {
stop();
} else {
// Shouldn't be necessary but acts as a check that sub-classes are
// doing what they are supposed to.
if (!state.equals(LifecycleState.STARTING)) {
invalidTransition(Lifecycle.AFTER_START_EVENT);
}
setStateInternal(LifecycleState.STARTED, null, false);
}
}

对于StandardHost的初始化,是在start()的时候进行的。那接下来看一下StandardHost#init(),通过查看代码,StandardHost本身没有实现initInternal()的钩子方法,也就意味着最终初始化会调用ContainerBase#initInternal(),而通过上文的描述,已经清楚ContainerBase#initInternal()主要是初始化一个startStopExecutor,这个线程池主要是为异步的初始化子容器来用的。
知道StandardEngine初始化的时候,也是初始化一个线程池,而StandardHost也初始化一个线程池,它们的不同点在与创建线程的工厂方法不同,在采用缺省配置的情况下,StandardEngine的线程池中的线程是以Catalina-startStop的形式命名的,而StandardHost是以localhost-startStop的方式进行命名的。
StandardHost#start()调用init()初始化完StandardHost以后,会调用钩子的startInternal(),而startInternal()又是调用ContainerBased#startInternal(),而ContainerBase#startInternal()最终又会去启动子容器的,对于StandardHost来说,子容器就是StandardContext。 因此分析到这里可以得出如下结论。
对于StandardEngineStandardHost的启动,父容器在init()的时候创建一个启动和停止子容器的线程池,然后父容器启动的时候首先通过异步的方式将子容器的启动通过org.apache.catalina.core.ContainerBase.StartChild提交到父容器中对应的线程池中进行启动,而子容器启动的时候首先会初始化,然后再启动。(子容器的初始化时执行init(),这里子容器调用star(),未初始化,先执行init()
另外这里还需要注意一点就是,StandEngine#start()的时候,最终调用ContainerBase#startInternal()(StandEngine没有实现start(),调用LifecycleBase#start()start()里调用startInternal(),这个方法被ContainerBase覆写),而ContainerBase#startInternal()的最后,调用threadStart(),我们来看看它的代码如下。
org.apache.catalina.core.ContainerBase#threadStart()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/**
* Start the background thread that will periodically check for
* session timeouts.
*/
protected void threadStart() {
if (thread != null)
return;
if (backgroundProcessorDelay <= 0)
return;
threadDone = false;
String threadName = "ContainerBackgroundProcessor[" + toString() + "]";
thread = new Thread(new ContainerBackgroundProcessor(), threadName);
thread.setDaemon(true);
thread.start();
}

上面的代码,首先会判断backgroundProcessorDelay是否小于0,而这个值默认情况下是-1,也就意味这后面的代码不会运行,而对于StandardEngine来说,它将backgroundProcessorDelay的值在构造函数中赋值为10,这样的话,当StandardEngine启动的时候,就会启动名称为ContainerBackgroundProcessor[StandardEngine[Catalina]]的线程。
经过上面的分析,我们已经清楚StandardEngine启动的过程,但是我们还有一个地方需要进一步的分析。
因为上面的分析仅仅只是分析容器通过conf/server.xml配置文件的配置结构进行的启动,而都知道CATALINA-HOME/webapps/中的应用也是需要启动的,那么webapps目录的应用又是如何启动的呢?下面来分析一下,通过Tomcat总体结构的描述,已经知道,webapps目录下面的应用其实是属于Context的,而Context对应Tomcat中的StandardContext类,因此就知道应该对谁下手,知道目标以后,还是采用之前的那种方式,打印调用栈,这里还是通过打印调用栈的方式进行,在org.apache.catalina.core.StandardContext#initInternal()中增加打印调用栈的方法,具体代码如下。
org.apache.catalina.core.StandardContext#initInternal()

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
protected void initInternal() throws LifecycleException {
super.initInternal();
Throwable ex = new Throwable();
StackTraceElement[] stackElements = ex.getStackTrace();
if (stackElements != null) {
for (int i = stackElements.length - 1; i >= 0; i--) {
System.out.print(stackElements[i].getClassName() + "\t");
System.out.print(stackElements[i].getMethodName() + "\t");
System.out.print(stackElements[i].getFileName() + "\t");
System.out.println(stackElements[i].getLineNumber());
}
}

if (processTlds) {
this.addLifecycleListener(new TldConfig());
}

// Register the naming resources
if (namingResources != null) {
namingResources.init();
}

// Send j2ee.object.created notification
if (this.getObjectName() != null) {
Notification notification = new Notification("j2ee.object.created",
this.getObjectName(), sequenceNumber.getAndIncrement());
broadcaster.sendNotification(notification);
}
}

运行代码,可以看到控制台有如下的输出。

1
2
3
4
5
6
7
8
9
10
11
12
13
java.util.concurrent.ThreadPoolExecutor$Worker    run  ThreadPoolExecutor.java   918
java.util.concurrent.ThreadPoolExecutor$Worker runTask ThreadPoolExecutor.java 895
java.util.concurrent.FutureTask run FutureTask.java 138
java.util.concurrent.FutureTask$Sync innerRun FutureTask.java 303
java.util.concurrent.Executors$RunnableAdapter call Executors.java 439
org.apache.catalina.startup.HostConfig$DeployDirectory run HostConfig.java 1671
org.apache.catalina.startup.HostConfig deployDirectory HostConfig.java 1113
org.apache.catalina.core.StandardHost addChild StandardHost.java 622
org.apache.catalina.core.ContainerBase addChild ContainerBase.java 877
org.apache.catalina.core.ContainerBase addChildInternal ContainerBase.java 901
org.apache.catalina.util.LifecycleBase start LifecycleBase.java 139
org.apache.catalina.util.LifecycleBase init LifecycleBase.java 102
org.apache.catalina.core.StandardContext initInternal StandardContext.java 6449

通过查看控制台的输出,可以看到有一个org.apache.catalina.startup.HostConfig$DeployDirectory类,于是乎找到这个类去看看呗。打开一看它是一个Runable接口的实现类。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
private static class DeployDirectory implements Runnable {
private HostConfig config;
private ContextName cn;
private File dir;

public DeployDirectory(HostConfig config, ContextName cn, File dir) {
this.config = config;
this.cn = cn;
this.dir = dir;
}

@Override
public void run() {
config.deployDirectory(cn, dir);
}
}

因此推断它也是放到某个线程池中进行异步运行的,最终通过IntellIJ IDEA提供的类调用栈分析工具(ctrl+alt+h)得到DeployDirectory构造器方法的调用栈如下图所示

通过上图可以清楚的看到,最终的调用方是org.apache.catalina.startup.HostConfig#lifecycleEvent(),到这里就知道Context的启动是通过某个组件的生命周期事件的监听器来启动的,而HostConfig到底是谁的监听器呢?通过名称应该可以猜测出它是StandardHost的监听器,那么它到底监听哪个事件呢?查看下org.apache.catalina.startup.HostConfig#lifecycleEvent()的代码如下。
org.apache.catalina.startup.HostConfig#lifecycleEvent()

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
/**
* Process the START event for an associated Host.
*
* @param event The lifecycle event that has occurred
*/
@Override
public void lifecycleEvent(LifecycleEvent event) {
// Identify the host we are associated with
try {
host = (Host) event.getLifecycle();
if (host instanceof StandardHost) {
setCopyXML(((StandardHost) host).isCopyXML());
setDeployXML(((StandardHost) host).isDeployXML());
setUnpackWARs(((StandardHost) host).isUnpackWARs());
}
} catch (ClassCastException e) {
log.error(sm.getString("hostConfig.cce", event.getLifecycle()), e);
return;
}

// Process the event that has occurred
if (event.getType().equals(Lifecycle.PERIODIC_EVENT)) {
check();
} else if (event.getType().equals(Lifecycle.START_EVENT)) {
start();
} else if (event.getType().equals(Lifecycle.STOP_EVENT)) {
stop();
}
}

通过上面的代码,可以看出监听的事件是Lifecycle.START_EVENT,而通过查看org.apache.catalina.LifecycleState的代码STARTING(true,Lifecycle.START_EVENT)就可以得知,此时生命周期状态应该是LifecycleState.STARTING,到这里应该已经猜到,HostConfig是在StandardHost#start()的时候通过监听器调用,为验证的猜测,debug一下代码,可以在HostConfig#start()中打个断点,运行以后得到如下内存结构。

通过上面的分析清楚webapps目录中context的启动,总结如下。
webapps目录中应用的启动在StandardHost#start()的时候,通过Lifecycle.START_EVENT这个事件的监听器HostConfig进行进一步的启动。
综合上面的文章所述,最后再来一下总结,知道Java程序启动以后,最终会以进程的形式存在,而Java进程中又会有很多条线程存在,因此最后就来看看Tomcat启动以后,到底启动哪些线程,通过这些可以反过来验证对源代码的理解是否正确。接下来启动Tomcat,然后运行jstack -l <pid>来看看,jstack的输入如下所示。

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
Full thread dump Java HotSpot(TM) 64-Bit Server VM (20.51-b01-457 mixed mode):

"ajp-bio-8009-AsyncTimeout" daemon prio=5 tid=7f8738afe000 nid=0x115ad6000 waiting on condition [115ad5000]
java.lang.Thread.State: TIMED_WAITING (sleeping)
at java.lang.Thread.sleep(Native Method)
at org.apache.tomcat.util.net.JIoEndpoint$AsyncTimeout.run(JIoEndpoint.java:148)
at java.lang.Thread.run(Thread.java:680)

Locked ownable synchronizers:
- None

"ajp-bio-8009-Acceptor-0" daemon prio=5 tid=7f8738b05800 nid=0x1159d3000 runnable [1159d2000]
java.lang.Thread.State: RUNNABLE
at java.net.PlainSocketImpl.socketAccept(Native Method)
at java.net.PlainSocketImpl.accept(PlainSocketImpl.java:439)
- locked <7f46a8710> (a java.net.SocksSocketImpl)
at java.net.ServerSocket.implAccept(ServerSocket.java:468)
at java.net.ServerSocket.accept(ServerSocket.java:436)
at
org.apache.tomcat.util.net.DefaultServerSocketFactory.acceptSocket(DefaultServerSocketFactory.java:60)
at org.apache.tomcat.util.net.JIoEndpoint$Acceptor.run(JIoEndpoint.java:216)
at java.lang.Thread.run(Thread.java:680)

Locked ownable synchronizers:
- None

"http-bio-8080-AsyncTimeout" daemon prio=5 tid=7f8735acb800 nid=0x1158d0000 waiting on condition [1158cf000]
java.lang.Thread.State: TIMED_WAITING (sleeping)
at java.lang.Thread.sleep(Native Method)
at org.apache.tomcat.util.net.JIoEndpoint$AsyncTimeout.run(JIoEndpoint.java:148)
at java.lang.Thread.run(Thread.java:680)

Locked ownable synchronizers:
- None

"http-bio-8080-Acceptor-0" daemon prio=5 tid=7f8735acd000 nid=0x1157cd000 runnable [1157cc000]
java.lang.Thread.State: RUNNABLE
at java.net.PlainSocketImpl.socketAccept(Native Method)
at java.net.PlainSocketImpl.accept(PlainSocketImpl.java:439)
- locked <7f46a8690> (a java.net.SocksSocketImpl)
at java.net.ServerSocket.implAccept(ServerSocket.java:468)
at java.net.ServerSocket.accept(ServerSocket.java:436)
at
org.apache.tomcat.util.net.DefaultServerSocketFactory.acceptSocket(DefaultServerSocketFactory.java:60)
at org.apache.tomcat.util.net.JIoEndpoint$Acceptor.run(JIoEndpoint.java:216)
at java.lang.Thread.run(Thread.java:680)

Locked ownable synchronizers:
- None

"ContainerBackgroundProcessor[StandardEngine[Catalina]]" daemon prio=5 tid=7f8732850800 nid=0x111203000 waiting on condition [111202000]
java.lang.Thread.State: TIMED_WAITING (sleeping)
at java.lang.Thread.sleep(Native Method)
at
org.apache.catalina.core.ContainerBase$ContainerBackgroundProcessor.run(ContainerBase.java:1508)
at java.lang.Thread.run(Thread.java:680)

Locked ownable synchronizers:
- None

"main" prio=5 tid=7f8735000800 nid=0x10843e000 runnable [10843c000]
java.lang.Thread.State: RUNNABLE
at java.net.PlainSocketImpl.socketAccept(Native Method)
at java.net.PlainSocketImpl.accept(PlainSocketImpl.java:439)
- locked <7f32ea7c8> (a java.net.SocksSocketImpl)
at java.net.ServerSocket.implAccept(ServerSocket.java:468)
at java.net.ServerSocket.accept(ServerSocket.java:436)
at org.apache.catalina.core.StandardServer.await(StandardServer.java:452)
at org.apache.catalina.startup.Catalina.await(Catalina.java:779)
at org.apache.catalina.startup.Catalina.start(Catalina.java:725)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
at java.lang.reflect.Method.invoke(Method.java:597)
at org.apache.catalina.startup.Bootstrap.start(Bootstrap.java:322)
at org.apache.catalina.startup.Bootstrap.main(Bootstrap.java:456)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
at java.lang.reflect.Method.invoke(Method.java:597)
at com.intellij.rt.execution.application.AppMain.main(AppMain.java:120)

从上图中可以清楚的看到,有6条线程,其中ajp-bio-8009-AsyncTimeoutajp-bio-8009-Acceptor-0是在Ajp的Connector启动的时候启动的,http-bio-8080-AsyncTimeouthttp-bio-8080-Acceptor-0是http的Connector启动的时候启动的,ContainerBackgroundProcessor[StandardEngine[Catalina]]是在StandardEngine启动的时候启动的,而main()线程就是主线程。这里还需要注意一点就是除main线程以外,其它的线程都是Dameon线程,相关的内容在下篇Tomcat的关闭再来详细说明。

评论

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×