tomcat启动浅析 - 969251639/study GitHub Wiki
在启动tomcat时基本上所有的开发人员都知道在tomcat目录中bin目录下执行startup.sh(linux)或startup.bat(windows)这个脚本
...
PRGDIR=`dirname "$PRG"`
EXECUTABLE=catalina.sh
...
传递一个start参数到catalina脚本中
exec "$PRGDIR"/"$EXECUTABLE" start "$@"
最终这个脚本最后会执行当前目录下的catalina.sh(linux)或catalina.bat(windows)这个脚本
elif [ "$1" = "start" ] ; then
检测tomcat是否已经启动
if [ ! -z "$CATALINA_PID" ]; then
if [ -f "$CATALINA_PID" ]; then
if [ -s "$CATALINA_PID" ]; then
echo "Existing PID file found during start."
if [ -r "$CATALINA_PID" ]; then
PID=`cat "$CATALINA_PID"`
ps -p $PID >/dev/null 2>&1
if [ $? -eq 0 ] ; then
echo "Tomcat appears to still be running with PID $PID. Start aborted."
echo "If the following process is not a Tomcat process, remove the PID file and try again:"
ps -f -p $PID
exit 1
else
echo "Removing/clearing stale PID file."
rm -f "$CATALINA_PID" >/dev/null 2>&1
if [ $? != 0 ]; then
if [ -w "$CATALINA_PID" ]; then
cat /dev/null > "$CATALINA_PID"
else
echo "Unable to remove or clear stale PID file. Start aborted."
exit 1
fi
fi
fi
else
echo "Unable to read PID file. Start aborted."
exit 1
fi
else
rm -f "$CATALINA_PID" >/dev/null 2>&1
if [ $? != 0 ]; then
if [ ! -w "$CATALINA_PID" ]; then
echo "Unable to remove or write to empty PID file. Start aborted."
exit 1
fi
fi
fi
fi
fi
shift
touch "$CATALINA_OUT" //输出CATALINA日志
if [ "$1" = "-security" ] ; then
if [ $have_tty -eq 1 ]; then
echo "Using Security Manager"
fi
shift
//nohup方式后台启动java进程,并且指定org.apache.catalina.startup.Bootstrap这个类面方法启动,并传递start参数到main方法中
eval $_NOHUP "\"$_RUNJAVA\"" "\"$LOGGING_CONFIG\"" $LOGGING_MANAGER $JAVA_OPTS $CATALINA_OPTS \
-Djava.endorsed.dirs="\"$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 \
>> "$CATALINA_OUT" 2>&1 "&"
else
eval $_NOHUP "\"$_RUNJAVA\"" "\"$LOGGING_CONFIG\"" $LOGGING_MANAGER $JAVA_OPTS $CATALINA_OPTS \
-Djava.endorsed.dirs="\"$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."
所以tomcat的启动入口就在org.apache.catalina.startup.Bootstrap这个类的main方法中,且main方法中的args参数会接收一个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);
//加载server。xml,同事初始化server的生命周期
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);
}
}
上面的面方法主要由两个重点
- Bootstrap类的初始化,主要加载了tomcat的类加载器,这个在加载器那一章分析
- daemon(即Bootstrap)的load和start
下面分析下Bootstrap类的load方法
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);
}
很简单,用反射迪奥用了catalinaDaemon实例的load方法,而catalinaDaemon又是哪个实例呢?
在上面的第一步的时候,也就是Bootstrap类的初始化的时候就已经创建好了,看下Bootstrap类的init方法
public void init() throws Exception {
....
Class<?> startupClass = catalinaLoader.loadClass("org.apache.catalina.startup.Catalina");
Object startupInstance = startupClass.getConstructor().newInstance();
...
catalinaDaemon = startupInstance;
}
很明显,catalinaDaemon的实例是org.apache.catalina.startup.Catalina,也就是load方法最终会调用org.apache.catalina.startup.Catalina类的load方法
public void load() {
...
// Create and execute our Digester
// 创建server.xml解析规则
Digester digester = createStartDigester();
...
try {
inputSource.setByteStream(inputStream);
digester.push(this);//将当前对象压入栈顶,这样在创建Server时就可以调用this,也就时Catalina类的setServer注入Server对象了
digester.parse(inputSource);//解析xml,注入对象
} catch (SAXParseException spe) {
log.warn("Catalina.start using " + getConfigFile() + ": " +
spe.getMessage());
return;
} catch (Exception e) {
log.warn("Catalina.start using " + getConfigFile() + ": " , e);
return;
}
// 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("Catalina.start", e);
}
}
...
}
protected Digester createStartDigester() {
...
// Configure the actions we will be using
digester.addObjectCreate("Server",
"org.apache.catalina.core.StandardServer",
"className");//设置server节点的实例类是org.apache.catalina.core.StandardServer
digester.addSetProperties("Server");//注入Server节点中配置的属性到Server类中
digester.addSetNext("Server",
"setServer",
"org.apache.catalina.Server");//调用栈顶类的setServer,也就是Catalina的setServer方法
digester.addObjectCreate("Server/GlobalNamingResources",
"org.apache.catalina.deploy.NamingResourcesImpl");//设置GlobalNamingResources节点的实例类是org.apache.catalina.core.NamingResourcesImpl
digester.addSetProperties("Server/GlobalNamingResources");//注入GlobalNamingResources节点中配置的属性到GlobalNamingResources类中
digester.addSetNext("Server/GlobalNamingResources",
"setGlobalNamingResources",
"org.apache.catalina.deploy.NamingResourcesImpl");//调用Server类的setGlobalNamingResources,也就是StandardServer的setGlobalNamingResources方法
...
}
- 调用createStartDigester方法创建Digester,也就是解析server.xml节点规则
- 调用Digester.parse解析server.xml
- 初始化Server(org.apache.catalina.core.StandardServer)
另外启动前Bootstrap还有一段静态代码块会先执行,主要设置了下tomcat的当前路径
总之,load方法最终的调用就是Server.init进行服务器的初始化,初始化完后进入Bootstrap.start方法
public void start()
throws Exception {
if( catalinaDaemon==null ) init();
Method method = catalinaDaemon.getClass().getMethod("start", (Class [] )null);
method.invoke(catalinaDaemon, (Object [])null);
}
也很类似,用反射调用org.apache.catalina.startup.Catalina的start方法
public void start() {
// 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;
}
}
而start方法最重要还是调用Server.start方法进行服务器启动工作