三. Tomcat源码分析-类加载和类加载器

三. Tomcat源码分析-类加载和类加载器

1. 使用maven构建tomcat源码

1. 下载源码

https://tomcat.apache.org/download-80.cgi

2. 新建catalina-home目录

解压、新建catalina-home目录,同时将目录中的conf和webapps文件夹复制到catalina-home目录中

源码原始目录

三. Tomcat源码分析-类加载和类加载器_第1张图片

新建后

三. Tomcat源码分析-类加载和类加载器_第2张图片

3. 添加pom.xml

需要通过Maven组织文件,因此需要在根目录下创建目录中新建pom.xml文件:


<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">

    <modelVersion>4.0.0modelVersion>
    <groupId>org.apache.tomcatgroupId>
    <artifactId>Tomcat8.5artifactId>
    <name>Tomcat8.5name>
    <version>8.5version>
 
    <build>
        <finalName>Tomcat8.5finalName>
        <sourceDirectory>javasourceDirectory>
        <testSourceDirectory>testtestSourceDirectory>
        <resources>
            <resource>
                <directory>javadirectory>
            resource>
        resources>
        <testResources>
            <testResource>
                <directory>testdirectory>
            testResource>
        testResources>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.pluginsgroupId>
                <artifactId>maven-compiler-pluginartifactId>
                <version>2.3version>
                <configuration>
                    <encoding>UTF-8encoding>
                    <source>1.8source>
                    <target>1.8target>
                configuration>
            plugin>
        plugins>
    build>

    <dependencies>
        <dependency>
            <groupId>junitgroupId>
            <artifactId>junitartifactId>
            <version>4.12version>
            <scope>testscope>
        dependency>
        <dependency>
            <groupId>org.easymockgroupId>
            <artifactId>easymockartifactId>
            <version>3.4version>
        dependency>
        <dependency>
            <groupId>antgroupId>
            <artifactId>antartifactId>
            <version>1.7.0version>
        dependency>
        <dependency>
            <groupId>wsdl4jgroupId>
            <artifactId>wsdl4jartifactId>
            <version>1.6.2version>
        dependency>
        <dependency>
            <groupId>javax.xmlgroupId>
            <artifactId>jaxrpcartifactId>
            <version>1.1version>
        dependency>
        <dependency>
            <groupId>org.eclipse.jdt.core.compilergroupId>
            <artifactId>ecjartifactId>
            <version>4.5.1version>
        dependency>

    dependencies>
project>

如果测试代码报错,注释即可, 例如 : util.TestCookieFilter

4. 启动Tomcat

tomcat启动类: org.apache.catalina.startup.Bootstrap

添加VM options

-Dcatalina.home=catalina-home
-Dcatalina.base=catalina-home
-Djava.endorsed.dirs=catalina-home/endorsed
-Djava.io.tmpdir=catalina-home/temp
-Djava.util.logging.manager=org.apache.juli.ClassLoaderLogManager
-Djava.util.logging.config.file=catalina-home/conf/logging.properties

启动报错

E:\idea-workspace\tomcat\apache-tomcat-8.5.42-src\.idea\ant.xml
加载项目配置失败: cannot parse file E:\idea-workspace\tomcat\apache-tomcat-8.5.42-src\.idea\ant.xml: Unexpected End-of-input in prolog
 at [row,col {unknown-source}]: [1,1]

我的原因是没有使用jdk8编译, 修改后可以启动了

访问 http://localhost:8080 报错

三. Tomcat源码分析-类加载和类加载器_第3张图片

原因是我们直接启动org.apache.catalina.startup.Bootstrap的时候没有加载org.apache.jasper.servlet.JasperInitializer,从而无法编译JSP。

解决办法是在tomcat的源码org.apache.catalina.startup.ContextConfig中的configureStart函数中手动将JSP解析器初始化:

添加代码:context.addServletContainerInitializer(new JasperInitializer(), null);

添加到这个方法org.apache.catalina.startup.ContextConfig#configureStart

三. Tomcat源码分析-类加载和类加载器_第4张图片

再次访问

三. Tomcat源码分析-类加载和类加载器_第5张图片

5. Tomcat 源码目录

三. Tomcat源码分析-类加载和类加载器_第6张图片

catalina 目录

​ catalina 包含所有的 Servlet 容器实现,以及涉及到安全、会话、集群、部署管理 Servlet 容器的各个方面,同时,它还包含了启动入口

coyote 目录

coyote 是 Tomcat 链接器框架的名称,是 Tomcat 服务器提供的客户端访问的外部接口,客户端通过 Coyote 与服务器建立链接、发送请求并接收响应。

El 目录 提供 java 表达式语言

Jasper 模块 提供 JSP 引擎

Naming 模块 提供 JNDI 的服务

Juli 提供服务器日志的服务

tomcat 提供外部调用的接口 api

2. Tomcat 面临的挑战

Tomcat 上可以部署多个项目

Tomcat 的一般部署,可以通过多种方式启动一个 Tomcat 部署多个项目,那么 Tomcat 在设计时会遇到什么挑战呢?

  1. Tomcat 运行时需要加载哪些类

  2. Tomcat 中的多个项目可能存在相同的类

Tomcat 中面临类加载的挑战

3. 类加载与类加载器

1. 类加载

类加载: 主要是将.class 文件中的二进制字节读入到 JVM 中

我们可以看到因为这个定义,所以并没有规定一定是要磁盘加载文件,可以通过网络,内存什么的加载。只要是二进制流字节数据,JVM 就认。

类加载过程:

​ 1. 通过类的全限定名获取该类的二进制字节流

​ 2. 将字节流所代表的静态结构转化为方法区的运行时数据结构

​ 3. 在内存中生成一个该类的 java.lang.Class 对象,作为方法区这个类的各种数据的访问入口

2. 类加载器

**定义:**JVM 设计者把“类加载”这个动作放到 java 虚拟机外部去实现,以便让应用程序决定如何获取所需要的类。实现这个动作的代码模块成为“类加载器”

类与类加载器

对于任何一个类,都需要由加载它的类加载器和这个类来确定其在 JVM 中的唯一性。也就是说,两个类来源于同一个 Class 文件,并且被同一个类加载器加载,这两个类才相等。

注意:这里所谓的“相等”,一般使用 instanceof 关键字做判断。

3. 类加载器与双亲委派模型

启动类加载器:该加载器使用 C++语言实现,属于虚拟机自身的一部分。

启动类加载器(Bootstrap ClassLoader):负责加载 JAVA_HOME\lib 目录中并且能被虚拟机识别的类库加载到 JVM 内存中,如果名称不符合的类库即使在 lib 目录中也不会被加载。该类加载器无法被 java 程序直接引用

拓展类加载器与应用程序类加载器:另一部分就是所有其它的类加载器,这些类加载器是由 Java 语言实现,独立于 JVM 外部,并且全部继承抽象类 java.lang.ClassLoader。

扩展类加载器(Extension ClassLoader):该加载器主要负责加载 JAVA_HOME\lib\ext 目录中的类库,开发者可以使用扩展加载器。

应用程序类加载器(Application ClassLoader):该列加载器也称为系统加载器,它负责加载用户类路径(Classpath)上所指定的类库,开发者可以直接使 用该类加载器,如果应用程序中没有自定义过自己的类加载器,一般情况下这个就是程序中默认的类加载器

双亲委派

2. JVM 中的类加载器源码分析

sun.misc.Launcher.AppClassLoader

AppClassLoader是Launcher的一个静态内部类

核心代码

static class AppClassLoader extends URLClassLoader {
    final URLClassPath ucp = SharedSecrets.getJavaNetAccess().getURLClassPath(this);

    public static ClassLoader getAppClassLoader(final ClassLoader var0) throws IOException {
        final String var1 = System.getProperty("java.class.path");
        final File[] var2 = var1 == null ? new File[0] : Launcher.getClassPath(var1);
        return (ClassLoader)AccessController.doPrivileged(new PrivilegedAction<Launcher.AppClassLoader>() {
            public Launcher.AppClassLoader run() {
                URL[] var1x = var1 == null ? new URL[0] : Launcher.pathToURLs(var2);
                return new Launcher.AppClassLoader(var1x, var0);
            }
        });
    }

    AppClassLoader(URL[] var1, ClassLoader var2) {
        super(var1, var2, Launcher.factory);
        this.ucp.initLookupCache(this);
    }

    // loadClass是核心
    public Class<?> loadClass(String var1, boolean var2) throws ClassNotFoundException {
        int var3 = var1.lastIndexOf(46);
        if (var3 != -1) {
            SecurityManager var4 = System.getSecurityManager();
            if (var4 != null) {
                var4.checkPackageAccess(var1.substring(0, var3));
            }
        }

        if (this.ucp.knownToNotExist(var1)) {
            Class var5 = this.findLoadedClass(var1);
            if (var5 != null) {
                if (var2) {
                    this.resolveClass(var5);
                }

                return var5;
            } else {
                throw new ClassNotFoundException(var1);
            }
        } else {
            return super.loadClass(var1, var2); // 调用父类的loadClass
        }
    }

    static {
        ClassLoader.registerAsParallelCapable();
    }
}

注意这个super.loadClass(var1, var2)不是调用 java.net.FactoryURLClassLoader#loadClass

public final Class<?> loadClass(String name, boolean resolve)
    throws ClassNotFoundException
{
    // First check if we have permission to access the package. This
    // should go away once we've added support for exported packages.
    SecurityManager sm = System.getSecurityManager();
    if (sm != null) {
        int i = name.lastIndexOf('.');
        if (i != -1) {
            sm.checkPackageAccess(name.substring(0, i));
        }
    }
    return super.loadClass(name, resolve);
}
// 虽然这个方法也在URLClassLoader类文件里面, 但是URLClassLoader和FactoryURLClassLoader只是被雪写在了统一
// .class文件里面的类FactoryURLClassLoader是URLClassLoader的子类, 很明显它也调用了super.loadClass(name, resolve);

所以我们要看的super.loadClass(name, resolve);是这个方法

java.lang.ClassLoader#loadClass(java.lang.String, boolean)

protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
    synchronized (getClassLoadingLock(name)) {
        // First, check if the class has already been loaded
        Class<?> c = findLoadedClass(name);
        if (c == null) {
            long t0 = System.nanoTime();
            try {
                // 委派过程
                if (parent != null) { // 如果有父加载器
                    // (注意和父类区分,父类是静态代码概念, 父实例是一种属性的组合关系)实例
                    c = parent.loadClass(name, false);
                } else { // 如果没有父加载器的实例, 就使用启动类加载器(c++实现, jvm的一部分), 
                    // 所以默认每个类肯定会有一个父加载器实例
                    c = findBootstrapClassOrNull(name);
                }
            } catch (ClassNotFoundException e) {
                // 没有打印任何, 因为有些类加载不到很正常, 避免不必要的干扰
                // ClassNotFoundException thrown if class not found
                // from the non-null parent class loader
            }

            if (c == null) { // 委派加载失败, 只能自己想办法加载了
                // If still not found, then invoke findClass in order
                // to find the class.
                long t1 = System.nanoTime();
                c = findClass(name); // 这是一个钩子方法, 如果你重写了这个方法,回调自己的类实现

                // this is the defining class loader; record the stats
                sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
                sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
                sun.misc.PerfCounter.getFindClasses().increment();
            }
        }
        if (resolve) {
            resolveClass(c); // 解析
        }
        return c;
    }
}

很显然, sun.misc.Launcher.AppClassLoader自己重写findClass(name);

所以会调用 java.net.URLClassLoader#findClass

protected Class<?> findClass(final String name)
    throws ClassNotFoundException
{
    final Class<?> result;
    try {
        result = AccessController.doPrivileged(
            new PrivilegedExceptionAction<Class<?>>() {
                public Class<?> run() throws ClassNotFoundException {
                    String path = name.replace('.', '/').concat(".class");
                    Resource res = ucp.getResource(path, false);
                    if (res != null) {
                        try {
                            return defineClass(name, res); // 将字节流转换成方法区运行时数据结构
                            // jvm中有了class对象了
                        } 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;
}

private Class<?> defineClass(String name, Resource res) throws IOException {
    long t0 = System.nanoTime();
    int i = name.lastIndexOf('.');
    URL url = res.getCodeSourceURL();
    if (i != -1) {
        String pkgname = name.substring(0, i);
        // Check if package already loaded.
        Manifest man = res.getManifest();
        definePackageInternal(pkgname, man, url);
    }
    // Now read the class bytes and define the class
    java.nio.ByteBuffer bb = res.getByteBuffer();
    if (bb != null) {
        // Use (direct) ByteBuffer:
        CodeSigner[] signers = res.getCodeSigners();
        CodeSource cs = new CodeSource(url, signers);
        sun.misc.PerfCounter.getReadClassBytesTime().addElapsedTimeFrom(t0);
        return defineClass(name, bb, cs); // 这里
    } else {
        byte[] b = res.getBytes();
        // must read certificates AFTER reading bytes.
        CodeSigner[] signers = res.getCodeSigners();
        CodeSource cs = new CodeSource(url, signers);
        sun.misc.PerfCounter.getReadClassBytesTime().addElapsedTimeFrom(t0);
        return defineClass(name, b, 0, b.length, cs); // 这里
    }
}

看看URLClassLoader注释:jar和目录

三. Tomcat源码分析-类加载和类加载器_第7张图片

SecureClassLoader主要实现了defineClass功能

java.security.SecureClassLoader#defineClass(java.lang.String, byte[], int, int, java.security.CodeSource)

protected final Class<?> defineClass(String name, byte[] b, int off, int len,
                                     ProtectionDomain protectionDomain)
    throws ClassFormatError
{
    protectionDomain = preDefineClass(name, protectionDomain);
    String source = defineClassSourceLocation(protectionDomain);
    Class<?> c = defineClass1(name, b, off, len, protectionDomain, source);
    postDefineClass(c, protectionDomain);
    return c;
}

3. Tomcat 中的类加载解决方案

1. Tomcat 类加载的考虑

隔离性

Web 应用类库相互隔离,避免依赖库或者应用包相互影响,比如有两个 Web 应用,一个采用了 Spring 4,一个采用了 Spring 5,而如果如果采用同一个类加载器,那么 Web 应用将会由于 jar 包覆盖而无法启动成功。

这个地方每个人理解不同,可能就很难想象出来为什么会有问题, 我们很容易理解成应用类加载器不就能解决这个问题吗

假设app1和app2, 有两个版本的两个类com.app.A

如果我们使用双亲委派, 那么最终肯定是app1的应用类加载器加载app1应用下的,app2的应用类加载器加载app2应用下的

这句话看似没有毛病, 其实有好几处问题

  1. jvm是一个, 应用类加载器也只有一个

我们所谓的app1应用加载器和app2的应用加载器在同一个JVM中本来就是一个东西(即: sun.misc.Launcher.AppClassLoader的实例)

  1. 当app1先加载了类app1项目下的com.app.A, 那么com.app.A.class已经这放入到jvm的方法区(运行时数据结构),所以app2通过应用类加载和全限定名判断, 不在加载, 直接从jvm的方法区中获取到class了

通过分析, 发现问题就在于我们要让"app1的类加载器"和"app2的类加载器"变成jvm中的两个类加载器, 并且不在委派给AppClassLoader加载即可, Tomcat就是这么干的

灵活性

因为隔离性,所以 Web 应用之间的类加载器相互独立,如果一个 Web 应用重新部署时,该应用的类加载器重新加载,同时不会影响其他 web 应用。

比如:不需要重启 Tomcat 的创建 xml 文件的类加载,

还有 context 元素中的 reloadable 字段:如果设置为 true 的话,Tomcat 将检测该项目是否变更,当检测到变更时,自动重新加载 Web 应用。

性能

由于每一个 Web 应用都有一个类加载器,所以在 Web 应用在加载类时,不会搜索其他 Web 应用包含的 Jar 包,性能自然高于只有一个类加载的情况。

这些和热部署很类似

2. Tomcat 中的类加载器

Tomcat 提供 3 个基础类加载器(common、catalina、shared)和 Web 应用类加载器。

三. Tomcat源码分析-类加载和类加载器_第8张图片

4. Tomcat 中的类加载源码分析

1. 三个基础类加载器

3 个基础类加载器的加载路径在 catalina.properties 配置,默认情况下,3 个基础类加载器的实例都是一个。 createClassLoader 调用 ClassLoaderFactory 属于一种工厂模式,并且都是使用 URLClassLoader

apache-tomcat-8.5.42-src\conf\catalina.properties

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

org.apache.catalina.startup.Bootstrap#initClassLoaders

//初始化三个类加载器以及确定父子关系
private void initClassLoaders() {
    try {
        // commonLoader的加载路径为common.loader
        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();
        }
        // 加载路径为server.loader,默认为空,父类加载器为commonLoader
        catalinaLoader = createClassLoader("server", commonLoader);
        // 加载路径为shared.loader,默认为空,父类加载器为commonLoader
        sharedLoader = createClassLoader("shared", commonLoader);
    } catch (Throwable t) {
        handleThrowable(t);
        log.error("Class loader creation threw exception", t);
        System.exit(1);
    }
}

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

        String value = CatalinaProperties.getProperty(name + ".loader"); // 加后缀
        // catalinaLoader与sharedLoader的加载路径均为空,所以直接返回commonLoader对象,默认3者为同一个对象
        if ((value == null) || (value.equals("")))
            return parent; // 如果是common进来,parent==null但是 vale不为空,所以不会进来 

        value = replace(value);

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

        String[] repositoryPaths = getPaths(value); // 获取存储地址

        for (String repository : repositoryPaths) {
            // 检查一个jar URL仓库
            try {
                @SuppressWarnings("unused")
                URL url = new URL(repository);
                repositories.add(
                        new Repository(repository, RepositoryType.URL));
                continue;
            } catch (MalformedURLException e) {
                // Ignore
            }

            // 本地仓库
            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));
            }
        }

        // 使用ClassLoaderFactory工厂模式创建
        return ClassLoaderFactory.createClassLoader(repositories, parent);
    }

默认情况三个是一个实例,但是可以通过修改配置创建 3 个不同的类加载机制,使它们各司其职。

举个例子:如果我们不想实现自己的会话存储方案,并且这个方案依赖了一些第三方包,我们不希望这些包对 Web 应用可见,因此我们可以配置 server.loader,创建独立的 Catalina 类加载器。

共享性:

Tomcat 通过 Common 类加载器实现了 Jar 包在应用服务器与 Web 应用之间的共享(自然也包含web应用之间的共享)

通过 Shared 类加载器实现了 Jar 包在 Web 应用之间的共享

通过 Catalina 类加载器加载服务器依赖的类。

public void init() throws Exception {
    System.out.println("Bootsrap--init()");
    //类加载器初始化
    initClassLoaders();

    //设置线程上下文类加载器来解决有可能的ClassNotFoundException问题
    Thread.currentThread().setContextClassLoader(catalinaLoader);

    SecurityClassLoad.securityClassLoad(catalinaLoader);

    // 加载启动类Catalina并调用其process()方法
    if (log.isDebugEnabled())
        log.debug("Loading startup class");
    // catalinaLoader去加载catalina
    Class<?> startupClass = catalinaLoader.loadClass("org.apache.catalina.startup.Catalina");
    Object startupInstance = startupClass.getConstructor().newInstance();

    // 设置共享扩展类加载器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];
    //这里把shared加载器传递给catalina
    paramValues[0] = sharedLoader;
    Method method =
            startupInstance.getClass().getMethod(methodName, paramTypes);
    method.invoke(startupInstance, paramValues); // 调用这个方法 setParentClassLoader

    catalinaDaemon = startupInstance;

}

2. 类加载工厂

因为类加载需要做很多事情,比如读取字节数组(加载)、验证、解析、初始化等。而 Java 提供的 URLClassLoader 类能够方便的将 Jar、Class 或者网络资源加 载到内存中。而 Tomcat 中则用一个工厂类,ClassLoaderFactory 把创建类加载器的细节进行封装,可以通过它方便的创建自定义类加载器

private void initClassLoaders() {
    try {
        // commonLoader的加载路径为common.loader
        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();
        }
        // 加载路径为server.loader,默认为空,父类加载器为commonLoader
        catalinaLoader = createClassLoader("server", commonLoader);
        // 加载路径为shared.loader,默认为空,父类加载器为commonLoader
        sharedLoader = createClassLoader("shared", commonLoader);
    } catch (Throwable t) {
        handleThrowable(t);
        log.error("Class loader creation threw exception", t);
        System.exit(1);
    }
}

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

        String value = CatalinaProperties.getProperty(name + ".loader"); // 加后缀
        // catalinaLoader与sharedLoader的加载路径均为空,所以直接返回commonLoader对象,默认3者为同一个对象
...

        return ClassLoaderFactory.createClassLoader(repositories, parent); // 工厂
    }

org.apache.catalina.startup.ClassLoaderFactory#createClassLoader(List, ClassLoader)

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

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

    // Construct the "class path" for this class loader
    Set<URL> set = new LinkedHashSet<>();

    if (repositories != null) {
        for (Repository repository : repositories)  {
            if (repository.getType() == RepositoryType.URL) {
                ...
            }
        }
    }

    // Construct the class loader itself
    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<URLClassLoader>() {
                @Override
                public URLClassLoader run() {
                    if (parent == null)
                        return new URLClassLoader(array);
                    else
                        return new URLClassLoader(array, parent);
                }
            });
}

// org.apache.catalina.startup.ClassLoaderFactory.RepositoryType
public enum RepositoryType {
    DIR, //表示整个目录下的资源,包括所有Class、jar包以及其他类型资源
    GLOB,//表示整个目录下所有的Jar包资源,仅仅是.jar后缀的资源
    JAR, //表示单个Jar包资源
    URL  //表示从URL上获取的Jar包资源
}

使用加载器工厂的好处

ClassLoadFactory 有一个内部 Repository,它就是表示资源的类,资源的类型用一个 RepositoryType 的枚举表示。

如果在检查 jar 包的时候,如果有检查的 URL 地址的如果检查有异常就忽略掉,可以确保部分类加载正确。

3. 尽早设置线程上下文类加载器

每一个运行线程中有一个成员 ContextClassLoader,用于在运行时动态载入其他类,当程序中没有显示声明由哪个类加载器去加载哪个类(比如 new 出一 个类时),将默认由当前线程类加载器加载,所以一般系统默认的 ContextClassLoad 是系统类加载器。

public void init() throws Exception {
    System.out.println("Bootsrap--init()");
    //类加载器初始化
    initClassLoaders();

    //设置线程上下文类加载器来解决有可能的ClassNotFoundException问题
    Thread.currentThread().setContextClassLoader(catalinaLoader);

一般在实际的系统上,使用线程上下文类加载器,可以设置不同的加载方式,这个也是 Java 灵活的类加载方式的体现,也可以很轻松的打破双亲委派模式,同时也会避免类加载的异常。

4. Webapp 类加载器

每个 web 应用会对一个 Context 节点,在 JVM 中就会对应一个 org.apache.catalina.core.StandardContext 对象,而每一个 StandardContext 对象内部都 一个加载器实例 loader 实例变量。这个 loader 实际上是 WebappLoader 对象。而每一个 WebappLoader 对象内部关联了一个 classLoader 变量(就这这个类的定义中,可以看到该变量的类型是 org.apache.catalina.loader.WebappClassLoader)。

所以,这里一个 web 应用会对应一个 StandardContext 一个 WebappLoader 一个 WebappClassLoader 。

一个 web 应用对应着一个 StandardContext 实例,每个 web 应用都拥有独立 web 应用类加载器(ParallelWebappClassLoader(不同版本使用的可能不是同一个)),这个类加载器在 StandardContext.startInternal()中被构造了出来。

org.apache.catalina.core.StandardContext#startInternal

protected synchronized void startInternal() throws LifecycleException {
...
    //Webapp类加载的设置
    //这里的获取和设置类加载
    if (getLoader() == null) { // 每个Context都会创建一个自己的WebappLoader实例, 这样每一个webapp都会
        WebappLoader webappLoader = new WebappLoader(getParentClassLoader());
        webappLoader.setDelegate(getDelegate());
        setLoader(webappLoader); // 这里使用了读写锁方式
    }
...
} // 所以前面的 commonLoader, catalinaLoader, sharedLoader 都在Bootstrap.class里面, 整个tomcat里面只会创建一个实例

注意这里:设置加载器和获取加载器都使用了读写锁机制,确保多线程情况下对共享资源的访问不会出现问题。

同时因为 Tomcat 的生命周期管理,必定会调用 WebappLoader.java 的 startInternal()方法,该方法中 new 出WebappClassLoader

public class WebappClassLoader extends WebappClassLoaderBase 
public abstract class WebappClassLoaderBase extends URLClassLoader

org.apache.catalina.loader.WebappLoader

// WebappClassLoaderBase抽象类两个实现 WebappClassLoader 和 ParallelWebappClassLoader
// 这里使用的ParallelWebappClassLoader
private String loaderClass = ParallelWebappClassLoader.class.getName();

org.apache.catalina.loader.WebappLoader#startInternal

protected void startInternal() throws LifecycleException {
...
    // Construct a class loader based on our current repositories list
    try {

        classLoader = createClassLoader();
        classLoader.setResources(context.getResources());
        classLoader.setDelegate(this.delegate);
    }
...
}

5. 热加载源码分析

当配置信息中有 reloadable 的属性,并且值为 true 时,Tomcat 怎么去完成这个过程呢?

<Context pathname="/" docBase="/app/app1" reloadable="true"/>

据 Tomcat 的启动流程,我们分析下 Context 的初始化 start 方法,Context 只是一个接口,具体实现类是 StandardContext,我们分析下 startInternal 方法(此方法由之前的抽像骨架类中的 start 方法触发)

三. Tomcat源码分析-类加载和类加载器_第9张图片

有一个线程启动的方法 threadStart()

// org.apache.catalina.core.StandardContext#startInternal
protected synchronized void startInternal() throws LifecycleException {
    ...
}
//Webapp类加载的设置
//这里的获取和设置类加载
if (getLoader() == null) {
    WebappLoader webappLoader = new WebappLoader(getParentClassLoader());
    webappLoader.setDelegate(getDelegate());
    setLoader(webappLoader);
}
...
    try{
        // 启动ContainerBackgroundProcessor线程(容器后台线程)
        super.threadStart();
    } finally {
        // Unbinding thread
        unbindThread(oldCCL);
    }
...
}

// org.apache.catalina.core.ContainerBase#threadStart
protected void threadStart() {

    if (thread != null)
        return;
    if (backgroundProcessorDelay <= 0)
        return;

    threadDone = false;
    String threadName = "ContainerBackgroundProcessor[" + toString() + "]";
    thread = new Thread(new ContainerBackgroundProcessor(), threadName); // 这里
    thread.setDaemon(true);
    thread.start();

}

// org.apache.catalina.core.ContainerBase.ContainerBackgroundProcessor
protected class ContainerBackgroundProcessor implements Runnable {

        @Override
        public void run() {
            Throwable t = null;
            String unexpectedDeathMessage = sm.getString(
                    "containerBase.backgroundProcess.unexpectedThreadDeath",
                    Thread.currentThread().getName());
            try {
                while (!threadDone) {
                    try {
                        Thread.sleep(backgroundProcessorDelay * 1000L); // 这个值初始值是-1, sleep传入的值必须是0~Log.MAX_VALUE
//    @Override 这个方法进行了赋值操作
//    public void setBackgroundProcessorDelay(int delay) {
//        backgroundProcessorDelay = delay;
//    }
                    } catch (InterruptedException e) {
                        // Ignore
                    }
                    if (!threadDone) {
                        processChildren(ContainerBase.this); // 这里
                    }
                }
            } catch (RuntimeException|Error e) {
                t = e;
                throw e;
            } finally {
                if (!threadDone) {
                    log.error(unexpectedDeathMessage, t);
                }
            }
        }
    ...
        protected void processChildren(Container container) {
        ClassLoader originalClassLoader = null;

        try {
            if (container instanceof Context) {
                Loader loader = ((Context) container).getLoader();
                // Loader will be null for FailedContext instances
                if (loader == null) {
                    return;
                }

                // Ensure background processing for Contexts and Wrappers
                // is performed under the web app's class loader
                originalClassLoader = ((Context) container).bind(false, null);
            }
            container.backgroundProcess(); // 容器的后台任务
            Container[] children = container.findChildren();
            for (int i = 0; i < children.length; i++) {
                if (children[i].getBackgroundProcessorDelay() <= 0) {
                    processChildren(children[i]);
                }
            }
        } catch (Throwable t) {
            ExceptionUtils.handleThrowable(t);
            log.error("Exception invoking periodic operation: ", t);
        } finally {
            if (container instanceof Context) {
                ((Context) container).unbind(false, originalClassLoader);
            }
        }
    }
}

org.apache.catalina.core.StandardContext#backgroundProcess

public void backgroundProcess() {

    if (!getState().isAvailable())
        return;
    //删减后的↓↓↓↓↓↓↓ 逐个调用内部相关的backgroundProcess()方法
    Loader loader = getLoader();
    if (loader != null) {
        try {
            loader.backgroundProcess(); // WebappLoader
        } catch (Exception e) {
            log.warn(sm.getString(
                    "standardContext.backgroundProcess.loader", loader), e);
        }
    }
...
    super.backgroundProcess();
}

org.apache.catalina.loader.WebappLoader#backgroundProcess

public void backgroundProcess() {
    if (reloadable && modified()) {
        try {
            //设置线程类加载器的类加载器为WebappClassloader
            Thread.currentThread().setContextClassLoader
                (WebappLoader.class.getClassLoader());
            if (context != null) {
                //终要执行的重新加载的方法:StandardContext类的reload():
                context.reload();
            }
        } finally {
            if (context != null && context.getLoader() != null) {
                Thread.currentThread().setContextClassLoader
                    (context.getLoader().getClassLoader());
            }
        }
    }
}

6. JasperLoader

先看看官方代码注释

三. Tomcat源码分析-类加载和类加载器_第10张图片

显然, 是用来加载jsp文件的, 说白了就是将*.jsp, 解析加载成*_servlet.class然后以运行时数据结构加载jvm中

所以加载肯定是核心的代码: org.apache.jasper.servlet.JasperLoader#loadClass(String, boolean)

代码注释:

三. Tomcat源码分析-类加载和类加载器_第11张图片

@Override
public synchronized Class<?> loadClass(final String name, boolean resolve)
    throws ClassNotFoundException {

    Class<?> clazz = null;

    // (0) Check our previously loaded class cache
    clazz = findLoadedClass(name);
    if (clazz != null) {
        if (resolve)
            resolveClass(clazz);
        return (clazz);
    }

    // (.5) Permission to access this class when using a SecurityManager
    if (securityManager != null) {
        int dot = name.lastIndexOf('.');
        if (dot >= 0) {
            try {
                // Do not call the security manager since by default, we grant that package.
                if (!"org.apache.jasper.runtime".equalsIgnoreCase(name.substring(0,dot))){
                    securityManager.checkPackageAccess(name.substring(0,dot));
                }
            } catch (SecurityException se) {
                String error = "Security Violation, attempt to use " +
                    "Restricted Class: " + name;
                se.printStackTrace();
                throw new ClassNotFoundException(error);
            }
        }
    }
//     public static final String JSP_PACKAGE_NAME =
//			System.getProperty("org.apache.jasper.Constants.JSP_PACKAGE_NAME", "org.apache.jsp");
// 这是我感觉和其他类加载不同的一个地方, 对特殊包名进行了一些过滤
    if( !name.startsWith(Constants.JSP_PACKAGE_NAME + '.') ) {
        // Class is not in org.apache.jsp, therefore, have our
        // parent load it
        clazz = getParent().loadClass(name);
        if( resolve )
            resolveClass(clazz);
        return clazz;
    }

    return findClass(name);
}

你可能感兴趣的:(tomcat,tomcat)