Tomcat啟動加載過程(一)的源碼解析
今天,我將分享用源碼的方式講解Tomcat啟動的加載過程,關於Tomcat的架構請參閱《Tomcat源碼分析二:先看看Tomcat的整體架構》一文。
先看看應用情況
在《Servlet與Tomcat運行示例》一文中,我詳細的記錄了Tomcat是如何啟動一個Servlet的程序的步驟。其中,第6步驟是啟動Tomcat,也就是在windows系統上執行startup.bat, 在linux操作系統上執行startup.sh的腳本。那么,我們就從這個腳本出發,走進Tomcat,看看它是如何啟動的?這里,我們以startup.sh為例,windows端的startup.bat類似。
startup.sh的內容是什么?
我們先看看tomcat的啟動腳本startup.sh的內容是什么,先看看其腳本內容(省略部分注釋),如下:
#!/bin/sh
# -----------------------------------------------------------------------------
# Start Script for the CATALINA Server
# -----------------------------------------------------------------------------
# Better OS/400 detection: see Bugzilla 31132
os400=false
case "`uname`" in
OS400*) os400=true;;
esac
# resolve links - $0 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:
# 1. owned by the user
# 2. 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 1
fi
fi
exec "$PRGDIR"/"$EXECUTABLE" start "$@"
提取其中主要的幾句:
PRGDIR=`dirname "$PRG"`
EXECUTABLE=catalina.sh
exec "$PRGDIR"/"$EXECUTABLE" start "$@"
簡而概之,該腳本的執行內容為:調用catalina.sh腳本。下面,我們繼續來看下catalina.sh腳本的內容
catalina.sh腳本
由於catalina.sh腳本內容比較多,這里提取一些重要的內容,然后解釋其用途:


再簡要的描述下在catalina.sh中作用:完成環境檢查、環境初始化、參數初始化、啟動操作步驟。注意一下上圖中被綠色框出來的內容,可以看到其調用執行的是org.apache.catalina.startup.Bootstrap類,並且傳輸過去的command指令為start。
回歸Java代碼
Bootstrap類進行了什么操作呢?
接下來,我們帶着這幾個問題來去探索一下Bootstrap類:
- Bootstrap類在接收到start指令后要去干什么?
- Bootstrap類在啟動過程中的職責是什么?
下面,我們帶着上面的幾個問題來具體的探討一下Tomcat的源碼。先來看看Bootstrap類的main方法:
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 {
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);
}
}
從這段代碼中,可以看出,其主要實現了兩個功能:
- 初始化一個守護進程變量daemon
- 加載catalina.sh傳遞過來的參數,解析catalina.sh傳遞過來的指令,並按照指令執行程序,控制守護進程daemon的啟停等操作
bootstrap.init();有什么操作呢?
針對上面的兩個功能,我們進入到 init()方法看下有什么操作,先看下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;
}
在init()方法中,首先執行的方法initClassLoaders()的作用是初始化三個類加載器,代碼如下:
/**
* Daemon reference.
*/
private Object catalinaDaemon = null;
ClassLoader commonLoader = null;
ClassLoader catalinaLoader = null;
ClassLoader sharedLoader = null;
private void initClassLoaders() {
try {
commonLoader = createClassLoader("common", null);
if (commonLoader == null) {
// no config file, default to this loader - we might be in a 'single' env.
commonLoader = this.getClass().getClassLoader();
}
catalinaLoader = createClassLoader("server", commonLoader);
sharedLoader = createClassLoader("shared", commonLoader);
} catch (Throwable t) {
handleThrowable(t);
log.error("Class loader creation threw exception", t);
System.exit(1);
}
}
private ClassLoader createClassLoader(String name, ClassLoader parent)
throws Exception {
String value = CatalinaProperties.getProperty(name + ".loader");
if ((value == null) || (value.equals("")))
return parent;
value = replace(value);
List<Repository> repositories = new ArrayList<>();
String[] repositoryPaths = getPaths(value);
for (String repository : repositoryPaths) {
// Check for a JAR URL repository
try {
@SuppressWarnings("unused")
URL url = new URL(repository);
repositories.add(new Repository(repository, RepositoryType.URL));
continue;
} catch (MalformedURLException e) {
// Ignore
}
// Local repository
if (repository.endsWith("*.jar")) {
repository = repository.substring
(0, repository.length() - "*.jar".length());
repositories.add(new Repository(repository, RepositoryType.GLOB));
} else if (repository.endsWith(".jar")) {
repositories.add(new Repository(repository, RepositoryType.JAR));
} else {
repositories.add(new Repository(repository, RepositoryType.DIR));
}
}
return ClassLoaderFactory.createClassLoader(repositories, parent);
}
// catalina.properties
common.loader="${catalina.base}/lib","${catalina.base}/lib/*.jar","${catalina.home}/lib","${catalina.home}/lib/*.jar"
- commonLoader: 根據common.loader屬性的配置(通過代碼
CatalinaProperties.getProperty(name + ".loader");讀取:catalina.properties), 創建commonLoader類加載器, 默認情況下順序加載 ${catalina.base}/lib, ${catalina.base}/lib/.jar, ${catalina.home}/lib, ${catalina.home}/lib/.jar 四個目錄下的class和jar. - catalinaLoader: 根據server.loader屬性的配置, 創建catalinaLoader類加載器,其父類加載其為commonLoader, 默認server.loader屬性為空, 直接使用commonLoader.
- sharedLoader:根據shared.loader屬性配置,創建sharedLoader類加載器,其父類加載其為commonLoader, 默認shared.loader屬性為空, 直接使用commonLoader.
當執行完initClassLoaders()方法之后,調用Thread.currentThread().setContextClassLoader(catalinaLoader);設置上下文類加載器為catalinaLoader,從上面解析的情況看,其實設置的上下文類加載器為catalinaLoader的父類commonLoader。
SecurityClassLoad.securityClassLoad(catalinaLoader) 的作用是如果有SecurityManager,提前加載部分類。
之后,通過使用catalinaLoader加載org.apache.catalina.startup.Catalina類,創建實例Catalina並利用反射調用方法setParentClassLoader(),設置Catalina實例的parentClassLoader屬性為sharedLoader類加載器(也就是commonLoader)。
最后,設置daemon為新創建的實例Bootstrap。接下來,看一下main()方法下的指令處理。
傳遞過來的command指令是如何處理的呢?
我們觀察一下main()方法的后半段,這里貼一下代碼:
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) {
// ...... 省略
}
可以看到,其默認指令為start, 然后,其根據接收到的參數區分為startd、stopd、start、stop、configtest和其他6種指令情況。這里我們主要看一下start指令的執行邏輯。
- daemon.setAwait(true) :這句代碼有什么含義呢,下面我們來具體的分析一下:
/**
* Set flag.
* @param await <code>true</code> if the daemon should block
* @throws Exception Reflection error
*/
public void setAwait(boolean await)
throws Exception {
Class<?> paramTypes[] = new Class[1];
paramTypes[0] = Boolean.TYPE;
Object paramValues[] = new Object[1];
paramValues[0] = Boolean.valueOf(await);
Method method =
catalinaDaemon.getClass().getMethod("setAwait", paramTypes);
method.invoke(catalinaDaemon, paramValues);
}
這段代碼的主要作用是通過反射調用Catalina.setAwait(true),主要目的是當啟動完成后, 阻塞main線程,等待stop命令到來。 如果不設置daemon.setAwait(true), 則main線程執行完之后就 直接退出了。
- **daemon.load(args) **
daemon.load(args);其實是最終執行的Catalina.load(),在Catalina.load()方法中,主要功能是首先初始化temp目錄,然后再初始化naming的一些系統屬性,然后獲取server.xml配置文件, 創建Digester實例, 開始解析server.xml的操作。
/**
* 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) {
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)));
}
}
- daemon.start(): 啟動Tomcat
通過調用daemon.start()啟動Tomcat,其內容如下:
/**
* Start the Catalina daemon.
* @throws Exception Fatal start error
*/
public void start() throws Exception {
if (catalinaDaemon == null) {
init();
}
Method method = catalinaDaemon.getClass().getMethod("start", (Class [])null);
method.invoke(catalinaDaemon, (Object [])null);
}
程序通過反射的方式調用Catalina.start()方式啟動Tomcat,下面,我們看下Catalina.start()方法的實現邏輯:
/**
* 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();
}
}
可以看出,程序調用getServer().start()啟動,getServer()方法返回的是一個StandardServer類,繼而其調用的是StandardServer.startInternal()方法,在StandardServer中,又調用到StandardService.startInternal()方法。
// StandardServer.java
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();
}
}
// ......省略部分代碼
}
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();
}
}
}
}
注意,這里為什么不是start()方法,而是startInternal()方法呢?原因是StandardServer和StandService類都繼承了LifecycleMBeanBase類,而LifecycleMBeanBase類又繼承了LifecycleBase類。下面看下LifecycleBase類的start()方法:
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;
}
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);
}
try {
setStateInternal(LifecycleState.STARTING_PREP, null, false);
startInternal();
if (state.equals(LifecycleState.FAILED)) {
// This is a 'controlled' failure. The component put itself into the
// FAILED state so call stop() to complete the clean-up.
stop();
} else if (!state.equals(LifecycleState.STARTING)) {
// Shouldn't be necessary but acts as a check that sub-classes are
// doing what they are supposed to.
invalidTransition(Lifecycle.AFTER_START_EVENT);
} else {
setStateInternal(LifecycleState.STARTED, null, false);
}
} catch (Throwable t) {
// This is an 'uncontrolled' failure so put the component into the
// FAILED state and throw an exception.
handleSubClassException(t, "lifecycleBase.startFail", toString());
}
}
可以看出,調用start()方法,最終都會調用到startInternal()方法。在下篇文章中,我們將詳細看下StandardService.java中的engine.start()、executor.start()、connector.start()都分別啟動了什么?敬請期待!
微信公眾號: 源碼灣
歡迎關注本人微信公眾號: 源碼灣。 本公眾號將不定期進行相關源碼及相關開發技術的分享,共同成長,共同進步~

Blog:
- 簡書: https://www.jianshu.com/u/91378a397ffe
- csdn: https://blog.csdn.net/ZhiyouWu
- 開源中國: https://my.oschina.net/u/3204088
- 掘金: https://juejin.im/user/5b5979efe51d451949094265
- 博客園: https://www.cnblogs.com/zhiyouwu/
- 微信公眾號: 源碼灣
- 微信: WZY1782357529 (歡迎溝通交流)
