【Tomcat专题】Tomcat如何打破双亲委派机制?

news2025/1/10 20:30:25

文章目录

  • 类加载器
  • 双亲委派机制
    • 双亲委派的好处
  • Tomcat的类加载器
    • loadClass总体加载步骤:

类加载器

三种JDK内部的类加载器

  1. 启动类加载器(BootStrap ClassLoader)

负责加载JRE\lib下的rt.jar、resources.jar、charsets.jar包中的class

  1. 扩展类加载器(Extension ClassLoader)

负责加载jre\lib\ext\目录下面的jar包。

  1. 应用类加载器(Application ClassLoader)

应用类加载器就是负责加载应用路径(classpath)上的类了。

具体可参考下图:
在这里插入图片描述

双亲委派机制

双亲委派机制指的是,当收到一个类加载请求后,ClassLoader首先不会自己直接去加载这个类,而是委派给父类去加载,只有当父类加载器在其路径下没有找到所需要加载的类之后,子类加载器才会自己尝试去加载。

双亲委派的好处

采用双亲委派机制可以避免类的重复加载,以及一些需要保护的类,不会被篡改,比如我们要加载rt.jar包中的java.lang.Object类,不论是哪个类加载器,最终一定要交给启动类加载器(BootstrapClassLoader)来加载,而启动类加载器就负责加载像rt.jar这样的包中的类,从而也保证了最终系统中只有一个Object类。

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 {
            	// 如果parent为空,则表示已经交给了Bootstrap类加载器了
                if (parent != null) {
                	// 通过递归的方式,让父类加载器去加载
                    c = parent.loadClass(name, false);
                } else {
                	// 通过bootstrap类加载器加载
                    c = findBootstrapClassOrNull(name);
                }
            } catch (ClassNotFoundException e) {
                // ClassNotFoundException thrown if class not found
                // from the non-null parent class loader
            }
            // 如果父类加载器都没有加载到,就使用findClass方法自己去加载
            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;
    }
}

// 通过重写findClass方法实现自定义加载方式
protected Class<?> findClass(String name) throws ClassNotFoundException {
    throw new ClassNotFoundException(name);
}

Tomcat的类加载器

可以看出JVM的双亲委派加载机制关键实现就在loadClass和findClass这个方法中,所以如果要打破双亲委派机制关键就要重写loadClass和findClass这两个方法。

我们先来看看loadClass方法

public Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundE
    synchronized (JreCompat.isGraalAvailable() ? this : getClassLoadingLock(n
        if (log.isDebugEnabled())
            log.debug("loadClass(" + name + ", " + resolve + ")");
        Class<?> clazz = null;
        // Log access to stopped class loader
        checkStateForClassLoading(name);
        // (0) Check our previously loaded local class cache
        // 先在本地缓存中查找是否已经加载过了
        clazz = findLoadedClass0(name);
        if (clazz != null) {
            if (log.isDebugEnabled())
                log.debug("  Returning class from cache");
            if (resolve)
                resolveClass(clazz);
            return clazz;
        }
        // (0.1) Check our previously loaded class cache
        // 先在本地缓存中查找是否已经加载过了
        clazz = JreCompat.isGraalAvailable() ? null : findLoadedClass(name);
        if (clazz != null) {
            if (log.isDebugEnabled())
                log.debug("  Returning class from cache");
            if (resolve)
                resolveClass(clazz);
            return clazz;
        }
        // (0.2) Try loading the class with the system class loader, to preve
        //       the webapp from overriding Java SE classes. This implements
        //       SRV.10.7.2
        // 在加载webapp目录之前先尝试使用系统类加载器加载(也就是BootstrapClassLoader或者ExtClassLoader)
        String resourceName = binaryNameToPath(name, false);
        ClassLoader javaseLoader = getJavaseClassLoader();
        boolean tryLoadingFromJavaseLoader;
        try {
            // Use getResource as it won't trigger an expensive
            // ClassNotFoundException if the resource is not available from
            // the Java SE class loader. However (see
            // https://bz.apache.org/bugzilla/show_bug.cgi?id=58125 for
            // details) when running under a security manager in rare cases
            // this call may trigger a ClassCircularityError.
            // See https://bz.apache.org/bugzilla/show_bug.cgi?id=61424 for
            // details of how this may trigger a StackOverflowError
            // Given these reported errors, catch Throwable to ensure any
            // other edge cases are also caught
            URL url;
            if (securityManager != null) {
                PrivilegedAction<URL> dp = new PrivilegedJavaseGetResource(re
                url = AccessController.doPrivileged(dp);
            } else {
                url = javaseLoader.getResource(resourceName);
            }
            tryLoadingFromJavaseLoader = (url != null);
        } catch (Throwable t) {
            // Swallow all exceptions apart from those that must be re-thrown
            ExceptionUtils.handleThrowable(t);
            // The getResource() trick won't work for this class. We have to
            // try loading it directly and accept that we might get a
            // ClassNotFoundException.
            tryLoadingFromJavaseLoader = true;
        }
        if (tryLoadingFromJavaseLoader) {
            try {
                clazz = javaseLoader.loadClass(name);
                if (clazz != null) {
                    if (resolve)
                        resolveClass(clazz);
                    return clazz;
                }
            } catch (ClassNotFoundException e) {
                // Ignore
            }
        }
        // (0.5) Permission to access this class when using a SecurityManager
        if (securityManager != null) {
            int i = name.lastIndexOf('.');
            if (i >= 0) {
                try {
                    securityManager.checkPackageAccess(name.substring(0,i));
                } catch (SecurityException se) {
                    String error = sm.getString("webappClassLoader.restricted
                    log.info(error, se);
                    throw new ClassNotFoundException(error, se);
                }
            }
        }

		// 如果执行到这还没有找到,说明没有被加载过,且也不是JDK中的类
		// 如果delegate为true,则tomcat还是会使用双亲委派加载方式
        boolean delegateLoad = delegate || filter(name, true);
        // (1) Delegate to our parent if requested
        if (delegateLoad) {
            if (log.isDebugEnabled())
                log.debug("  Delegating to parent classloader1 " + parent);
            try {
                clazz = Class.forName(name, false, parent);
                if (clazz != null) {
                    if (log.isDebugEnabled())
                        log.debug("  Loading class from parent");
                    if (resolve)
                        resolveClass(clazz);
                    return clazz;
                }
            } catch (ClassNotFoundException e) {
                // Ignore
            }
        }
        // delegate默认为false,所以会使用findClass方法加载
        // (2) Search local repositories
        if (log.isDebugEnabled())
            log.debug("  Searching local repositories");
        try {
            clazz = findClass(name);
            if (clazz != null) {
                if (log.isDebugEnabled())
                    log.debug("  Loading class from local repository");
                if (resolve)
                    resolveClass(clazz);
                return clazz;
            }
        } catch (ClassNotFoundException e) {
            // Ignore
        }
        // (3) Delegate to parent unconditionally
        // 最后都没有加载,则再委托给父加载器加载
        if (!delegateLoad) {
            if (log.isDebugEnabled())
                log.debug("  Delegating to parent classloader at end: " + par
            try {
                clazz = Class.forName(name, false, parent);
                if (clazz != null) {
                    if (log.isDebugEnabled())
                        log.debug("  Loading class from parent");
                    if (resolve)
                        resolveClass(clazz);
                    return clazz;
                }
            } catch (ClassNotFoundException e) {
                // Ignore
            }
        }
    }
    throw new ClassNotFoundException(name);
}

findClass方法则会优先在Web目录中查找要加载的类

public Class<?> findClass(String name) throws ClassNotFoundException {
    if (log.isDebugEnabled())
        log.debug("    findClass(" + name + ")");
    checkStateForClassLoading(name);
    // (1) Permission to define this class when using a SecurityManager
    if (securityManager != null) {
        int i = name.lastIndexOf('.');
        if (i >= 0) {
            try {
                if (log.isTraceEnabled())
                    log.trace("      securityManager.checkPackageDefinition");
                securityManager.checkPackageDefinition(name.substring(0,i));
            } catch (Exception se) {
                if (log.isTraceEnabled())
                    log.trace("      -->Exception-->ClassNotFoundException", se);
                throw new ClassNotFoundException(name, se);
            }
        }
    }
    // Ask our superclass to locate this class, if possible
    // (throws ClassNotFoundException if it is not found)
    Class<?> clazz = null;
    try {
        if (log.isTraceEnabled())
            log.trace("      findClassInternal(" + name + ")");
        try {
            if (securityManager != null) {
                PrivilegedAction<Class<?>> dp =
                    new PrivilegedFindClassByName(name);
                clazz = AccessController.doPrivileged(dp);
            } else {
            	// 先在/WEB-INF/classes目录下查找要加载的类
                clazz = findClassInternal(name);
            }
        } catch(AccessControlException ace) {
            log.warn(sm.getString("webappClassLoader.securityException", name,
                    ace.getMessage()), ace);
            throw new ClassNotFoundException(name, ace);
        } catch (RuntimeException e) {
            if (log.isTraceEnabled())
                log.trace("      -->RuntimeException Rethrown", e);
            throw e;
        }
        if ((clazz == null) && hasExternalRepositories) {
            try {
            	// 如果找不到则交给父类加载器去查找
                clazz = super.findClass(name);
            } catch(AccessControlException ace) {
                log.warn(sm.getString("webappClassLoader.securityException", name,
                        ace.getMessage()), ace);
                throw new ClassNotFoundException(name, ace);
            } catch (RuntimeException e) {
                if (log.isTraceEnabled())
                    log.trace("      -->RuntimeException Rethrown", e);
                throw e;
            }
        }
        if (clazz == null) {
            if (log.isDebugEnabled())
                log.debug("    --> Returning ClassNotFoundException");
            throw new ClassNotFoundException(name);
        }
    } catch (ClassNotFoundException e) {
        if (log.isTraceEnabled())
            log.trace("    --> Passing on ClassNotFoundException");
        throw e;
    }
    // Return the class we have located
    if (log.isTraceEnabled())
        log.debug("      Returning class " + clazz);
    if (log.isTraceEnabled()) {
        ClassLoader cl;
        if (Globals.IS_SECURITY_ENABLED){
            cl = AccessController.doPrivileged(
                new PrivilegedGetClassLoader(clazz));
        } else {
            cl = clazz.getClassLoader();
        }
        log.debug("      Loaded by " + cl.toString());
    }
    return clazz;
}

loadClass总体加载步骤:

  1. 先在cache中查找是否已经加载过,如果有说明Tomcat的类加载器已经加载过了。
  2. 如果Tomcat没有加载过,则先让系统类加载器加载,也就是BootstrapClassLoader或者ExtClassLoader(优先让系统加载器加载能够保证JDK的核心类不会被覆盖加载)。
  3. 如果还是没有,则说明不是JDK中的核心类,那么就通过delegate属性来决定是继续按照双亲委派方式加载,还是按照自定义的方式加载,也就是调用本地的findClass方法,去/WEB-INF/classes目录下查找要加载的类。
  4. 如果这些都没有找到,则回到双亲委派方式交给父类加载器去查找。
  5. 最后都没有找到,则抛出ClassNot在这里插入代码片Found异常。

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/21605.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

一文带你搞懂sklearn.metrics混淆矩阵

一般的二分类任务需要的评价指标有4个 accuracyprecisionrecallf1-score 四个指标的计算公式如下 计算这些指标要涉及到下面这四个概念&#xff0c;而它们又构成了混淆矩阵 TP (True Positive)FP (False Positive)TN (True Negative)FN (False Negative) 混淆矩阵实际值01预测…

周杰伦腾格尔晚上八点同时开线上演唱会,究竟是巧合还是刻意安排

从日历上面看&#xff0c;2022年11月19日&#xff0c;是一个再平凡不过的日子&#xff0c;不过有了周杰伦和腾格尔的加持&#xff0c;这个平凡的日子也变得不平凡了。根据腾格尔老师本人透露&#xff0c;他准备在11月19日&#xff0c;在某音平台开启线上演唱会&#xff0c;为歌…

智慧实验室解决方案-最新全套文件

智慧实验室解决方案-最新全套文件一、建设背景二、建设架构智慧实验室建设核心目标三、建设方案四、获取 - 智慧实验室全套最新解决方案合集一、建设背景 当前高校和中小学的智慧校园建设正如火如荼地进行中&#xff0c;智慧实验室建设属于“智慧校园”建设的重要组成部分之一…

ctf_BUUCTF_web(1)

文章目录BUUCTF_webSQL注入1. [极客大挑战 2019]EasySQL2. [SUCTF 2019]EasySQL3.[强网杯 2019]随便注4.[极客大挑战 2019]BabySQL5.[BJDCTF2020]Easy MD56.[极客大挑战 2019]HardSQL7.[GXYCTF2019]BabySQli8.[GYCTF2020]Blacklist9.[CISCN2019 华北赛区 Day2 Web1]Hack World1…

面试:HTTP 的长连接和短连接

https://www.cloudflare.com/zh-cn/learning/ddos/syn-flood-ddos-attack/ 一文搞懂 HTTP 的长连接和短连接_文晓武的博客-CSDN博客 1、HTTP 协议与 TCP/IP 协议的关系 HTTP 的长连接和短连接本质上是 TCP 长连接和短连接。HTTP 属于应用层协议&#xff0c;在传输层使用 TCP…

区块链交易明细中各字段的含义

Transaction Hash&#xff1a;标识本次交易的 hashStatus&#xff1a;交易状态Block&#xff1a;7768188 表示本次块高&#xff0c;217034 表示在 7768188 后面又新挖的区块数量&#xff0c;该数值会随着新区块增加而不断增长Timestamp&#xff1a;交易成功的时间戳From&#x…

直流无刷电机(BLDC)转速闭环调速系统及Matlab/Simulink仿真分析

文章目录前言一、转速闭环直流调速系统二、Matlab/Simulink仿真2.1.仿真电路分析2.2.仿真结果分析总结前言 变压调速是直流调速系统的主要调速方法&#xff0c;因此系统的硬件至少包含&#xff1a;可调直流电源和直流电机两部分。可调直流电源多采用直流PWM变换器&#xff0c;…

v-for的用法及key值原理

v-for的用途&#xff1a; &#xff08;1&#xff09;关键字&#xff1a; v-for遍历的时候&#xff0c;关键字有两个&#xff1a;in、of&#xff1a;两个关键字没有区别&#xff0c;用哪一个都行&#xff1b; &#xff08;2&#xff09;支持对象、数组、数字遍历&#xff1a…

java线程生命周期

如图 java线程的生命周期主要分为 新建: :新建这一刻 他会创建出一个线程对象 这个就是我们通过new线程类 这部操作实现的 当我们通过new出来的线程对象 执行 start方法之后 他就会进入第二个生命周期 就绪: 在这个过程中 他有执行资格 就是他是可以执行线程的程序的 但这个阶…

zk中session的基本原理、create、set、delete命令的使用(重补早期学习记录)

前言:补学习记录,几年前写一半丢草稿箱,突然看到,有强迫症所以补完 一、session基本原理 二、创建节点 create [-s] | [-e] 路径 数据 权限 还是一样的连接zk客户端 ./zkCli.sh 使用help查看命令 我们创建一个父节点,并存入数据 使用get来或者它的数据和状态信息 状态参…

【LeetCode】Day187-分割回文串

题目 131. 分割回文串【中等】 题解 如何判断字符串是回文串&#xff1f; 使用动态规划&#xff1a;f[i][j]代表s[i…j]是否是回文串&#xff0c;则有状态转移方程如下&#xff0c; 有了f[i][j]&#xff0c;如何分割回文串&#xff1f; 利用回溯搜索&#xff0c;当s[0…i-1…

Spring参数校验

如何使用 Spring提供了简便的参数校验注解&#xff0c;不需要像以前一样if else去判断了&#xff0c;下面记录一下如何使用注解实现参数的校验 导入坐标 要使用各种注解完成参数的校验&#xff0c;需要导入hibernate-validator坐标以实现 <dependency><groupId>…

[Spring Cloud] nacos安装与使用

✨✨个人主页:沫洺的主页 &#x1f4da;&#x1f4da;系列专栏: &#x1f4d6; JavaWeb专栏&#x1f4d6; JavaSE专栏 &#x1f4d6; Java基础专栏&#x1f4d6;vue3专栏 &#x1f4d6;MyBatis专栏&#x1f4d6;Spring专栏&#x1f4d6;SpringMVC专栏&#x1f4d6;SpringBoot专…

密码学消息鉴别

信息安全 完整性 1.数据完整性&#xff1a;数据未被篡改或损坏。数据是不可否认的&#xff0c;发送方和接收方不能抵赖处理了数据。 2.系统完整性&#xff1a;系统未被非授权使用。 真实性 确认实体是它声明的&#xff0c;适用于用户、进程等等的合法的信息&#xff08;是否真…

LVS-DR模式

文章目录一、LVS-DR集群介绍1、LVS-DR 工作原理2、 数据包流向分析3、LVS-DR 模式的特点4、LVS-DR中的ARP问题4.1 问题一4.2问题二二、构建LVS-DR集群的步骤实验环境准备&#xff1a;1、配置负载调度器&#xff08;192.168.2.66&#xff09;1.1 配置虚拟 IP 地址&#xff08;VI…

HTML+CSS+JS网页设计期末课程大作业—— 艺术官网17页(包含登陆注册)

⛵ 源码获取 文末联系 ✈ Web前端开发技术 描述 网页设计题材&#xff0c;DIVCSS 布局制作,HTMLCSS网页设计期末课程大作业 | 公司官网网站 | 企业官网 | 酒店官网 | 等网站的设计与制 | HTML期末大学生网页设计作业&#xff0c;Web大学生网页 HTML&#xff1a;结构 CSS&#…

力扣(LeetCode)22. 括号生成(C++)

回溯 括号合法的性质&#xff1a; 任意前缀的左括号数大于右括号数左括号和右括号的数量相等。 根据性质 &#xff0c; 写递归体 。 class Solution { public:vector<string> ans;vector<string> generateParenthesis(int n) {dfs(n,0,0,"");return …

Mosaic数据增强

paper&#xff1a;YOLOv4: Optimal Speed and Accuracy of Object Detection mosaic data augmentation最早是在YOLO v4的文章中提出的&#xff0c;但其实在ultralytics-YOLOv3中就已经实现了。具体就是将4张样本拼接成一张图&#xff0c;具有以下优点&#xff1a;&#xff08…

C++string—常用接口介绍+模拟实现+习题讲解

如果调试一个程序让你很苦恼&#xff0c;千万不要放弃&#xff0c;成功永远在拐角之后&#xff0c;除非你走到拐角&#xff0c;否则你永远不知道你离他多远&#xff0c;所以&#xff0c;请记住&#xff0c;坚持不懈&#xff0c;直到成功。 目录 前言 1.string类的常用接口 1.1s…

c++提高篇——模板(下)

c提高篇——模板&#xff08;下&#xff09;一、类模板二、类模板与函数模板区别三、类模板中成员函数创建时机四、类模板对象做函数参数一、类模板 类模板可以建立一个通用类&#xff0c;类中的成员数据类型可以不具体制定&#xff0c;用一个虚拟的类型来代表。 类模板的语法…