《Tomcat源码解析》Tomcat类加载器

本文详细解读了J2SE类加载器的机制,介绍了Tomcat自定义的Web应用、Common、Catalina和Shared类加载器,探讨了它们如何实现隔离性、灵活性和性能优化。重点剖析了委派机制和Tomcat类加载器源码,以及如何通过配置控制委派行为。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

《Tomcat源码解析》Tomcat类加载器

《Tomcat源码》系列文章集合:
Tomcat结构设计
Tomcat启动过程
Tomcat类加载器
Tomcat的Web请求和处理过程



前言


一、J2SE类加载器

JVM默认提供了3个类加载器,他们以一种父子树的方式创建,同时使用委派模式确保应用程序可通过自身的类加载器(System)加载所有可见的Java类。Jvm对类的初始化是一个延迟的机制,即使用的是lazy的方式,当一个类在首次使用的时候才会被初始化,在同一个运行包下,一个Class只会被初始化一次。
在这里插入图片描述

1.1 启动类加载器

在这里插入图片描述
打印启动类加载器的加载路径:
在这里插入图片描述
在这里插入图片描述

1.2 扩展类加载器

在这里插入图片描述
打印扩展类加载器的加载路径
在这里插入图片描述
在这里插入图片描述

1.3 系统类加载器

应用程序不在自己构造类加载器的情况下,使用System作为默认的类加载器。如果应用程序自己构造类加载器,基本也以System作为父类加载器。
在这里插入图片描述

1.4 代码获取各种类加载器

在这里插入图片描述
在这里插入图片描述

1.5 双亲委派机制

双亲委派模型的工作过程是:如果一个类加载器收到了类加载的请求,他首先不会自己去尝试加载这个类,而是把这个请求委派父类加载器去完成。每一个层次的类加载器都是如此,因此所有的加载请求最终都应该传送到顶层的启动类加载器中,只有当父加载器反馈自己无法完成这个请求(他的搜索范围中没有找到所需的类)时,子加载器才会尝试自己去加载。
在这里插入图片描述
在这里插入图片描述
(1)当自定义一个java.lang.String对象并且尝试在程序里面使用时,会发现程序不会调用自定义的String对象,而是调用jdk提供的String对象,是因为类加载时,会先让他的父类加载器先去加载,一直向上委托,直到启动类加载器,启动类加载器会加载java包下的类,所以String对象是jdk提供的对象。
(2)同理,如果想运行自己定义的java.lang.String的main方法,程序会报错,提示找不到main方法,因为jdk提供的String对象不存在main方法。
双亲委派机制的作用就是避免类的重复加载保护程序安全,防止核心API被随意篡改

1.6 ClassLoader

ClassLoader的loadClass实现了双亲委派机制

protected Class<?> loadClass(String name, boolean resolve)
        throws ClassNotFoundException
    {
        synchronized (getClassLoadingLock(name)) {
            // 1、首先从缓存中获取
            Class<?> c = findLoadedClass(name);
            if (c == null) {
                long t0 = System.nanoTime();
                try {
                	// 2、缓存中没有,就从父类加载器从加载
                    if (parent != null) {
                        c = parent.loadClass(name, false);
                    } else {
                    	// parent == null,代表parent为BootStrap类加载器。
                        c = findBootstrapClassOrNull(name);
                    }
                } catch (ClassNotFoundException e) {
                    // ClassNotFoundException thrown if class not found
                    // from the non-null parent class loader
                }

                if (c == null) {
                    // 如果还没有找到,就调用findClass加载类。
                    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;
        }
    }

二、Tomcat类加载器

应用服务器通常会自行创建类加载器以实现更灵活的控制,这一方面是对规范的实现(Servelt规范要求每个Web应用都有一个独立的类加载器实例),另一方面也有架构层面的考虑。
(1)隔离性:Web应用类库相互隔离,避免依赖库或者应用包相互影响。
(2)灵活性:既然Web应用之间的类加载器相互独立,那么我们就能只针对一个Web应用进行重新部署,此时该Web应用的类加载器将会被重新创建,而且不会影响其他Web应用。
(3)性能:由于每个Web应用都有一个类加载器,因此Web应用在加载类时,不会搜索其他Web应用包含的Jar包,性能自然高于应用服务器只有一个类加载器的情况。
Tomcat类加载器方案:
在这里插入图片描述
除了每个Web应用的类加载器外,Tomcat也提供了3个基础的类加载器和Web应用类加载器,而且这三个类加载器指向的路径和包列表均可以有catalina.properties配置。
(1)Common:以System为父类加载器,是位于Tomcat应用服务器顶层的公用类加载器。其路径为common.loader,默认指向$CATALINA_HOME/lib下的包。
(2)Catalina:以Common为父加载器,适用于加载Tomcat应用服务器的类加载器,其路径为server.loader,默认为空。此时Tomcat使用Common类加载器应用服务器。
(3)Sharded:以Common为父加载器,是所有Web应用的父加载器,其路径是shared.loader,默认为空,此时Tomcat使用Common类加载器作为Web应用的父加载器。
(4)Web应用:以shared为父加载器,加载/WEB-INF/classes目录下的为压缩的Class和资源文件以及/WEB-INF/lib目录下的Jar包。该加载器只对当前Web应用可见,对其他Web应用不可见。

首先Common类加载器负责加载Tomcat应用服务器内部和Web应用均可见的类,例如Servelt规范相关的包和一些通用的工具包。
其次,Catalina类加载器负责加载只有Tomcat应用服务器内部可见的类,这些类对Web应用不可见,如Tomcat的具体实现类,因为Web应用最好与服务器松耦合,所以不应该依赖应用服务器内部的类。
再次Shared类加载器负责加载Web应用共享的类,这些类Tomcat服务器不会依赖。
如果我们想实现自己的会话存储方案,而且该方案依赖了一些第三方包,我们会不希望这些包会Web应用可见,此时我们可以配置server.loader,创建新的Catalina类加载器。
最后,Tomcat服务器$CATALINA_HOME/bin目录下的包作为启动入口由System类加载器加载。通过这几个启动包的隔离,Tomcat简化了应用服务器的启动,同时增加了灵活性。

三、Web应用类加载器

Java默认的委派机制的过程如下:
(1)从缓存中加载
(2)如果缓存中没有,则从父类加载器中加载
(3)如果父类加载器没有,则从当前类加载器加载。
(4)如果没有,则抛出异常
Tomcat提供的Web应用类加载器与默认的委派机制不同。当进行类加载器时,除了JVM基础类库外,它会首先尝试通过当前类加载器加载,然后才进行委派Servelt规范相关API禁止通过Web应用类加载器加载,因此不要在Web应用中包含这些包。
Web应用类加载器默认的加载顺序如下:
(1)从缓存中加载
(2)如果没有,则从JVM的BootStrap类加载器加载。
(3)如果没有,则从当前类加载器加载(按照WEB-INF/classes、WEB-INF/lib的顺序)
(4)如果没有,则从当前父类加载器加载,由于父类加载器采用默认的委派模式,所以加载顺序为System、Common、Shared。
Tomcat提供了delegate属性用于控制是否启用Java委派模式,默认为false(不启用)。当配置为true时,Tomcat将使用Java默认的委派模式:
(1)从缓存中加载
(2)如果没有,从JVM的Bootstrap类加载器加载
(3)如果没有,从父类的加载器加载(System、Common、Shared)
(4)如果没有,则从当前类加载器加载。
除了可以通过delegate属性控制是否启用Java的委派模式外,Tomcat还可以通过packegeTriggerDeny属性只让某些包路径采用Java的委派模式,Web应用类加载器对于符合packegeTriggerDeny指定包路径的类强制采用Java的委派模式。
Tomcat通过该机制实现对Web应用中的Jar包覆盖服务器提供包的目的,Java核心类库、Servelt规范相关类库是无法覆盖的,此外Java默认提供的诸如XML工具包,由于位于Jvm的Bootstrap类加载器也无法覆盖,只能通过endorsed方式实现。

四、Tomcat类加载器源码

Tomcat类加载器的初始化是在Bootstrap的init方法。

 // Tomcat的三个类加载器
    ClassLoader commonLoader = null;
    ClassLoader catalinaLoader = null;
    ClassLoader sharedLoader = null;
    // -------------------------------------------------------- Private Methods
    // 初始化类加载器,tomcat自定义的类加载器
    private void initClassLoaders() {
        try {
            // 1、创建CommonClassLoader;负责加载Web应用和服务器都可见的类
            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();
            }
            // 2、创建CatalinaClassLoader;负责加载只有Tomcat应用服务器内部可见的类
            catalinaLoader = createClassLoader("server", commonLoader);
            // 3、创建SharedClassLoader;负责加载Web应用共享的类
            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 {
        // 从CatalinaProperties里面加载  属性name + ".loader"
        // Value: "${catalina.base}/lib" 用的是相对路径
        String value = CatalinaProperties.getProperty(name + ".loader");
        // 找不到该属性,就返回parent
        if ((value == null) || (value.equals("")))
            return parent;
        //将相对路径转成绝对路径
        value = replace(value);

        List<Repository> repositories = new ArrayList<>();
        // 将一个字符串形式的PATH路径分割成仓库数组
        String[] repositoryPaths = getPaths(value);
        // 遍历数组,封装Repository集合
        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));
            }
        }
        // 根据加载目录的集合 和 父加载器,创建类加载器对象。
        // 在这个方法里面可以看到这三个类加载器都是URLClassLoader的子类。
        return ClassLoaderFactory.createClassLoader(repositories, parent);
    }
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值