Tomcat源码解析-高层之Bootstrap

1 Tomcat启动

当我们需要启动tomcat,最常用的方式是通过Tomcat的/bin目录下的脚本startup.sh来启动Tomcat。Tomcat 本质上是一个Java程序,因此startup.sh脚本会启动
一个 JVM 来运行 Tomcat 的启动类 Bootstrap。

tomcat启动.png

2 Bootstrap职责

  • 创建tomcat自定义的类加载器commonLoader,catalinaLoader,sharedLoader

  • 创建Catalina实例

  • 作为运行Tomcat的启动类,解析脚本指令中参数,通过不同参数类型调用Catalina实例对应方法完成tomcat的启动,停止,配置等功能

3 Bootstrap运行流程

Bootstarp作为tomcat启动类,JVM启动会调用main函数完成tomcat启动启动,停止,配置等功能,其内部流程如下:

  • 1 实例化Bootstrap对象。

  • 2 对Bootstrap对象init,其内部包括构建Tomcat自定义类加载器,并实例化Catalina对象

  • 3 以main函数args参数作为指令,通过调用Catalina类中不同的方法,完成相应的功能。

    • startd指令 : 首先调用load()方法,接着调用start()方法,
    • start指令 : 首先调用setAwait()方法,接着调用load()方法,最后调用start()方法
    • stopd指令 : 调用stopServer()方法
    • configtest指令 : 调用load()方法

核心方法功能

  • bootstrap.init():负责初始化Bootstrap,在初始化的过程最重要的就是创建tomcat自定义的类加载器commonLoader,catalinaLoader,sharedLoader,并创建Catalina。
  • daemon.load(args):负责通过反射调用Catalina.load方法,实现tomcat加载。
  • daemon.start():负责通过反射调用Catalina.start方法,实现tomcat启动。
  • daemon.stop():负责通过反射调用Catalina.stop方法,实现tomcat停止。
  • daemon.setAwait(true):负责通过反射调用Catalina.setAwait方法,设置当前命令执行执行的线程是否要阻塞,对于start指令,当前线程是tomcat主线程,我们当然需要阻塞主线程,保证tomcat的运行。而主线程被释放则表示tomcat被停止。
image

相关源码

 public static void main(String args[]) {

        /** 第一次启动时为null **/
        if (daemon == null) {
            /**  实例化 Bootstrap对象**/
            Bootstrap bootstrap = new Bootstrap();
            try {
                /** 初始化bootstrap **/
                bootstrap.init();
            } catch (Throwable t) {
                handleThrowable(t);
                t.printStackTrace();
                return;
            }
            /** 设置daemon **/
            daemon = bootstrap;
        } else {
            /** 将catalinaLoader类加载器设置为当前线程的上下文类加载器 **/
            Thread.currentThread().setContextClassLoader(daemon.catalinaLoader);
        }

        try {
            /** 默认执行的指令为"start" **/
            String command = "start";

            /** 获取最后一个参数作为执行指令**/ 
            if (args.length > 0) {
                command = args[args.length - 1];
            }

            /** 根据arg中不同的参数指令调用不同的函数完成不同的功能 **/
            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) {
            if (t instanceof InvocationTargetException &&
                    t.getCause() != null) {
                t = t.getCause();
            }
            /** 异常处理 **/
            handleThrowable(t);
            /** 打印异常 **/
            t.printStackTrace();
            /** 设置程序异常退出 **/
            System.exit(1);
        }
    }

3 安装目录 & 工作目录

在Tomcat运行过程中需要直到两个重要的目录即:tomcat安装目录tomcat工作目录

安装目录:所谓安装目录就是我们下载tomcat后解压到本地目录。

image

工作目录:工作目录泛指一个或多个应用程序运行的工作目录。相对于tomcat安装目录,工作目录只存储配置文件,日志,以及临时文件三个目录。

image

如果是用tomcat脚本启动默认情况下安装目录=工作目录,如果是使用IDEA工作时通常工作目录!=安装目录

image

4 Bootstrap类的初始化

如果想要运行Bootstrap,首先需要使用启动类加载器加载Bootstrap类,并调用<clinit>函数初始化.执行静态方法块。

Bootstrap类逻辑主要从系统属性中获取tomcat安装目录,tomcat工作目录并设置对应系统属性中。

  • 1 获取tomcat安装目录,设置系统属性Globals.CATALINA_HOME_PROP中。

  • 2 获取tomcat工作目录,设置系统属性Globals.CATALINA_BASE_PROP中。

相关源码

/**
     * 初始化Bootstrap类
     */
    static {
        /** 根据user.dir,从系统属性中当前用户目录 **/
        String userDir = System.getProperty("user.dir");

        /** 根据Globals.CATALINA_HOME_PROP键,从系统属性中获取tomcat安装目录路径**/
        String home = System.getProperty(Globals.CATALINA_HOME_PROP);

        /** 判断系统属性获取安装目录路径home对应的文件是否存在,如果存在设置到属性homeFile **/
        File homeFile = null;
        if (home != null) {
            File f = new File(home);
            try {
                /** 获取绝对路径描述的File **/
                homeFile = f.getCanonicalFile();
            } catch (IOException ioe) {
                homeFile = f.getAbsoluteFile();
            }
        }

        /**
         * 尝试以userDir/bootstrap.jar/..目录作为tomcat安装目录文件路径,并判断该路径文件是否存在,如果存在设置到属性homeFile
         * */
        if (homeFile == null) {
            File bootstrapJar = new File(userDir, "bootstrap.jar");

            if (bootstrapJar.exists()) {
                File f = new File(userDir, "..");
                try {
                    /** 获取绝对路径描述的File **/
                    homeFile = f.getCanonicalFile();
                } catch (IOException ioe) {
                    homeFile = f.getAbsoluteFile();
                }
            }
        }

        /**
         * 尝试以userDir目录作为tomcat安装目录文件路径,并判断该路径文件是否存在,如果存在设置到属性homeFile
         **/
        if (homeFile == null) {
            File f = new File(userDir);
            try {
                /** 获取绝对路径描述的File **/
                homeFile = f.getCanonicalFile();
            } catch (IOException ioe) {
                homeFile = f.getAbsoluteFile();
            }
        }

        /** 设置homeFile文件作为tomcat安装目录**/
        catalinaHomeFile = homeFile;

        /** 重新设置系统属性 Globals.CATALINA_HOME_PROP  **/
        System.setProperty(
                Globals.CATALINA_HOME_PROP, catalinaHomeFile.getPath());


        /** 根据Globals.CATALINA_BASE_PROP键,从系统属性中获取tomcat工作目录路径 **/
        /** 如果不存在,则以tomcat安装目录作为tomcat工作目录 **/
        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();
            }
            /** 设置tomcat工作目录**/
            catalinaBaseFile = baseFile;
        }

        /** 重新设置系统属性 CATALINA_BASE_PROP  **/
        System.setProperty(
                Globals.CATALINA_BASE_PROP, catalinaBaseFile.getPath());
    }

5 Bootstrap对象初始化

5.1 类加载器
5.1.1 概述

Java 的类加载,就是把字节码格式“.class”文件加载到 JVM的方法区,并在 JVM 的堆区建立一个java.lang.Class对象的实例,用来封装Java类相关的数据和方法。 Class对象可以把它理解成业务类的模板,JVM根据这个模板来创建具体业务类对象实例。

JVM类加载使用懒加载的机制,也就是说并不是在启动时就把所有的“.class”文件都加载一遍,而是程序在运行过程中用到了这个类才去加载。JVM 类加载是由类加载器来完成的,JDK类加载器的抽象基类为ClassLoader。

在 Java 虚拟机中,类的唯一性是由类加载器实例以及类的全名一同确定的。即便是同一串字节流,经由不同的类加载器加载,也会得到两个不同的类。在大型应用中,我们往往借助这一特性,来运行同一个类的不同版本。

案例

@Test
public void  test() throws Exception {
    /** 使用加载ClassLoaderTest.class的类加载器加载Bootstrap类,返回Bootstrap.Class对象**/
    Class<?> aClass = ClassLoaderTest.class.getClassLoader().loadClass("jvm.Bootstrap");
    /** 通过Class对象获取对应的构造函数 **/
    Constructor<?> constructor = aClass.getConstructor();
    /** 通过反射构造实例化类对象**/
    Bootstrap Bootstrap = (Bootstrap)constructor.newInstance();
    System.out.println(Bootstrap);
}
5.1.2 ClassLoader类结构
image
  • ClassLoader:ClassLoader类加载器的基类,其内部存在一个类型为ClassLoader属性parent,用来实现类加载器之间的父子关系。
  • URLClassLoader:URLClassLoader是ClassLoader子类,其内部存一个类型为URLClassPath属性为ucp,ucp负责管理被当前类加载器加载Class文件资源。
public abstract class ClassLoader {
    ...
    //父类加载器
    private final ClassLoader parent;
    ...
        
        
public class URLClassLoader extends SecureClassLoader implements Closeable {
   private final URLClassPath ucp;
   ...
5.1.3 ClassLoader中核心方法
  • defineClass是个工具方法,它的职责是调用 native 方法把 Java 类的字节码解析成一个 Class 对象,所谓的 native 方法就是由 C 语言实现的方法,Java 通过 JNI 机制调用。

  • findClass 方法的主要职责就是找到“.class”文件,.class”文件可能来自文件系统或者网络,找到后把“.class”文件读到内存中得到字节码数组,然后调用 defineClass 方法得到 Class 对象。

  • loadClass 是个 public 方法,说明它才是对外提供服务的接口,具体实现也比较清晰:首先检查这个类是不是已经被加载过了,如果加载过了直接返回,否则交给父加载器去加载。请你注意,这是一个递归调用,也就是说子加载器持有父加载器的引用,当一个类加载器需要加载一个 Java 类时,会先委托父加载器去加载,然后父加载器在自己的加载路径中搜索 Java 类,当父加载器在自己的加载范围内找不到时,才会交还给子加载器加载,这就是双亲委托机制。

protected final Class<?> defineClass(String name, byte[] b, int  off, int len) throws ClassFormatError{ … }  

protected Class<?> findClass(String name) throws ClassNotFoundException { … }

public synchronized Class<?> loadClass(String name) throws ClassNotFoundException{ … }  
5.1.4 Java 虚拟机中类加载器

在JVM中定义了4类加载器分别为:启动(Bootstrap)类加载器扩展(Extension)类加载器系统(System)类加载器,以及用户自定义加载器

image

启动(Bootstrap)类加载器

引导类加载器是负责加载并管理<JAVA_HOME>/lib 的核心类库 -Xbootclasspath选项指定的jar包class文件对应的Class对象。

启动(Bootstrap)类加载器是扩展(Extension)类加载器的父加载器,是最高等级的加载器,由于启动类加载器涉及到虚拟机本地实现细节,开发者无法直接获取到启动类加载器的引用,所以 不允许直接通过引用进行操作

//从系统属性中获取启动(Bootstrap)类加载器加载并管理核心类库
System.getProperty("sun.boot.class.path")
C:\Program Files\Java\jdk1.8.0_91\jre\lib\resources.jar;
C:\Program Files\Java\jdk1.8.0_91\jre\lib\rt.jar;
C:\Program Files\Java\jdk1.8.0_91\jre\lib\sunrsasign.jar;
C:\Program Files\Java\jdk1.8.0_91\jre\lib\jsse.jar;
C:\Program Files\Java\jdk1.8.0_91\jre\lib\jce.jar;
C:\Program Files\Java\jdk1.8.0_91\jre\lib\charsets.jar;
C:\Program Files\Java\jdk1.8.0_91\jre\lib\jfr.jar;
C:\Program Files\Java\jdk1.8.0_91\jre\classes

扩展(Extension)类加载器

扩展类加载器是由Sun的ExtClassLoader(sun.misc.Launcher$ExtClassLoader)实现的,它负责加载并管理 <JAVA_HOME >/lib/ext或者由系统变量-Djava.ext.dir指定位置中class文件对应的Class对象。

//从系统属性中获取扩展(Extension)类加载器加载并管理核心类库
System.getProperty("java.ext.dirs")
C:\Program Files\Java\jdk1.8.0_91\jre\lib\ext;
C:\windows\Sun\Java\lib\ext

系统(System)类加载器

系统类加载器是由 Sun 的 AppClassLoader(sun.misc.Launcher$AppClassLoader)实现的,它负责加载并管理用户类路径(java -classpath或-Djava.class.path变量所指定的URL资源)中class文件对应的Class对象。开发者可以直接使用系统类加载器。

//从系统属性中获取系统(System)类加载器加载并管理核心类库
System.getProperty("java.class.path")
...省略
D:\project_alibaba\jvm-in-action\target\classes;
...省略

案例

package jvm;
import java.lang.reflect.Constructor;

/**获取JVM类加载器 **/
public class ClassLoaderTest {
    public static void main(String[] args) {
        try {
            System.out.println(ClassLoader.getSystemClassLoader());
            System.out.println(ClassLoader.getSystemClassLoader().getParent());
            System.out.println(ClassLoader.getSystemClassLoader().getParent().getParent());
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
sun.misc.Launcher$AppClassLoader@18b4aac2
sun.misc.Launcher$ExtClassLoader@45ee12a7
null
5.1.5 类加载(双亲委派机制)

首先检查这个类是不是已经被加载过了,如果加载过了直接返回,否则交给父加载器去加载。请你注意,这是一个递归调用,也就是说子加载器持有父加载器的引用,当一个类加载器需要加载一个 Java 类时,会先委托父加载器去加载,然后父加载器在自己的加载路径中搜索 Java 类,当父加载器在自己的加载范围内找不到时,才会交还给子加载器加载,直到最终交给不存在父类加载器的启动(Bootstrap)类加载器加载。

image

实现流程

双亲委派模型对于保证Java程序的稳定运作很重要,它的具体实现在java.lang.ClassLoader类loadClass()方法中。

public Class<?> loadClass(String name) throws ClassNotFoundException {  
    return loadClass(name, false);  
}  

protected synchronized Class<?> loadClass(String name, boolean resolve)  
        throws ClassNotFoundException {  

    // 首先判断该类是否已经被加载  
    Class c = findLoadedClass(name);  
    if (c == null) {  
        //如果没有被加载,就委托给父类加载
        try {  
            if (parent != null) {  
                //如果存在父类加载器,就委派给父类加载器加载,这里是一个递归调用的过程。  
                c = parent.loadClass(name, false);  
            } else {    // 递归终止条件
                // 由于启动类加载器无法被Java程序直接引用,因此默认用 null 替代
                // parent == null就意味着由启动类加载器尝试加载该类,  
                // 即通过调用 native方法 findBootstrapClass0(String name)加载  
                c = findBootstrapClass0(name);  
            }  
        } catch (ClassNotFoundException e) {  
            // 如果父类加载器不能完成加载请求时,再调用自身的findClass方法进行类加载,若加载成功,findClass方法返回的是defineClass方法的返回值
            // 注意,若自身也加载不了,也会产生ClassNotFoundException异常并向上抛出
            c = findClass(name);  
        }  
    }  
    if (resolve) {  
        resolveClass(c);  
    }  
    return c;  
}
//没错!此方法没有具体实现,只是抛了一个异常,而且访问权限是protected。这充分证明了:这个方法就是给开发者重写用的,即自定义类加载器时需实现此方法!
protected Class<?> findClass(String name) throws ClassNotFoundException {
    throw new ClassNotFoundException(name);
}
  • AppClassLoader,ExtClassLoader 对findClass实现都继承自URLClassLoader,

protected Class<?> findClass(final String name)
        throws ClassNotFoundException
    {
        final Class<?> result;
        try {
            result = AccessController.doPrivileged(
                new PrivilegedExceptionAction<Class<?>>() {
                    public Class<?> run() throws ClassNotFoundException {
                        //将类的包路径转换为url资源路径
                        String path = name.replace('.', '/').concat(".class");
                        //通过URLClassLoader URLClassPath ucp中获取url路径对应的二进制数据
                        Resource res = ucp.getResource(path, false);
                        if (res != null) {
                            try {
                                //读取二进制获取调用defineClass获取Class对象象
                                return defineClass(name, res);
                            } catch (IOException e) {
                                throw new ClassNotFoundException(name, e);
                            }
                        } else {
                            return null;
                        }
                    }
                }, acc);
        } catch (java.security.PrivilegedActionException pae) {
            throw (ClassNotFoundException) pae.getException();
        }
        if (result == null) {
            throw new ClassNotFoundException(name);
        }
        return result;
    }

案例

ClassLoaderTest,EventID,Sdp属于被不同类加载器管理的类对象,使用相同系统类加载器加载时,由于采用双亲委派机制,获取Class对象是由不同的类加载器加载。

@Test
    public void classLoaderLoadClass2() throws IOException {
        try {
            //调用加载当前类的类加载器(这里即为系统类加载器)加载ClassLoaderTest
            Class typeLoaded = ClassLoaderTest.class.getClassLoader().loadClass("com.wuhao.jvm.classLoader.ClassLoaderTest");
            //查看被加载的ClassLoaderTest对象是被那个类加载器加载的
            System.out.println(typeLoaded.getClassLoader());

            //调用加载当前类的类加载器(这里即为系统类加载器)加载EventID
            Class typeLoaded1 = ClassLoaderTest.class.getClassLoader().loadClass("com.sun.java.accessibility.util.EventID");
            //查看被加载的ClassLoaderTest对象是被那个类加载器加载的
            System.out.println(typeLoaded1.getClassLoader());

            //调用加载当前类的类加载器(这里即为系统类加载器)加载Sdp
            Class typeLoaded2 = ClassLoaderTest.class.getClassLoader().loadClass("com.oracle.net.Sdp");
            //查看被加载的ClassLoaderTest对象是被那个类加载器加载的
            System.out.println(typeLoaded2.getClassLoader());
        } catch (Exception e) {
        }
    }

模式优点

双亲委派模型很好的解决了各个类加载器的基础类的统一问题(越基础的类由越上层的加载器进行加载).

例如类java.lang.Object,它存在在rt.jar中,无论哪一个类加载器要加载这个类,最终都是委派给处于模型最顶端的启动类加载器进行加载,因此Object类在程序的各种类加载器环境中都是同一个类。
相反,如果没有双亲委派模型而是由各个类加载器自行加载的话,用户编写了一个java.lang.Object的类,并放在程序的ClassPath中,那系统中将会出现多个不同的Object类,程序将混乱。因此,如果开发者尝试编写一个与rt.jar类库中已有类重名的Java类,将会发现可以正常编译,但是永远无法被加载运行。

模式缺点

由于双亲委派模型存在,一个类被谁加载是由类加载器管理资源所决定,即使某个类存在于多个类加载器管理资源中。也只会被更上程的类加载器加载。这样就会导致一个在被上层类加载中Class中,只能获取上程类加载器,而上层类加载器是无法加载下层类加载器中管理Class对象。

线程上下文类加载器

为了解决这个问题JVM引用了线程上下文类加载器

可以通过java.lang.Thread类的setContextClassLoader方法将一个下层的类加载器放置到线程本地变量中。这样就可以从线程的本地变量中获取下程类加载器加载下程类管理Class对象。

如果创建线程时还未设置上下文类加载器,它将会从父线程中继承一个,如果在应用程序的全局范围内都没有设置过多的话,那这个类加载器默认即使用系统程序类加载器。

如何破坏双亲委派模型

双亲委派模型并不是一个强制性的约束模型,而是Java设计者推荐给开发者的类加载器的实现方式。大多数的类加载器都遵循这个模型,双亲委派模型的具体逻辑实现在ClassLoader的loadClass方法,如果我们自定义类加载器,采用双亲委派模型:只需要重写ClassLoader的findClass()方法即可,破坏双亲委派模型:重写ClassLoader的整个loadClass()方法(因为双亲委派模型的逻辑主要实现就在此方法中,若我们重写即可破坏掉。)

5.2 tomcat中类加载器
5.2.1 Tomcat 如果使用默认的类加载机制行不行

Tomcat是个web容器,那么它要解决什么问题

  • 一个web容器可能需要部署多个个应用程序,不同的应用程序可能会依赖同一个第三方类库的不同版本,不能要求同一个类库在同一个服务器只有一份,因此要保证每个应用程序的类库都是独立的,保证相互隔离。
  • 部署在同一个web容器中相同的类库相同的版本可以共享给所有的应用程序。节省资源。
  • web容器也有自己依赖的类库,不能于应用程序的类库混淆。基于安全考虑,应该让容器的类库和程序的类库隔离开来。
  • web容器要支持jsp的修改,我们知道,jsp 文件最终也是要编译成class文件才能在虚拟机中运行,但程序运行后修改jsp已经是司空见惯的事情,否则要你何用? 所以,web容器需要支持 jsp 修改后不用重启。

Tomcat 如果使用默认的类加载机制是无法解决上面的问题,

  • 对于第一个问题,如果使用默认的类加载器机制,那么是无法加载两个相同类库的不同版本的,默认的累加器是不管你是什么版本的,只在乎你的全限定类名,并且只有一份。
  • 第三个问题和第一个问题一样。
  • 第四个问题,我们想我们要怎么实现jsp文件的热部署。jsp 文件其实也就是class文件,那么如果修改了,但类名还是一样,类加载器会直接取方法区中已经存在的,修改后的jsp是不会重新加载的。我们只能将jsp文件Class对象从类加载器中卸载,在重新加载。
5.2.2 Tomcat类加载器
image
  • commonLoader: Tomcat 通用类加载器, 加载的资源可被 Tomcat 和 所有的 Web 应用程序共同获取
  • catalinaLoader: Tomcat 类加载器, 加载的资源只能被 Tomcat 获取
  • sharedLoader: Tomcat 各个Context的父加载器, 这个类是所有 WebappClassLoader 的父类, sharedLoader 所加载的类将被所有的 WebappClassLoader 共享获取
  • WebappClassLoader: 每个Context 对应一个 WebappClassloader, 主要用于加载 WEB-INF/lib 与 WEB-INF/classes 下面的资源
5.3 初始化bootstrap

初始化bootstrap本质只做了2件事情:

1 调用initClassLoaders构造Tomcat自定义的类加载器commonLoader,catalinaLoader,sharedLoader。

2 实例化catalina实例,并设置其父类加载器为sharedLoader,并将其引用绑定到bootstrap对应的catalinaDaemon属性上。


/**
 * catalina实例
 */
private Object catalinaDaemon = null;

/**
 * Common类加载器,负责加载Tomcat和Web应用都复用的类
 */
ClassLoader commonLoader = null;

/**
 * Catalina类加载器,负责加载Tomcat专用的类,而这些被加载的类在Web应用中将不可见
 */
ClassLoader catalinaLoader = null;

/**
 * Shared类加载器,负责加载Tomcat下所有的Web应用程序都复用的类,而这些被加载的类在Tomcat中将不可见
 */
ClassLoader sharedLoader = null;

 **
 * 初始化bootstrap
 */
public void init() throws Exception {
    /** 非常关键的地方,初始化类加载器 **/
    initClassLoaders();

    /** 设置上下文类加载器为catalinaLoader,  **/
    Thread.currentThread().setContextClassLoader(catalinaLoader);

    /**  使用catalinaLoader加载tomcat源代码里面的各个专用类 **/
    SecurityClassLoad.securityClassLoad(catalinaLoader);

    if (log.isDebugEnabled())
        log.debug("Loading startup class");

    /** 使用catalinaLoader类加载类获取Catalina Class对象,通过反射实例化Catalina **/
    Class<?> startupClass = catalinaLoader.loadClass("org.apache.catalina.startup.Catalina");
    Object startupInstance = startupClass.getConstructor().newInstance();

    /** 使用反射调用catalina中setParentClassLoader方法。设置其父类加载器为sharedLoader **/ 
    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);

    /** 设置catalina实例 **/
    catalinaDaemon = startupInstance;
}
5.3.1 实例化tomcat类加载器

Bootstrap的初始化中实例化的tomcat类加载器主要包括commonLoader,catalinaLoader,sharedLoader,其本质实现类为URLClassLoader。其核心思想是“根据类加载器的名称从读取/conf/catalina.properties中读取不同类加载加载class文件资源路径,转为为URL数组,最终来实例化为URLClassLoader”

源码

initClassLoaders方法中构造类加载器被封装到createClassLoader方法中。第一个参数用来作为需要构造类加载器的名称,第二个参数用来作为构造加载器的父类加载器。

    private void initClassLoaders() {
        try {
            /** 实例化commonLoader,如果未创建成果的话,则使用应用程序类加载器作为commonLoader **/
            commonLoader = createClassLoader("common", null);
            if( commonLoader == null ) {
                commonLoader=this.getClass().getClassLoader();
            }
            /** 实例化catalinaLoader,其父加载器为commonLoader  **/
            catalinaLoader = createClassLoader("server", commonLoader);
            /** 实例化sharedLoader,其父加载器为commonLoader  **/
            sharedLoader = createClassLoader("shared", commonLoader);
        } catch (Throwable t) {
            handleThrowable(t);
            log.error("Class loader creation threw exception", t);
            System.exit(1);
        }
    }

createClassLoader方法中根据类加载器的名称从读取/conf/catalina.properties中读取不同类加载加载class文件资源路径,

  • 将读取的资源封装成Repository类的对象。Repository用来表示一个资源。其内部存在2个属性location,location。分别表示资源的路径和资源的类型

  • 调用ClassLoaderFactory.createClassLoader创建一个ClassLoader

/**
     * 按照名称创建不同tomcat 类加载器
     * @param name
     * @param parent
     * @return
     * @throws Exception
     */
    private ClassLoader createClassLoader(String name, ClassLoader parent)
        throws Exception {

        /** 根据类加载器的名称从CatalinaProperties配置中读取不同类加载加载class文件资源路径,
         *
         * CatalinaProperties配置来源于tomcat工作目录/conf/catalina.properties
         * 默认配置如下
         * common.loader="${catalina.base}/lib","${catalina.base}/lib/*.jar","${catalina.home}/lib","${catalina.home}/lib/*.jar"
         * server.loader=
         * shared.loader=
         */
        String value = CatalinaProperties.getProperty(name + ".loader");
        if ((value == null) || (value.equals("")))
            return parent;

        /** 替换资源路径字符串中属性遍历占位符,比如:${catalina.base}、${catalina.home}  **/
        value = replace(value);

        /**
         * 定义一个Repository类型的列表,
         * Repository类用来表示一个资源。其内部存在2个属性location,location。分别表示资源的路径和资源的类型
         **/
        List<Repository> repositories = new ArrayList<>();

        /** 读取配置中的资源按,分隔字符数组**/
        String[] repositoryPaths = getPaths(value);

        /**  遍历repositoryPaths  **/
        for (String repository : repositoryPaths) {
            /** 检查资源路径是否为URL **/
            try {
                @SuppressWarnings("unused")
                URL url = new URL(repository);
                /** 创建一个Repository,类型为URL添加到repositories **/
                repositories.add(
                        new Repository(repository, RepositoryType.URL));
                continue;
            } catch (MalformedURLException e) {
                // Ignore
            }

            /** 判断资源是否为某个目录下所有*.jar文件 **/
            if (repository.endsWith("*.jar")) {
                repository = repository.substring
                    (0, repository.length() - "*.jar".length());

                /** 创建一个Repository,类型为GLOB添加到repositories **/
                repositories.add(
                        new Repository(repository, RepositoryType.GLOB));
            }
            /** 判断资源是否为某个目录下.jar文件 **/
            else if (repository.endsWith(".jar")) {
                /** 创建一个Repository,类型为JAR添加到repositories **/
                repositories.add(
                        new Repository(repository, RepositoryType.JAR));
            }
            /** 判断资源是否目录 **/
            else {
                /** 创建一个Repository,类型为目录添加到repositories **/
                repositories.add(
                        new Repository(repository, RepositoryType.DIR));
            }
        }

        //创建一个ClassLoader
        return ClassLoaderFactory.createClassLoader(repositories, parent);
    }

    public enum RepositoryType {
        //目录
        DIR,
        //目录下*.jar
        GLOB,
        //单个jar
        JAR,
        //URL
        URL
    }

    public static class Repository {
        /**
         *  资源路径
         */
        private final String location;
        /**
         *  资源类型
         */
        private final RepositoryType type;

        public Repository(String location, RepositoryType type) {
            this.location = location;
            this.type = type;
        }

        public String getLocation() {
            return location;
        }

        public RepositoryType getType() {
            return type;
        }
    }    

createClassLoader方法中遍历列表中Repository,将其转换为URL,添加到定义URL集合中。最后将URL集合转化为数组,构造URLClassLoader。

public static ClassLoader createClassLoader(List<Repository> repositories,
                                                final ClassLoader parent)
        throws Exception {

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

        /** 定义个URL集合,用来作为构造URLClassLoader时需要URL集合参数 **/
        Set<URL> set = new LinkedHashSet<>();


        /** 读取遍历repositories,将资源转换为URL放入set集合中 **/
        if (repositories != null) {
            for (Repository repository : repositories)  {
                if (repository.getType() == RepositoryType.URL) {
                    /** 通过指定资源路径构造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 url = buildClassLoaderUrl(directory);
                    if (log.isDebugEnabled())
                        log.debug("  Including directory " + url);

                    set.add(url);
                } else if (repository.getType() == RepositoryType.JAR) {
                    File file=new File(repository.getLocation());
                    file = file.getCanonicalFile();
                    /** 校验jar文件资源 **/
                    if (!validateFile(file, RepositoryType.JAR)) {
                        continue;
                    }
                    /**  获取jar文件URL **/
                    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();
                    /** 校验jar文件资源 **/
                    if (!validateFile(directory, RepositoryType.GLOB)) {
                        continue;
                    }
                    if (log.isDebugEnabled())
                        log.debug("  Including directory glob "
                            + directory.getAbsolutePath());

                    String filenames[] = directory.list();
                    if (filenames == null) {
                        continue;
                    }
                    /** 遍历目录中文件,找到jar文件,添加到set中 **/
                    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);
                    }
                }
            }
        }

        /** 将集合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]);
            }

        /** 创建URLClassLoader **/
        return AccessController.doPrivileged(
                new PrivilegedAction<URLClassLoader>() {
                    @Override
                    public URLClassLoader run() {
                        if (parent == null)
                            return new URLClassLoader(array);
                        else
                            return new URLClassLoader(array, parent);
                    }
                });
    }
5.3.2 设置上下文类加载器
/** 设置上下文类加载器为catalinaLoader,  **/
Thread.currentThread().setContextClassLoader(catalinaLoader);
5.3.3 用catalinaLoader加载tomcat源代码里面的各个专用类
/**  使用catalinaLoader加载tomcat源代码里面的各个专用类 **/
SecurityClassLoad.securityClassLoad(catalinaLoader);

public static void securityClassLoad(ClassLoader loader) throws Exception {
        securityClassLoad(loader, true);
    }

    static void securityClassLoad(ClassLoader loader, boolean requireSecurityManager) throws Exception {

        if (requireSecurityManager && System.getSecurityManager() == null) {
            return;
        }

        loadCorePackage(loader);
        loadCoyotePackage(loader);
        loadLoaderPackage(loader);
        loadRealmPackage(loader);
        loadServletsPackage(loader);
        loadSessionPackage(loader);
        loadUtilPackage(loader);
        loadValvesPackage(loader);
        loadJavaxPackage(loader);
        loadConnectorPackage(loader);
        loadTomcatPackage(loader);
    }

    private static final void loadCorePackage(ClassLoader loader) throws Exception {
        final String basePackage = "org.apache.catalina.core.";
        loader.loadClass(basePackage + "AccessLogAdapter");
        loader.loadClass(basePackage + "ApplicationContextFacade$PrivilegedExecuteMethod");
        loader.loadClass(basePackage + "ApplicationDispatcher$PrivilegedForward");
        loader.loadClass(basePackage + "ApplicationDispatcher$PrivilegedInclude");
        loader.loadClass(basePackage + "ApplicationPushBuilder");
        loader.loadClass(basePackage + "AsyncContextImpl");
        loader.loadClass(basePackage + "AsyncContextImpl$AsyncRunnable");
        loader.loadClass(basePackage + "AsyncContextImpl$DebugException");
        loader.loadClass(basePackage + "AsyncListenerWrapper");
        loader.loadClass(basePackage + "ContainerBase$PrivilegedAddChild");
        loadAnonymousInnerClasses(loader, basePackage + "DefaultInstanceManager");
        loader.loadClass(basePackage + "DefaultInstanceManager$AnnotationCacheEntry");
        loader.loadClass(basePackage + "DefaultInstanceManager$AnnotationCacheEntryType");
        loader.loadClass(basePackage + "ApplicationHttpRequest$AttributeNamesEnumerator");
    }

    private static final void loadLoaderPackage(ClassLoader loader) throws Exception {
        final String basePackage = "org.apache.catalina.loader.";
        loader.loadClass(basePackage + "WebappClassLoaderBase$PrivilegedFindClassByName");
        loader.loadClass(basePackage + "WebappClassLoaderBase$PrivilegedHasLoggingConfig");
    }
5.3.4 实例化catalina并设置parentClassLoader
/** 使用catalinaLoader类加载类获取Catalina Class对象,通过反射实例化Catalina **/
    Class<?> startupClass = catalinaLoader.loadClass("org.apache.catalina.startup.Catalina");
    Object startupInstance = startupClass.getConstructor().newInstance();

    /** 使用反射调用catalina中setParentClassLoader方法。设置其父类加载器为sharedLoader **/ 
    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);

6 Bootstrap加载

 /**
     * 加载Tomcat
     * 内部反射调用catalina.load方法
     */
    private void load(String[] arguments)
        throws Exception {

        // Call the load() method
        String methodName = "load";
        Object param[];
        Class<?> paramTypes[];
        if (arguments==null || arguments.length==0) {
            paramTypes = null;
            param = null;
        } else {
            paramTypes = new Class[1];
            paramTypes[0] = arguments.getClass();
            param = new Object[1];
            param[0] = arguments;
        }
        Method method =
            catalinaDaemon.getClass().getMethod(methodName, paramTypes);
        if (log.isDebugEnabled())
            log.debug("Calling startup class " + method);
        method.invoke(catalinaDaemon, param);

    }

7 Bootstrap启动

    /**
     * 启动Bootstrap
     * 内部调用catalina.start()
     */
    public void start()
        throws Exception {
        if( catalinaDaemon==null ) init();

        Method method = catalinaDaemon.getClass().getMethod("start", (Class [] )null);
        method.invoke(catalinaDaemon, (Object [])null);
    }

7 Bootstrap停止

    /**
     * 停止 Bootstrap
     * 内部调用catalina.stop()
     */
    public void stop()
        throws Exception {

        Method method = catalinaDaemon.getClass().getMethod("stop", (Class [] ) null);
        method.invoke(catalinaDaemon, (Object [] ) null);

    }

8 Bootstrap设置await

设置当前命令执行执行的线程是否要阻塞,对于start指令,当前线程是tomcat主线程,我们当然需要阻塞主线程,保证tomcat的运行。而主线程被释放则表示tomcat被停止。

public void setAwait(boolean await)
        throws Exception {

        Class<?> paramTypes[] = new Class[1];
        paramTypes[0] = Boolean.TYPE;
        Object paramValues[] = new Object[1];
        paramValues[0] = Boolean.valueOf(await);
        Method method =
            catalinaDaemon.getClass().getMethod("setAwait", paramTypes);
        method.invoke(catalinaDaemon, paramValues);

    }

9 总结时刻

Tomcat作为一个Java程序,以Bootstrap类中main函数为入口

  • 1 首先会调用Bootstrap的init初始化函数。实例化的tomcat类加载器,主要包括commonLoader,catalinaLoader,sharedLoader,其本质是URLClassLoader。其核心思想是“根据类加载器的名称从读取/conf/catalina.properties中读取不同类加载加载class文件资源路径,转为为URL数组,来实例化成URLClassLoader”

  • 2 实例化catalina对象。

  • 3 以main函数args参数作为指令调用Catalina类中不同的方法,完成相应的功能。

image
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,117评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,328评论 1 293
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,839评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,007评论 0 206
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,384评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,629评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,880评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,593评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,313评论 1 243
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,575评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,066评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,392评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,052评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,082评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,844评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,662评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,575评论 2 270

推荐阅读更多精彩内容