Tomcat 9 源码分析(1)— 启动与停止
前言
本文分析的Tomcat版本为Tomcat9.0,该版本与Tomcat8,Tomcat7大体一致,仅在部分地方有所改动,而目前最新的Tomcat10则与之前的版本相差较大。
这是本人第一次阅读主流技术的源码,碍于个人能力原因,无法独自完成源码的阅读,因此本文参考了许多大佬们的文章,在本次学习中主要参考了泰山不老生博主的博客,很感谢能够翻阅到大佬的文章,所学甚多,受益匪浅。
愿编码半生,如一生老友,你也加油。
Tomcat模块
Tomcat的模块如图,接下来便从Tomcat的启动开始分析
启动
Tomcat的启动往往是调用tomcat/bin目录下的startup脚本,而startup.sh将会调用catalina.sh脚本
tomcat/bin/startup.sh
PRGDIR=`dirname "$PRG"`
EXECUTABLE=catalina.sh
if $os400; then
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:当前shell脚本所在的路径
- EXECUTABLE:脚本catalina.sh
startup.sh内最后会调用catalina.sh,初始化catalina_base,catalina_home等参数,并传递参数start,catalina.sh脚本则初传递各种参数调用Bootstrap.jar执行其main方法且传递传输start
tomcat/bin/catalina.sh
elif [ "$1" = "start" ] ; then
#省略参数校验脚本
eval $_NOHUP "\"$_RUNJAVA\"" "\"$CATALINA_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 \
>> "$CATALINA_OUT" 2>&1 "&"
由此可知,最后使用java命令执行了org.apache.catalina.startup.Bootstrap
类中的main方法,参数是start,在Bootstrap.main()
中,当传递参数start时变量command等于start
Bootstarp.main()
内先调用init()初始化,利用反射机制,正式载入Catalina类,创建其实例返回并赋值给Bootstrap类的catalinaDaemon对象
org.apche.catalina.startup.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);
}
}
// 省略后续操作,在后文分析
}
init()
org.apache.catalina.startup.Bootstrap.init()
public void init() throws Exception {
// 初始化catalina.properties中的commonLoader,serverLoader,sharedLoader
// 如果catalina.properties中的server.loader和sharedLoader为空则以common.Loader替代
// 查找各个loader中的系统变量,其中包含了以","分隔的文件路径(URL、JAR、目录等),称之为repository
// 调用ClassLoaderFactory.createClassLoader()产生classLoader
initClassLoaders();
// 把生成的catalinaLoader设置为当前线程上下文的classloader
Thread.currentThread().setContextClassLoader(catalinaLoader);
SecurityClassLoad.securityClassLoad(catalinaLoader);
// Load our startup class and call its process() method
if (log.isDebugEnabled())
log.debug("Loading startup class");
// 利用反射机制,正式载入Catalina类,创建其实例返回
Class<?> startupClass = catalinaLoader.loadClass("org.apache.catalina.startup.Catalina");
Object startupInstance = startupClass.getConstructor().newInstance();
// Set the shared extensions class loader
// 调用setParentClassLoader()将sharedLoader设置为父类加载器
// 将catalinaLoader实例赋值给Bootstrap类的catalinaDaemon对象
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;
}
initClassLoaders()
将初始化catalina.properties中的commonLoader,serverLoader,sharedLoader,且以commonLoader为父类加载器
org.apache.catalina.startup.Bootstrap.initClassLoaders()
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);
}
}
//common.loader:tomcat自身和各web服务都会用到的类
//server.loader:tomcat自身会用到的类 如果为空则由common.loader替代
//shared.loader:各web服务会用到的类 如果为空则由common.loader替代
common.loader="${catalina.base}/lib","${catalina.base}/lib/*.jar","${catalina.home}/lib","${catalina.home}/lib/*.jar"
server.loader=
shared.loader=
这三个类加载器都是UrlClassLoader
org.apache.catalina.startup.ClassLoaderFactory.createClassLoader()
return AccessController.doPrivileged(
(PrivilegedAction<URLClassLoader>) () -> {
if (parent == null)
return new URLClassLoader(array);
else
return new URLClassLoader(array, parent);
});
至此org.apache.catalina.startup.Bootstrap.init()
方法执行完毕,接下来main方法继续执行,在后续代码中根据catalina.sh最后的%ACTION%参数进行不同的操作,而main方法内的setAwait、load、start均为使用反射调用catalinaDaemon(即Catalina类)对应的方法
org.apache.catalina.startup.Bootstrap.main()
public static void main(String args[]) {
// 省略初始化代码,前文已分析
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);
}
}
setAwait()
当启动tomcat时传递的参数command为start,此时首先会调用Bootstrap.setAwait()
,在setAwait方法内会使用反射调用Catalina类中的setAwait方法设置启动flag
org.apache.catalina.startup.Catalina.setAwait()
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);
}
load()
在执行完setWait后便调用load正式初始化载入
org.apache.catalina.startup.Catalina.load()
public void load() {
if (loaded) {
return;
}
loaded = true;
long t1 = System.nanoTime();
initDirs();
// Before digester - it may be needed
initNaming();
// Parse main server.xml
// 处理server.xml并初始化实例化部分模块
parseServerXml(true);
Server s = getServer();
if (s == null) {
return;
}
getServer().setCatalina(this);
getServer().setCatalinaHome(Bootstrap.getCatalinaHomeFile());
getServer()