Tomcat源码解析系列(一)Bootstrap 启动

前言

Tomcat是后端服务最常见的web容器,对于后端程序员来说,了解它对个人自身技术能力以及业务开发能力都是必要的。

tomcat 版本:9.0.16

1. main 方法

Tomcat是可以独立启动的,java程序启动是需要main方法的,因此读tomcat源码就从它的main方法开始。Tomcat的main方法在org.apache.catalina.startup.Bootstrap

public final class Bootstrap {
    ……
    
    /**
     * Daemon object used by main.
     */
    private static final Object daemonLock = new Object();
    
    ……
    
    
   /**
     * Main method and entry point when starting Tomcat via the provided
     * scripts.
     *
     * @param args Command line arguments to be processed
     */
    public static void main(String args[]) {

        synchronized (daemonLock) {
            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();
                if (null == daemon.getServer()) {
                    System.exit(1);
                }
            } 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 对象,调用它的 init 方法初始化,然后根据启动参数,分别调用 Bootstrap 对象的不同方法。


1.1 init方法

首先看 Bootstrap 的 init 方法

/**
     * Initialize daemon.
     * @throws Exception Fatal initialization error
     */
    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() 来初始化一些 ClassLoader,tomcat 需要加载应用程序里,所以需要 ClassLoader,然后设置了一下当天线程的 contextClassLoader 为

catalinaLoader,这个 catalinaLoader就是在 initClassLoaders() 初始化的,最后通过反射创建了一个 Catalina 类型的 startupInstance 对象,并调用了它的 setParentClassLoader 方法。

1.1.1 initClassLoaders 方法

接着看一下 initClassLoaders() 方法

ClassLoader commonLoader = null;
    ClassLoader catalinaLoader = null;
    ClassLoader sharedLoader = null;



    private void initClassLoaders() {
        try {
            commonLoader = createClassLoader("common", null);
            if( commonLoader == null ) {
                // no config file, default to this loader - we might be in a 'single' env.
                commonLoader=this.getClass().getClassLoader();
            }
            catalinaLoader = createClassLoader("server", commonLoader);
            sharedLoader = createClassLoader("shared", commonLoader);
        } catch (Throwable t) {
            handleThrowable(t);
            log.error("Class loader creation threw exception", t);
            System.exit(1);
        }
    }

initClassLoaders() 就是为了初始化 Bootstrap 类里的三个 ClassLoader 成员变量,看 createClassLoader 方法的定义

private ClassLoader createClassLoader(String name, ClassLoader parent)

可以看出,catalinaLoader 和 sharedLoader 的 parentClassLoader 是 commonLoader。


1.1.2 initClassLoaders 方法

下面看看 createClassLoader 方法

private ClassLoader createClassLoader(String name, ClassLoader parent)
        throws Exception {

        String value = CatalinaProperties.getProperty(name + ".loader");
        if ((value == null) || (value.equals("")))
            return parent;

        value = replace(value);

        List<Repository> repositories = new ArrayList<>();

        String[] repositoryPaths = getPaths(value);

        for (String repository : repositoryPaths) {
            // Check for a JAR URL repository
            try {
                @SuppressWarnings("unused")
                URL url = new URL(repository);
                repositories.add(new Repository(repository, RepositoryType.URL));
                continue;
            } catch (MalformedURLException e) {
                // Ignore
            }

            // Local repository
            if (repository.endsWith("*.jar")) {
                repository = repository.substring
                    (0, repository.length() - "*.jar".length());
                repositories.add(new Repository(repository, RepositoryType.GLOB));
            } else if (repository.endsWith(".jar")) {
                repositories.add(new Repository(repository, RepositoryType.JAR));
            } else {
                repositories.add(new Repository(repository, RepositoryType.DIR));
            }
        }

        return ClassLoaderFactory.createClassLoader(repositories, parent);
    }

方法的逻辑也比较简单

就是从 catalina.property文件里找 common.loader, shared.loader, server.loader 对应的值,然后构造成Repository 列表,再将Repository 列表传入ClassLoaderFactory.createClassLoader 方法,ClassLoaderFactory.createClassLoader 返回的是 URLClassLoader,而Repository 列表就是这个URLClassLoader 可以加在的类的路径。

在catalina.property文件里

common.loader="${catalina.base}/lib","${catalina.base}/lib/*.jar","${catalina.home}/lib","${catalina.home}/lib/*.jar"
server.loader=
shared.loader=

其中 shared.loader, server.loader 是没有值的,createClassLoader 方法里如果没有值的话,就返回传入的 parent ClassLoader,也就是说,commonLoader,catalinaLoader,sharedLoader 其实是一个对象。在Tomcat之前的版本里,这三个是不同的URLClassLoader对象。

Class<?> startupClass = catalinaLoader.loadClass("org.apache.catalina.startup.Catalina");
        Object startupInstance = startupClass.getConstructor().newInstance();

初始化完三个ClassLoader对象后,init() 方法就使用 catalinaClassLoader 加载了org.apache.catalina.startup.Catalina 类,并创建了一个对象,然后通过反射调用这个对象的 setParentClassLoader 方法,传入的参数是 sharedClassLoader。最后吧这个 Catania 对象复制给 catalinaDaemon 属性。

1.2 start方法

main 方法初始化完 Bootstrap 对象后,就根据传入的参数,分别调用 Bootstrap 不同的方法,以 "start" 参数为例,调用了这三个方法

daemon.setAwait(true);
daemon.load(args);
daemon.start();

这三个方法的调用的共同点是通过反射去调用 init 方法里初始化过的 Catalina 对象的同名的方法。这三个方法将在下一篇文章总结

1.2 Bootstrap 的 static 块

Bootstrap 有一个 static 的代码块,这个代码块的作用是用来初始化 Bootstrap 里的两个static属性的

private static final File catalinaBaseFile;
private static final File catalinaHomeFile;

2. 小结

可以看出 Bootstrap 的作用是初始化公共资源,创建一个 Catalina 类,并执行其相关方法,起了一个引导的作用。

原文 

https://segmentfault.com/a/1190000021991301

本站部分文章源于互联网,本着传播知识、有益学习和研究的目的进行的转载,为网友免费提供。如有著作权人或出版方提出异议,本站将立即删除。如果您对文章转载有任何疑问请告之我们,以便我们及时纠正。

PS:推荐一个微信公众号: askHarries 或者qq群:474807195,里面会分享一些资深架构师录制的视频录像:有Spring,MyBatis,Netty源码分析,高并发、高性能、分布式、微服务架构的原理,JVM性能优化这些成为架构师必备的知识体系。还能领取免费的学习资源,目前受益良多

转载请注明原文出处:Harries Blog™ » Tomcat源码解析系列(一)Bootstrap 启动

赞 (0)
分享到:更多 ()

评论 0

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址