Tomcat9源代码分析(二)-初始化

Tomcat9源代码分析(二)-初始化


回顾

  前面一篇文章提到,当Tomecat启动(startup.sh)时,会调用org.apache.catalina.startup.Bootstrap.main()方法。

正文

  通过《Tomcat9源代码分析(一)-源码切入点》中的Tomcate9源代码下载地址我们获取到了apache-tomcat-9.0.2-src.zip文件,直接解压即可得到apache-tomcat-9.0.2源代码。用IDEA打开后如下图

  什么都不说,咱们直奔主题来看看Bootstrap.main()做了些什么。

代码2-1

org.apache.catalina.startup.Bootstrap#main

    /**
     * 通过提供的启动Tomcat时的主要方法和入口点
     *
     * @param args命令行参数进行处理
     */
    public static void main(String args[]) {

        if (daemon == null) {
            // 创建Bootstrap对象
            Bootstrap bootstrap = new Bootstrap();
            try {
                //bootstrap初始化
                bootstrap.init();
            } catch (Throwable t) {
                handleThrowable(t);
                t.printStackTrace();
                return;
            }
            daemon = bootstrap;
        } else {
            //重置当前线程类加载器
            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";
                //Bootstrap加载信息
                //内部操作:用反射调用catalinaDaemon的load方法加载和解析server.xml配置文件
                daemon.load(args);
                //Bootstrap启动
                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();
            } 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(引导程序)对象是否存在
  • 不存在则创建Bootstrap对象并初始化bootstrap.init();
  • 存在则将当前的Bootstrap对象的类加载器赋给当前线程的类加载器
  • 根据不同参数执行不同的操作

  本章讲Tomcat服务启动我们直接看start做了些什么。

  • Bootstrap加载基础信息(用反射调用catalinaDaemon的load方法加载和解析server.xml配置文件,往下看)
  • Bootstrap启动

  我们逐一来了解各步骤做了些什么。(异常情况本系列文章暂不做探究)我们先来看看初始化(==bootstrap.init();==)做了些什么。

代码2-2

org.apache.catalina.startup.Bootstrap#init()

    /**
     * 初始化程序
     *
     * @throws 抛出异常致命初始化错误
     */
    public void init() throws Exception {
        //初始化类加载器
        initClassLoaders();
        //设置Tomcat私有加载器到当前线程
        Thread.currentThread().setContextClassLoader(catalinaLoader);
        //加载Tomcat容器所需的class
        SecurityClassLoad.securityClassLoad(catalinaLoader);

        // 加载我们的启动类并调用它的process()方法
        if (log.isDebugEnabled())
            log.debug("Loading startup class");
        //通过反射找到启动类Catalina
        Class startupClass = catalinaLoader.loadClass("org.apache.catalina.startup.Catalina");
        Object startupInstance = startupClass.getConstructor().newInstance();

        // 设置共享类加载器
        if (log.isDebugEnabled())
            log.debug("Setting startup class properties");
        //反射找到父类加载器
        String methodName = "setParentClassLoader";
        Class paramTypes[] = new Class[1];
        //反射找到类加载器ClassLoader
        paramTypes[0] = Class.forName("java.lang.ClassLoader");
        Object paramValues[] = new Object[1];
        paramValues[0] = sharedLoader;
        Method method =
                startupInstance.getClass().getMethod(methodName, paramTypes);
        //调用ClassLoader.setParentClassLoader方法(执行加载器)
        method.invoke(startupInstance, paramValues);

        catalinaDaemon = startupInstance;

    }

  通过代码2-2我们可以看到引导程序(Bootstrap)初始化的步骤:
- 初始化类加载器
- 将类加载器设置到当前线程
- 加载Tomcat容器所需class
- 通过反射加载Tomcat启动类Catalina并调用process()类

  在看初始化类加载的实现之前,我们需要先了解Java类加载器都有哪些,分别是做什么用的。

  Java虚拟机规范中提到的主要类加载器;

  • Bootstrap Loader:加载lib目录下或者System.getProperty(“sun.boot.class.path”)、或者-XBootclasspath所指定的路径或jar。
  • Extended Loader:加载lib\ext目录下或者System.getProperty(“java.ext.dirs”) 所指定的 路径或jar。在使用Java运行程序时,也可以指定其搜索路径,例如:java -Djava.ext.dirs=d:\projects\testproj\classes HelloWorld。
  • AppClass Loader:加载System.getProperty(“java.class.path”)所指定的 路径或jar。在使用Java运行程序时,也可以加上-cp来覆盖原有的Classpath设置,例如: java -cp ./lavasoft/classes HelloWorld。

  根据java虚拟机的双亲委派模式的原则,类加载器在加载一个类时,首先交给父类加载器加载,层层往上直到Bootstrap Loader。也就是一个类最先由Bootstrap Loader加载,如果没有加载到,则交给下一层的类加载器加载,如果没有加载到,则依次层层往下,直到最下层的类加载器。这也就是说,凡是能通过父一级类加载器加载到的类,对于子类也是可见的。因此可以利用双亲委派模式的特性,使用类加载器对不同路径下的jar包或者类进行环境隔离。

然后用一张图片来展示Tomcat的类加载体系:

  结合之前对双亲委派模式的类加载过程的描述,对上图所示类加载体系进行介绍:

  • ClassLoader:Java提供的类加载器抽象类,用户自定义的类加载器需要继承实现
  • commonLoader:Tomcat最基本的类加载器,加载路径中的class可以被Tomcat容器本身以及各个Webapp访问;
  • catalinaLoader:Tomcat容器私有的类加载器,加载路径中的class对于Webapp不可见;
  • sharedLoader:各个Webapp共享的类加载器,加载路径中的class对于所有Webapp可见,但是对于Tomcat容器不可见;
  • WebappClassLoader:各个Webapp私有的类加载器,加载路径中的class只对当前Webapp可见;
代码2-3

org.apache.catalina.startup.Bootstrap#initClassLoaders

    /**
     * 初始化类加载器
     */
    private void initClassLoaders() {
        try {
            //创建Tomcat基本类加载器
            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();
            }
            //创建Tomcat容器私有类加载器
            catalinaLoader = createClassLoader("server", commonLoader);
            //创建WebApp共享类加载器
            sharedLoader = createClassLoader("shared", commonLoader);
        } catch (Throwable t) {
            handleThrowable(t);
            log.error("Class loader creation threw exception", t);
            System.exit(1);
        }
    }

  ==代码2-3==我们了解initClassLoaders顺序创建各个类加载器。

代码2-4

org.apache.catalina.startup.Bootstrap#createClassLoader

    /**
     * 创建类加载器
     * @param name 加载器名称
     * @param parent 加载器
     * @return 加载器对象
     * @throws Exception 议持仓
     */
    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 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
            }

            // 加载资源文件
            if (repository.endsWith("*.jar")) {
                //全局资源文件(*.jar)
                repository = repository.substring
                        (0, repository.length() - "*.jar".length());
                repositories.add(
                        new Repository(repository, RepositoryType.GLOB));
            } else if (repository.endsWith(".jar")) {
                //JAR包
                repositories.add(
                        new Repository(repository, RepositoryType.JAR));
            } else {
                //文件
                repositories.add(
                        new Repository(repository, RepositoryType.DIR));
            }
        }
        //创建加载器对象
        return ClassLoaderFactory.createClassLoader(repositories, parent);
    }

  ==代码2-4==则是获取创建类加载器所需参数(资源文件),调用==ClassLoaderFactory.createClassLoader==方法创建类加载器对象并返回。

代码2-5
  • org.apache.catalina.startup.ClassLoaderFactory#createClassLoader(List, ClassLoader)
    /**
     * 基于配置(默认值和指定的目录路径)创建和返回一个新的类加载器
     *
     * @param 类目录、JAR文件、JAR目录的存储库列表,或者应该添加到存储库中的URL,类加载器
     * @param 新类加载器对应的父类加载器
     * @return 新类加载器
     *
     * @exception Exception if an error occurs constructing the class loader
     */
    public static ClassLoader createClassLoader(List repositories,
                                                final ClassLoader parent)
        throws Exception {

        if (log.isDebugEnabled())
            log.debug("Creating new class loader");

        // 为类加载器构建包路径
        Set set = new LinkedHashSet<>();

        if (repositories != null) {
            //遍历资源仓库列表
            for (Repository repository : repositories)  {
                //如果资源类型为包路径
                if (repository.getType() == RepositoryType.URL) {
                    //根据路径构建类加载器包路径
                    URL url = buildClassLoaderUrl(repository.getLocation());
                    if (log.isDebugEnabled())
                        log.debug("  Including URL " + url);
                    set.add(url);
                } else if (repository.getType() == RepositoryType.DIR) {//如果资源文件类型为文件
                    File directory = new File(repository.getLocation());
                    directory = directory.getCanonicalFile();
                    //验证资源文件
                    if (!validateFile(directory, RepositoryType.DIR)) {
                        continue;
                    }
                    //根据文件构建类加载器包路径
                    URL url = buildClassLoaderUrl(directory);
                    if (log.isDebugEnabled())
                        log.debug("  Including directory " + url);
                    set.add(url);
                } else if (repository.getType() == RepositoryType.JAR) {//如果资源文件路径为JAR包
                    File file=new File(repository.getLocation());
                    file = file.getCanonicalFile();
                    //验证JAR包
                    if (!validateFile(file, RepositoryType.JAR)) {
                        continue;
                    }
                    //根据JAR包构建类加载器包路径
                    URL url = buildClassLoaderUrl(file);
                    if (log.isDebugEnabled())
                        log.debug("  Including jar file " + url);
                    set.add(url);
                } else if (repository.getType() == RepositoryType.GLOB) {//如果资源文件路径为全局资源文件
                    File directory=new File(repository.getLocation());
                    directory = directory.getCanonicalFile();
                    //验证文件夹
                    if (!validateFile(directory, RepositoryType.GLOB)) {
                        continue;
                    }
                    if (log.isDebugEnabled())
                        log.debug("  Including directory glob "
                            + directory.getAbsolutePath());
                    String filenames[] = directory.list();
                    if (filenames == null) {
                        continue;
                    }
                    //循环构建类加载器包路径
                    for (int j = 0; j < filenames.length; j++) {
                        String filename = filenames[j].toLowerCase(Locale.ENGLISH);
                        if (!filename.endsWith(".jar"))
                            continue;
                        File file = new File(directory, filenames[j]);
                        file = file.getCanonicalFile();
                        if (!validateFile(file, RepositoryType.JAR)) {
                            continue;
                        }
                        if (log.isDebugEnabled())
                            log.debug("    Including glob jar file "
                                + file.getAbsolutePath());
                        URL url = buildClassLoaderUrl(file);
                        set.add(url);
                    }
                }
            }
        }

        // 将构造的包放入类加载器中
        final URL[] array = set.toArray(new URL[set.size()]);
        if (log.isDebugEnabled())
            for (int i = 0; i < array.length; i++) {
                log.debug("  location " + i + " is " + array[i]);
            }

        //类加载器授权
        return AccessController.doPrivileged(
                new PrivilegedAction() {
                    @Override
                    public URLClassLoader run() {
                        if (parent == null)
                            return new URLClassLoader(array);
                        else
                            return new URLClassLoader(array, parent);
                    }
                });
    }

  ==代码2-5==可我们可以看出来,类加载器的创建即将资源文件添加到加载器中并授权,到此处我们可以想想,如果我们想Tomcat加载我们自己的类,是否可以在此处做功夫呢?理论可行,有兴趣的同学自己自己尝试下
  此处本人还有个疑问的地方,老版本的类加载器,创建完毕后会将创建的类加载器注册到JMX服务中,但在Tomcat9中此处不再注册,那移动到哪去了呢?有了解的同学欢迎帮忙解答。
  看到此处我们继续深入看看类加载器资源配置文件都写了些什么

代码2-6

conf/catalina.properties

# 多个服务资源文件路径使用“,”分隔
# 加载器的前缀用来定义加载器类型(common,server,shared)
# 路径不许是相对与catalina.base或catalina.home或绝对路径
# “common.loader”如果不设置值则将作为Catalina服务的资源文件
# “foo”:添加整个文件夹中的内容
# "foo/*.jar" 添加文件夹中所有的.jar文件
# "foo/bar.jar" 添加指定的jar文件
# 设置值使用“...”的情况下catalina.base或catalina.base路径包含一个逗号,因为“”用于赋值,路径可能会不存在
common.loader="${catalina.base}/lib","${catalina.base}/lib/*.jar","${catalina.home}/lib","${catalina.home}/lib/*.jar"
server.loader=
shared.loader=

  看到==代码2-6==我们会发现基本类加载器加载了Tomcat目录下lib文件夹下的所有文件
  通过==代码2-2==我们知道下一步操作为:加载Tomcat容器所需的class

代码2-7

org.apache.catalina.security.SecurityClassLoad#securityClassLoad(ClassLoader)

    /**
     * 加载Tomcat容器所需的class
     *
     * @param loader                 加载器(CatalinaLoader)
     * @param requireSecurityManager 空
     * @throws Exception
     */
    static void securityClassLoad(ClassLoader loader, boolean requireSecurityManager)
            throws Exception {

        if (requireSecurityManager && System.getSecurityManager() == null) {
            return;
        }
        //加载Tomcat 核心class(org.apache.catalina.core)
        loadCorePackage(loader);
        loadCoyotePackage(loader);
        //WebappClassLoade基础class(org.apache.catalina.loader)
        loadLoaderPackage(loader);
        loadRealmPackage(loader);
        //Tomcat有关Servlet的class(org.apache.catalina.servlets)
        loadServletsPackage(loader);
        //Tomcat有关session的class(org.apache.catalina.session)
        loadSessionPackage(loader);
        //Tomcat工具类的class(org.apache.catalina.util)
        loadUtilPackage(loader);
        //Tomcat处理Cookie的
        loadJavaxPackage(loader);
        //Tomcat处理请求的class(org.apache.catalina.connector)
        loadConnectorPackage(loader);
        loadTomcatPackage(loader);
    }

  到这里,我们Tomcat初始化就完成了。下一篇文件我们一起来看看初始化后Bootstrap.load做了些什么

你可能感兴趣的:(Java,tomcat,源代码,源码)