前言

Java版本:1.8 Tomcat版本:9.0.31 Tomcat安装目录:/usr/local/Cellar/tomcat/9.0.31_1/libexec

Bootstrap

Tomcat的启动程序是org.apache.catalina.startup.Bootstrap这个类,先分析它的静态代码,这一块最先被执行:

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());
}

这段代码的含义是依次初始化catalina.home和catalina.base这两个环境变量,根据启动方式的不同Tomcat作者考虑了多种情形,以确保这两个环境变量能被设置正确

接下来就是Bootstrap的main方法了,关键代码其实只有短短六行:

Bootstrap bootstrap = new Bootstrap();
bootstrap.init();
daemon = bootstrap;

daemon.load(args);  // 调用Catalina.load(args)
daemon.start();

可以看到,依次调用了Bootstrap类的init、load、start方法,分别对应初始化、加载、启动,接下来分析这三个方法

先是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;
}

这个方法其实就干了两件事儿,初始化类加载器、拉起tomcat真正的主程序Catalina类。Tomcat由于其作为中间件要考虑各种因素因此类加载器也是为自己量身定制了一套,关于Tomcat的类加载器网上其它地方有详细的描述,这里不重复了;虽然Bootstrap是入口程序,但是真正的主程序其实是Catalina,Bootstrap看名字就知道只是负责拉起Catalina和一些初始化操作而已,因此这里init的时候就拉起了一个新的Catalina实例

Bootstrap.load方法其实就是调用了Catalina.load方法,而Catalina.load方法用伪代码来表示关键代码就是:

public void load() {
    // 如果启动的时候不加-nonaming默认开启JNDI查询
    initNaming();
    Digester digester = createStartDigester();
    digester.parse("server.xml");
    getServer().init();
}

这里涉及一个重要的类Digester,这是Tomcat用来解析server.xml并且初始化容器相关类的方式,这里比较复杂,因此另起一篇文章来专门讲这个Digester