启动脚本
Tomcat以传统命令的方式启动时(比如startup.bat脚本),startup.bat脚本里,会调用catalina.bat脚本。
启动流程
类初始化过程
startup.bat
注意,执行catalina.bat脚本的时候,命令行第一个参数是start,在catalina.bat段落会看到这个关键参数。
set "EXECUTABLE=%CATALINA_HOME%\bin\catalina.bat"
rem 省略catalina.bat存在判定代码块...
rem 读取命令行参数
set CMD_LINE_ARGS=
:setArgs
if ""%1""=="""" goto doneSetArgs
set CMD_LINE_ARGS=%CMD_LINE_ARGS% %1
shift
goto setArgs
:doneSetArgs
rem 执行catalina.bat脚本
call "%EXECUTABLE%" start %CMD_LINE_ARGS%
catalina.bat
这里只展示了关键部分,概要说明一下常规启动方式。
rem 如果命令行第一个参数是start,那么进入doStart脚本分支
if ""%1"" == ""start"" goto doStart
rem 注意这里的mainClass,启动类是Bootstrap
set _EXECJAVA=%_RUNJAVA%
set MAINCLASS=org.apache.catalina.startup.Bootstrap
set ACTION=start
:doStart
shift
if "%TITLE%" == "" set TITLE=Tomcat
set _EXECJAVA=start "%TITLE%" %_RUNJAVA%
if not ""%1"" == ""-security"" goto execCmd
shift
echo Using Security Manager
set "SECURITY_POLICY_FILE=%CATALINA_BASE%\conf\catalina.policy"
goto execCmd
:execCmd
rem 设置命令行启动参数,一般不设置
set CMD_LINE_ARGS=
rem 在这里启动tomcat,只展示常规启动命令,忽略了其他启动分支
%_EXECJAVA% %JAVA_OPTS% %CATALINA_OPTS% %DEBUG_OPTS% -Djava.endorsed.dirs="%JAVA_ENDORSED_DIRS%" -classpath "%CLASSPATH%" -Dcatalina.base="%CATALINA_BASE%" -Dcatalina.home="%CATALINA_HOME%" -Djava.io.tmpdir="%CATALINA_TMPDIR%" %MAINCLASS% %CMD_LINE_ARGS% %ACTION%
注意这里的mainClass,启动类是【Bootstrap.class】
完整启动命令
可以对照上个段落中,catalina.bat里的启动脚本。
/usr/java/jdk1.7.0_71//bin/java -Djava.util.logging.config.file=/home/opt/tools/tomcat/conf/logging.properties -Djava.util.logging.manager=org.apache.juli.ClassLoaderLogManager -Djava.endorsed.dirs=/home/opt/tools/tomcat/endorsed -classpath /home/opt/tools/tomcat/bin/bootstrap.jar:/home/opt/tools/tomcat/bin/tomcat-juli.jar -Dcatalina.base=/home/opt/tools/tomcat -Dcatalina.home=/home/opt/tools/tomcat -Djava.io.tmpdir=/home/opt/tools/tomcat/temp org.apache.catalina.startup.Bootstrap start
对应源码
Bootstrap
main
这是Tomcat的操作入口,执行Bootstrap的方法main(String args[]),然后调用Catalina里的启动方法,根据配置文件,启动Tomcat服务。
private static volatile Bootstrap daemon = null;
private Object catalinaDaemon = null;
public static void main(String args[]) {
// 初始化实例
Bootstrap bootstrap = new Bootstrap();
// 初始化
bootstrap.init();
// 在初始化完成之后,再设置为daemon
daemon = bootstrap;
// 默认命令行参数
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...
}
init
先看看初始化方法,加载相关的资源和类文件;其中关键的地方是加载Catalina类,后续启动的时候,会使用反射来执行其内部start方法。
public void init() {
// 初始化启动类
initClassLoaders();
Thread.currentThread().setContextClassLoader(catalinaLoader);
// 加载启动类
Class<?> startupClass = catalinaLoader.loadClass("org.apache.catalina.startup.Catalina");
Object startupInstance = startupClass.getConstructor().newInstance();
// 设置启动类属性,后续使用此实例进行启动
catalinaDaemon = startupInstance;
}
start
引导类中的启动方法,可以看到是直接用反射的方式来调用Catalina类的启动方法
public void start(){
// 不会为空,因为上面的init方法执行之后,就会对此变量进行赋值
if (catalinaDaemon == null) {
init();
}
// 调用Catalina类的start方法
Method method = catalinaDaemon.getClass().getMethod("start", (Class [] )null);
method.invoke(catalinaDaemon, (Object [])null);
}
Catalina
最后来看看Catalina类的启动方法,getServer()获取到的就是前一章节提到的Server节点,代表的是一个tomcat服务,里面会有连接器和容器,先不展开,后续介绍;
/**
* Start a new server instance.
*/
public void start() {
if (getServer() == null) {
// 读取server.xml配置文件
load();
}
// 计算启动时间
long t1 = System.nanoTime();
// Start the new server
try {
// 开始启动,StandardServer类
getServer().start();
} catch (LifecycleException e) {
// 启动失败,停止服务
getServer().destroy();
return;
}
long t2 = System.nanoTime();
// 日志里输出启动时间
log.info(sm.getString("catalina.startup", Long.valueOf((t2 - t1) / 1000000)));
}
到此,tomcat服务就启动完成了。