本系列以Tomcat 8.5.33为例分析Tomcat的启动过程。
Tomcat的启动脚本
与Tomcat有关的脚本都在Tomcat主目录的bin子目录中,其中与启动有关的脚本有startup.sh、catalina.sh和setclasspath.sh。启动Tomcat时只需执行startup.sh即可,其内容如下:
# 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 "$@"
该脚本主要做了两件事:
- 用while循环处理符号链接,ls变量存ls命令的结果,link变量存符号链接指向的文件路径,直到PRG的值不再是符号链接为止,以此找到Tomcat的主目录;
- exec命令执行catalina.sh,命令行参数由start和传递给startup.sh的命令行参数两部分组成。
catalina.sh利用setclasspath.sh检验JDK或者JRE环境,然后根据传递给脚本的的命令行参数启动JVM,不同参数对应的分支代码如下,传给catalina.sh脚本的第一个命令行参数是给Bootstrap类传递的最后一个命令行参数。以默认情况为例,上文的startup.sh只为catalina.sh提供了start参数,则$@为空,因此会执行eval所示的代码。
if [ "$1" = "debug" ] ; then
# 省略部分代码
elif [ "$1" = "run" ]; then
# 省略部分代码
elif [ "$1" = "start" ] ; then
# 省略部分代码
shift
touch "$CATALINA_OUT"
if [ "$1" = "-security" ] ; then
# 省略部分代码
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 \
>> "$CATALINA_OUT" 2>&1 "&"
fi
if [ ! -z "$CATALINA_PID" ]; then
echo $! > "$CATALINA_PID"
fi
echo "Tomcat started."
elif [ "$1" = "stop" ] ; then
# 省略部分代码
elif [ "$1" = "configtest" ] ; then
# 省略部分代码
elif [ "$1" = "version" ] ; then
# 省略部分代码
else
echo "Usage: catalina.sh ( commands ... )"
# 省略部分代码
fi
用ps aux | grep tomcat可以看到对应的执行命令如下
/Library/Java/JavaVirtualMachines/jdk1.8.0_131.jdk/Contents/Home/bin/java
-Djava.util.logging.config.file=/Users/suntao/Documents/Workspace/apache-tomcat-8.5.33/conf/logging.properties
-Djava.util.logging.manager=org.apache.juli.ClassLoaderLogManager
-Djdk.tls.ephemeralDHKeySize=2048
-Djava.protocol.handler.pkgs=org.apache.catalina.webresources
-Dorg.apache.catalina.security.SecurityListener.UMASK=0027
-Dignore.endorsed.dirs= -classpath /Users/suntao/Documents/Workspace/apache-tomcat-8.5.33/bin/bootstrap.jar:/Users/suntao/Documents/Workspace/apache-tomcat-8.5.33/bin/tomcat-juli.jar
-Dcatalina.base=/Users/suntao/Documents/Workspace/apache-tomcat-8.5.33
-Dcatalina.home=/Users/suntao/Documents/Workspace/apache-tomcat-8.5.33
-Djava.io.tmpdir=/Users/suntao/Documents/Workspace/apache-tomcat-8.5.33/temp
org.apache.catalina.startup.Bootstrap start
- catalina.home是Tomcat的安装主目录;
- catalina.base是Tomcat的工作主目录,在多个Tomcat时与catalina.home不同;
- 主类是org.apache.catalina.startup.Bootstrap,命令行参数只有一个start,下面就分析Bootstrap类。
Bootstrap类
静态代码块
首先看静态代码块,该代码块重新设置了两个系统属性,catalina.home和catalina.base。
private static final File catalinaBaseFile;
private static final File catalinaHomeFile;
static {
// Will always be non-null
String userDir = System.getProperty("user.dir");
// Home first
String home = System.getProperty(Globals.CATALINA_HOME_PROP);
File homeFile = null;
if (home != null) {
File f = new File(home);
try {
homeFile = f.getCanonicalFile();
} catch (IOException ioe) {
homeFile = f.getAbsoluteFile();
}
}
if (homeFile == null) {
// First fall-back. See if current directory is a bin directory
// in a normal Tomcat install
File bootstrapJar = new File(userDir, "bootstrap.jar");
if (bootstrapJar.exists()) {
File f = new File(userDir, "..");
try {
homeFile = f.getCanonicalFile();
} catch (IOException ioe) {
homeFile = f.getAbsoluteFile();
}
}
}
if (homeFile == null) {
// Second fall-back. Use current directory
File f = new File(userDir);
try {
homeFile = f.getCanonicalFile();
} catch (IOException ioe) {
homeFile = f.getAbsoluteFile();
}
}
catalinaHomeFile = homeFile;
System.setProperty(
Globals.CATALINA_HOME_PROP, catalinaHomeFile.getPath());
// Then base
String base = System.getProperty(Globals.CATALINA_BASE_PROP);
if (base == null) {
catalinaBaseFile = catalinaHomeFile;
} else {
File baseFile = new File(base);
try {
baseFile = baseFile.getCanonicalFile();
} catch (IOException ioe) {
baseFile = baseFile.getAbsoluteFile();
}
catalinaBaseFile = baseFile;
}
System.setProperty(
Globals.CATALINA_BASE_PROP, catalinaBaseFile.getPath());
}
看源码的过程也是学习的过程,user.dir可以获取进程的工作目录,具体各属性的含义可以参见Javadoc文档,以前没用过这个属性,好奇地看了一下实现。以下是System类的部分源码,getProperty函数会从props变量取值,initProperties这个JNI方法会去初始化props变量:
private static Properties props;
private static native Properties initProperties(Properties props);
public static String getProperty(String key) {
checkKey(key);
SecurityManager sm = getSecurityManager();
if (sm != null) {
sm.checkPropertyAccess(key);
}
return props.getProperty(key);
}
接下来看一下JNI方法initProperties是如何实现的:
- 下载openjdk-8的源码,在openjdk-8-source/jdk/src/share/native/java/lang/System.c中找到initProperties对应的JNI函数Java_java_lang_System_initProperties,其部分代码如下,GetJavaProperties(env)函数返回了获得的系统属性:
JNIEXPORT jobject JNICALL Java_java_lang_System_initProperties(JNIEnv *env, jclass cla, jobject props) { char buf[128]; java_props_t *sprops = GetJavaProperties(env); // 省略一些代码 }
- java_props_t 结构体在openjdk-8-source/jdk/src/share/native/java/lang/java_props.h中定义,结构体的user_dir即为要获取的user.dir属性:
#ifdef WIN32 #include <tchar.h> typedef WCHAR nchar; #else typedef char nchar; #endif typedef struct { char *os_name; char *os_version; char *os_arch; #ifdef JDK_ARCH_ABI_PROP_NAME char *sun_arch_abi; #endif nchar *tmp_dir; nchar *font_dir; nchar *user_dir; char *file_separator; char *path_separator; char *line_separator; nchar *user_name; nchar *user_home; char *language; char *format_language; char *display_language; char *script; char *format_script; char *display_script; char *country; char *format_country; char *display_country; char *variant; char *format_variant; char *display_variant; char *encoding; char *sun_jnu_encoding; char *sun_stdout_encoding; char *sun_stderr_encoding; char *timezone; char *printerJob; char *graphics_env; char *awt_toolkit; char *unicode_encoding; /* The default endianness of unicode i.e. UnicodeBig or UnicodeLittle */ const char *cpu_isalist; /* list of supported instruction sets */ char *cpu_endian; /* endianness of platform */ char *data_model; /* 32 or 64 bit data model */ char *patch_level; /* patches/service packs installed */ char *desktop; /* Desktop name. */ // 省略一些代码 } java_props_t; java_props_t *GetJavaProperties(JNIEnv *env);
- GetJavaProperties函数在openjdk-8-source/jdk/src/solaris/native/java/lang/java_props_md.c中定义,其部分代码如下,这里看到了熟悉的getcwd函数,正是熟悉的APUE味道:
/* Current directory */ { char buf[MAXPATHLEN]; errno = 0; if (getcwd(buf, sizeof(buf)) == NULL) JNU_ThrowByName(env, "java/lang/Error", "Properties init: Could not determine current working directory."); else sprops.user_dir = strdup(buf); }
init函数
Bootstrap类的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方法初始化类加载器,并继续设置系统属性:
- 若JVM参数指定了catalina.config则根据值表示的路径加载配置文件,若文件存在则转到4,否则转到2;
- 尝试加载静态代码块解析的catalina.base的子目录conf中的catalina.properties文件,若文件存在则转到4,否则转到3;
- 尝试加载tomcat自身jar包中的org/apache/catalina/startup/catalina.properties文件;
- 将配置文件中的属性和值复制到系统属性中。
- 利用反射实例化Catalina类。
load函数
Bootstrap类的load函数代码如下,该函数利用反射在init函数生成的Catalina类实例上调用load函数,参数即为Bootstrap的命令行参数。
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);
}
main函数
在一般的启动流程中,Bootstrap类的main函数会先执行init函数,然后执行load函数,最后在start函数中利用反射调用Catalina类实例的start方法。
public static void main(String args[]) {
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();
} 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);
}
}
// 省略了一些代码
public void start()
throws Exception {
if( catalinaDaemon==null ) init();
Method method = catalinaDaemon.getClass().getMethod("start", (Class [] )null);
method.invoke(catalinaDaemon, (Object [])null);
}
// 省略了一些代码
接下来的重点是Catalina类。
网友评论