深入浅出Tomcat/2 - Tomcat启动和停止

时间:2023-03-09 05:20:09
深入浅出Tomcat/2 - Tomcat启动和停止

Tomcat启动和停止

很明显,我们启动或停止Tomcat,一般调用的是bin下的startup.sh或shutdown.sh(以Linux为例,以下涉及到平台,若无特殊说明,一般都指Linux)。我们看看startup.sh的脚本是什么?

# -----------------------------------------------------------------------------
# Start Script for the CATALINA Server
# ----------------------------------------------------------------------------- # Better OS/ detection: see Bugzilla
os400=false
case "`uname`" in
OS400*) os400=true;;
esac # resolve links - $ may be a softlink
PRG="$0" while [ -h "$PRG" ] ; do
ls=`ls -ld "$PRG"`
link=`expr "$ls" : '.*-> \(.*\)$'`
if expr "$link" : '/.*' > /dev/null; then
PRG="$link"
else
PRG=`dirname "$PRG"`/"$link"
fi
done PRGDIR=`dirname "$PRG"`
EXECUTABLE=catalina.sh # Check that target executable exists
if $os400; then
# -x will Only work on the os400 if the files are:
# . owned by the user
# . owned by the PRIMARY group of the user
# this will not work if the user belongs in secondary groups
eval
else
if [ ! -x "$PRGDIR"/"$EXECUTABLE" ]; then
echo "Cannot find $PRGDIR/$EXECUTABLE"
echo "The file is absent or does not have execute permission"
echo "This file is needed to run this program"
exit
fi
fi exec "$PRGDIR"/"$EXECUTABLE" start "$@"
最后一行,它执行的是"/"$EXECUTABLE,那它是什么呢?参看前面的一行
EXECUTABLE=catalina.sh
是的,启动脚本还是调用的catalina.sh.
接下来我们看看catalina.sh脚本有什么内容。
代码比较长,为了看的更明白,删除一些不必要的,完整的脚本大家可以参看catalina.sh
#!/bin/sh

# Control Script for the CATALINA Server
#
# Environment Variable Prerequisites
#
# Do not set the variables in this script. Instead put them into a script
# setenv.sh in CATALINA_BASE/bin to keep your customizations separate.
#
# CATALINA_HOME May point at your Catalina "build" directory.
#
# CATALINA_BASE (Optional) Base directory for resolving dynamic portions
# of a Catalina installation. If not present, resolves to
# the same directory that CATALINA_HOME points to.
#
# CATALINA_OUT (Optional) Full path to a file where stdout and stderr
# will be redirected.
# Default is $CATALINA_BASE/logs/catalina.out
#
# # JAVA_HOME Must point at your Java Development Kit installation.
# Required to run the with the "debug" argument.
#
# JRE_HOME Must point at your Java Runtime installation.
# Defaults to JAVA_HOME if empty. If JRE_HOME and JAVA_HOME
# are both set, JRE_HOME is used.
#
# JAVA_OPTS (Optional) Java runtime options used when any command
# is executed.
# Include here and not in CATALINA_OPTS all options, that
# should be used by Tomcat and also by the stop process,
# the version command etc.
# Most options should go into CATALINA_OPTS.
# Djava.util.logging.config.file=$CATALINA_BASE/conf/logging.properties"
## ----------------------------------------------------------------------------- # ----- Execute The Requested Command ----------------------------------------- elif [ "$1" = "start" ] ; then if [ -z "$CATALINA_OUT_CMD" ] ; then
touch "$CATALINA_OUT"
catalina_out_command=">> \"$CATALINA_OUT\" 2>&1"
else
catalina_out_command="| $CATALINA_OUT_CMD"
fi
if [ ! -z "$CATALINA_PID" ]; then
catalina_pid_file="$CATALINA_PID"
else
catalina_pid_file=/dev/null
fi
if [ "$1" = "-security" ] ; then
if [ $have_tty -eq ]; then
echo "Using Security Manager"
fi
shift
eval \{ $_NOHUP "\"$_RUNJAVA\"" "\"$LOGGING_CONFIG\"" $LOGGING_MANAGER $JAVA_OPTS $CATALINA_OPTS \
-D$ENDORSED_PROP="\"$JAVA_ENDORSED_DIRS\"" \
-classpath "\"$CLASSPATH\"" \
-Djava.security.manager \
-Djava.security.policy=="\"$CATALINA_BASE/conf/catalina.policy\"" \
-Dcatalina.base="\"$CATALINA_BASE\"" \
-Dcatalina.home="\"$CATALINA_HOME\"" \
-Djava.io.tmpdir="\"$CATALINA_TMPDIR\"" \
org.apache.catalina.startup.Bootstrap "$@" start \
\>\& \& echo \$! \>\"$catalina_pid_file\" \; \} $catalina_out_command "&" else
eval \{ $_NOHUP "\"$_RUNJAVA\"" "\"$LOGGING_CONFIG\"" $LOGGING_MANAGER $JAVA_OPTS $CATALINA_OPTS \
-D$ENDORSED_PROP="\"$JAVA_ENDORSED_DIRS\"" \
-classpath "\"$CLASSPATH\"" \
-Dcatalina.base="\"$CATALINA_BASE\"" \
-Dcatalina.home="\"$CATALINA_HOME\"" \
-Djava.io.tmpdir="\"$CATALINA_TMPDIR\"" \
org.apache.catalina.startup.Bootstrap "$@" start \
\>\& \& echo \$! \>\"$catalina_pid_file\" \; \} $catalina_out_command "&" fi echo "Tomcat started." elif [ "$1" = "stop" ] ; then eval "\"$_RUNJAVA\"" $JAVA_OPTS \
-D$ENDORSED_PROP="\"$JAVA_ENDORSED_DIRS\"" \
-classpath "\"$CLASSPATH\"" \
-Dcatalina.base="\"$CATALINA_BASE\"" \
-Dcatalina.home="\"$CATALINA_HOME\"" \
-Djava.io.tmpdir="\"$CATALINA_TMPDIR\"" \
org.apache.catalina.startup.Bootstrap "$@" stop elif [ "$1" = "version" ] ; then "$_RUNJAVA" \
-classpath "$CATALINA_HOME/lib/catalina.jar" \
org.apache.catalina.util.ServerInfo else echo "Usage: catalina.sh ( commands ... )"
echo "commands:"
if $os400; then
echo " debug Start Catalina in a debugger (not available on OS400)"
echo " debug -security Debug Catalina with a security manager (not available on OS400)"
else
echo " debug Start Catalina in a debugger"
echo " debug -security Debug Catalina with a security manager"
fi
echo " jpda start Start Catalina under JPDA debugger"
echo " run Start Catalina in the current window"
echo " run -security Start in the current window with security manager"
echo " start Start Catalina in a separate window"
echo " start -security Start in a separate window with security manager"
echo " stop Stop Catalina, waiting up to 5 seconds for the process to end"
echo " stop n Stop Catalina, waiting up to n seconds for the process to end"
echo " stop -force Stop Catalina, wait up to 5 seconds and then use kill -KILL if still running"
echo " stop n -force Stop Catalina, wait up to n seconds and then use kill -KILL if still running"
echo " configtest Run a basic syntax check on server.xml - check exit code for result"
echo " version What version of tomcat are you running?"
echo "Note: Waiting for the process to end and use of the -force option require that \$CATALINA_PID is defined"
exit
从以上脚本可以看出,无论是start还是stop,其实调用的都是org.apache.catalina.startup.Bootstrap 这个类。
整理一下,Tomcat整个程序的入口在Boostrap这个类这里。
OK,我们看看Bootstrap的启动过程。

Bootstrap的启动过程

Bootstrap这个类在以下package里

org.apache.catalina.startup

Bootstrap类有个main方法,这是Tomcat的入口。

public static void main(String args[]) {

    synchronized (daemonLock) {
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;
} 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")) {
daemon.setAwait(true);
daemon.load(args);
daemon.start();
if (null == daemon.getServer()) {
System.exit(1);
}
} 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);
} }

代码比较简单,也比较直观。
首先创建一个Bootstrap的实例,然后调用init方法。接着处理main方法的传入参数,是start还是stop等,当然默认是start了。
再看看init的方法

public void init() throws Exception {

    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.getConstructor().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; }

我们可以看到初始化ClassLoader,接着会创建一个startupClass的类,就是这段代码:

Class<?> startupClass = catalinaLoader.loadClass("org.apache.catalina.startup.Catalina");
Object startupInstance = startupClass.getConstructor().newInstance();
我们看到startup的类其实就是org.apache.catalina.startup.Catalina,最后创建了另外一个实例catalinaDaemon。Tomcat的启动,停止都是调用它。
我们看看start方法启动Tomcat
public void start()
throws Exception {
if( catalinaDaemon==null ) init(); Method method = catalinaDaemon.getClass().getMethod("start", (Class [] )null);
method.invoke(catalinaDaemon, (Object [])null); }
这里首先判断有没有初始化,如果没有,调用init并对其初始化,然后使用Method进行反射调用Catalina的start方法。
:Method是java.lang.reflect包里的类,代表了一个具体的方法,在这里,只是start方法。Method.invoke方法是指执行该方法,它有2个参数,第一个参数是方法名,第二个是该方法的参数。

Catalina的启动

追本溯源,最后Tomcat的管理落到了Catalina这个类了,所以Catalina才是最重要的类。
先看看Catalina的方法。

深入浅出Tomcat/2 - Tomcat启动和停止

我们可以清晰看到几个重要的方法:
  • await
  • load
  • start
  • stop
  • usage
对于这几个方法,也算是直观。那么我们从简单的开始说起。
方法usage
见以下代码,其实就是显示catalina的用法。
/**
* Print usage information for this application.
*/
protected void usage() { System.out.println(sm.getString("catalina.usage")); }
方法await
代码如下
/**
* Await and shutdown.
*/
public void await() { getServer().await(); }
这个方法搭配setAwait来用,setAwait方法用于设置Server启动完成后是否进入等待状态,如果为true,则进入,否则不进入。
方法load
/**
* Start a new server instance.
*/
public void load() { if (loaded) {
return;
}
loaded = true; long t1 = System.nanoTime(); initDirs(); // Before digester - it may be needed
initNaming(); // Set configuration source
ConfigFileLoader.setSource(new CatalinaBaseConfigurationSource(Bootstrap.getCatalinaBaseFile(), getConfigFile()));
File file = configFile(); // Create and execute our Digester
Digester digester = createStartDigester(); try (ConfigurationSource.Resource resource = ConfigFileLoader.getSource().getServerXml()) {
InputStream inputStream = resource.getInputStream();
InputSource inputSource = new InputSource(resource.getURI().toURL().toString());
inputSource.setByteStream(inputStream);
digester.push(this);
digester.parse(inputSource);
} catch (Exception e) {
if (file == null) {
log.warn(sm.getString("catalina.configFail", getConfigFile() + "] or [server-embed.xml"), e);
} else {
log.warn(sm.getString("catalina.configFail", file.getAbsolutePath()), e);
if (file.exists() && !file.canRead()) {
log.warn(sm.getString("catalina.incorrectPermissions"));
}
}
return;
} getServer().setCatalina(this);
getServer().setCatalinaHome(Bootstrap.getCatalinaHomeFile());
getServer().setCatalinaBase(Bootstrap.getCatalinaBaseFile()); // 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(sm.getString("catalina.initError"), e);
}
} long t2 = System.nanoTime();
if(log.isInfoEnabled()) {
log.info(sm.getString("catalina.init", Long.valueOf((t2 - t1) / 1000000)));
}
}
这个方法会加载conf/server.xml这个配置文件,并且将我们前面描述到的Server,Listener,Service,Engine等逐一加载。而且Server实例已经由Digester创建,以下是createStartDigester的实现,部分代码删除。
protected Digester createStartDigester() {
long t1=System.currentTimeMillis();
// Initialize the digester
Digester digester = new Digester();
digester.setValidating(false);
digester.setRulesValidation(true);
Map<Class<?>, List<String>> fakeAttributes = new HashMap<>();
// Ignore className on all elements
List<String> objectAttrs = new ArrayList<>();
objectAttrs.add("className");
fakeAttributes.put(Object.class, objectAttrs);
// Ignore attribute added by Eclipse for its internal tracking
List<String> contextAttrs = new ArrayList<>();
contextAttrs.add("source");
fakeAttributes.put(StandardContext.class, contextAttrs);
// Ignore Connector attribute used internally but set on Server
List<String> connectorAttrs = new ArrayList<>();
connectorAttrs.add("portOffset");
fakeAttributes.put(Connector.class, connectorAttrs);
digester.setFakeAttributes(fakeAttributes);
digester.setUseContextClassLoader(true); // 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.NamingResourcesImpl");
digester.addSetProperties("Server/GlobalNamingResources");
digester.addSetNext("Server/GlobalNamingResources",
"setGlobalNamingResources",
"org.apache.catalina.deploy.NamingResourcesImpl"); 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"); //Executor
digester.addObjectCreate("Server/Service/Executor",
"org.apache.catalina.core.StandardThreadExecutor",
"className");
digester.addSetProperties("Server/Service/Executor"); digester.addSetNext("Server/Service/Executor",
"addExecutor",
"org.apache.catalina.Executor"); digester.addRule("Server/Service/Connector",
new ConnectorCreateRule());
digester.addRule("Server/Service/Connector", new SetAllPropertiesRule(
new String[]{"executor", "sslImplementationName", "protocol"}));
digester.addSetNext("Server/Service/Connector",
"addConnector",
"org.apache.catalina.connector.Connector"); digester.addRule("Server/Service/Connector", new AddPortOffsetRule()); digester.addObjectCreate("Server/Service/Connector/SSLHostConfig",
"org.apache.tomcat.util.net.SSLHostConfig");
digester.addSetProperties("Server/Service/Connector/SSLHostConfig");
digester.addSetNext("Server/Service/Connector/SSLHostConfig",
"addSslHostConfig",
"org.apache.tomcat.util.net.SSLHostConfig"); 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"); digester.addObjectCreate("Server/Service/Connector/UpgradeProtocol",
null, // MUST be specified in the element
"className");
digester.addSetProperties("Server/Service/Connector/UpgradeProtocol");
digester.addSetNext("Server/Service/Connector/UpgradeProtocol",
"addUpgradeProtocol",
"org.apache.coyote.UpgradeProtocol"); // 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/Host/"));
addClusterRuleSet(digester, "Server/Service/Engine/Host/Cluster/");
digester.addRuleSet(new NamingRuleSet("Server/Service/Engine/Host/Context/")); // When the 'engine' is found, set the parentClassLoader.
digester.addRule("Server/Service/Engine",
new SetParentClassLoaderRule(parentClassLoader));
addClusterRuleSet(digester, "Server/Service/Engine/Cluster/"); long t2=System.currentTimeMillis();
if (log.isDebugEnabled()) {
log.debug("Digester for server.xml created " + ( t2-t1 ));
}
return digester; }
方法start
start用来启动一个Server,具体代码如下
/**
* Start a new server instance.
*/
public void start() { if (getServer() == null) {
load();
} if (getServer() == null) {
log.fatal(sm.getString("catalina.noServer"));
return;
} long t1 = System.nanoTime(); // Start the new server
try {
getServer().start();
} catch (LifecycleException e) {
log.fatal(sm.getString("catalina.serverStartFail"), e);
try {
getServer().destroy();
} catch (LifecycleException e1) {
log.debug("destroy() failed for failed Server ", e1);
}
return;
} long t2 = System.nanoTime();
if(log.isInfoEnabled()) {
log.info(sm.getString("catalina.startup", Long.valueOf((t2 - t1) / 1000000)));
} // 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是否存在,如果不存在,调用load初始化一下。接着调用Server的start方法启动它。最后如果启用关闭的钩子,还要注册一下该钩子。在末尾,如果如果await设置为true的话,需要调用await和stop。Await会直接调用Server的await方法,Server内部会进入一个while循环,以下代码是Server的await方法的实现。当await里while结束,执行stop方法,停止服务器。

public void await() {
// Set up a server socket to wait on
try {
awaitSocket = new ServerSocket(getPortWithOffset(), 1,
InetAddress.getByName(address));
} catch (IOException e) {
log.error(sm.getString("standardServer.awaitSocket.fail", address,
String.valueOf(getPortWithOffset()), String.valueOf(getPort()),
String.valueOf(getPortOffset())), e);
return;
} try {
awaitThread = Thread.currentThread(); // Loop waiting for a connection and a valid command
while (!stopAwait) {
ServerSocket serverSocket = awaitSocket;
if (serverSocket == null) {
break;
} // Wait for the next connection
Socket socket = null;
StringBuilder command = new StringBuilder();
try {
InputStream stream;
long acceptStartTime = System.currentTimeMillis();
try {
socket = serverSocket.accept();
socket.setSoTimeout(10 * 1000); // Ten seconds
stream = socket.getInputStream();
} catch (SocketTimeoutException ste) {
// This should never happen but bug 56684 suggests that
// it does.
log.warn(sm.getString("standardServer.accept.timeout",
Long.valueOf(System.currentTimeMillis() - acceptStartTime)), ste);
continue;
} catch (AccessControlException ace) {
log.warn(sm.getString("standardServer.accept.security"), ace);
continue;
} catch (IOException e) {
if (stopAwait) {
// Wait was aborted with socket.close()
break;
}
log.error(sm.getString("standardServer.accept.error"), e);
break;
} // Read a set of characters from the socket
int expected = 1024; // Cut off to avoid DoS attack
while (expected < shutdown.length()) {
if (random == null)
random = new Random();
expected += (random.nextInt() % 1024);
}
while (expected > 0) {
int ch = -1;
try {
ch = stream.read();
} catch (IOException e) {
log.warn(sm.getString("standardServer.accept.readError"), e);
ch = -1;
}
// Control character or EOF (-1) terminates loop
if (ch < 32 || ch == 127) {
break;
}
command.append((char) ch);
expected--;
}
} finally {
// Close the socket now that we are done with it
try {
if (socket != null) {
socket.close();
}
} catch (IOException e) {
// Ignore
}
} // Match against our command string
boolean match = command.toString().equals(shutdown);
if (match) {
log.info(sm.getString("standardServer.shutdownViaPort"));
break;
} else
log.warn(sm.getString("standardServer.invalidShutdownCommand", command.toString()));
}
} finally {
ServerSocket serverSocket = awaitSocket;
awaitThread = null;
awaitSocket = null; // Close the server socket and return
if (serverSocket != null) {
try {
serverSocket.close();
} catch (IOException e) {
// Ignore
}
}
}
}

Server的启动

前面也提到了Catalina的start其实调用的是Server的start方法,我们看看Server的start的方法怎么实现的。在深入前,我们看看Server的方法列表等信息。
深入浅出Tomcat/2 - Tomcat启动和停止
Server是一个接口,继承了LifeCycle这个接口。
Server提供了2个方法addService()以及removeService(),分别用来增加或删除Service。在前面我们讲过,一个Server是可以包含多个Service的。
在Server 启动前,Server包含的几个Service也需要初始化,其实每个Service的初始化也是用Service本身的init方法来初始化。
Server的默认实现是org.apache.catalina.core.StandardServer,StandardServer又继承了LifeCycleMBeanBase,
public final class StandardServer extends LifecycleMBeanBase implements Server 
LifeCycleMBeanBase又继承了LifecycleBase
public abstract class LifecycleMBeanBase extends LifecycleBase 
LifeCycleBase又实现了LifeCycle
public abstract class LifecycleBase implements Lifecycle
因为init和start都是LifeCycleBase里,这也是tomcat的生命周期重要的方法。Init和start也分别调用了initInternal和startInternal。至于怎么实现,还得看子类的具体逻辑,前面说到StandardServer是默认的实现,那我们看看StandardServer的具体实现逻辑。继续上代码:
protected void initInternal() throws LifecycleException {

    super.initInternal();

     //… …

    // Initialize our defined Services

    for (int i = 0; i < services.length; i++) {

        services[i].init();

    }

}
上面是initInternal的代码,我们发现其实就是调用Server包含的Service的init方法。
下面是startInternal的代码。 
protected void startInternal() throws LifecycleException {

fireLifecycleEvent(CONFIGURE_START_EVENT, null);

setState(LifecycleState.STARTING);

globalNamingResources.start();

// Start our defined Services

synchronized (servicesLock) {

for (int i = 0; i < services.length; i++) {

services[i].start();

}

}

if (periodicEventDelay > 0) {

monitorFuture = getUtilityExecutor().scheduleWithFixedDelay(

new Runnable() {

@Override

public void run() {

startPeriodicLifecycleEvent();

}

}, 0, 60, TimeUnit.SECONDS);

}

}

首先需要启动各个Service,这也是通过Service 的start方法来实现。

Service的启动过程

前面已经说到,Server会调用Service的start方法来启动各个Service,我们继续看Service的启动。Service的默认实现是org.apache.catalina.core.StandardService,StandardService也继承了LifeCycleMBeanBase类,所以init和start最终也会调用initInternal和startInternal这两个方法。还是先看StandardService的方法列表。

深入浅出Tomcat/2 - Tomcat启动和停止

和StandardServer类似,一个Service可能包含多个Connector,所以我们看到了addConnector和removeConnector这两个方法。
我们可以看到initInternal和startInternal两个方法,我们抽取核心的代码在这里展示一下:

@Override
protected void initInternal() throws LifecycleException { super.initInternal(); if (engine != null) {
engine.init();
} // Initialize any Executors
for (Executor executor : findExecutors()) {
if (executor instanceof JmxEnabled) {
((JmxEnabled) executor).setDomain(getDomain());
}
executor.init();
} // Initialize mapper listener
mapperListener.init(); // Initialize our defined Connectors
synchronized (connectorsLock) {
for (Connector connector : connectors) {
connector.init();
}
}
} protected void startInternal() throws LifecycleException { if(log.isInfoEnabled())
log.info(sm.getString("standardService.start.name", this.name));
setState(LifecycleState.STARTING); // Start our defined Container first
if (engine != null) {
synchronized (engine) {
engine.start();
}
} synchronized (executors) {
for (Executor executor: executors) {
executor.start();
}
} mapperListener.start(); // Start our defined Connectors second
synchronized (connectorsLock) {
for (Connector connector: connectors) {
// If it has already failed, don't try and start it
if (connector.getState() != LifecycleState.FAILED) {
connector.start();
}
}

根据以上代码可以看出,无论是initInternal还是startInternal,都涉及到了Engine,Executor,Listener以及Connector。
这里需要说明的是Executor,前面并未提及,因为Tomcat默认把它注释掉了,其实它就是Connector中管理线程的线程池。

<!--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"/>

如何使用Executor?只需要在Connector加上executor这个属性,并指向定义的Executor。

<Connector executor = “tomcatThreadPool” port="8080" protocol="HTTP/1.1"
connectionTimeout="20000"
redirectPort="8443" />

综合上述,现在我们可以了解Tomcat的启动过程了,先初始化,然后在启动。
Startup.sh -> Bootstrap的main -> Catalina -> StandardServer -> StandardService->Container->Executor->Connector