水煮Tomcat(二) - 俺是咋启动的?

171 阅读2分钟

启动脚本

Tomcat以传统命令的方式启动时(比如startup.bat脚本),startup.bat脚本里,会调用catalina.bat脚本。

启动流程

image.png

类初始化过程

image.png

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服务就启动完成了。