【基础篇】六、自定义类加载器打破双亲委派机制

文章目录

  • 1、ClassLoader抽象类的方法源码
  • 2、打破双亲委派机制:自定义类加载器重写loadclass方法
  • 3、自定义类加载器默认的父类加载器
  • 4、两个自定义类加载器加载相同限定名的类,不会冲突吗?
  • 5、一点思考

1、ClassLoader抽象类的方法源码

ClassLoader类的核心方法:

这里是引用

从一句常写的代码开始看ClassLoader这个抽象类的源码:

ClassLoader classLoader = TestJvm.class.getClassLoader();
Class<?> clazz = classLoader.loadClass("com.plat.A");

loadClass方法源码:

public Class<?> loadClass(String name) throws ClassNotFoundException {
   //传入了false,往下跟
    return loadClass(name, false);
}

往下跟:

protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
     //加synchronized,防止多线程下重复加载
     synchronized (getClassLoadingLock(name)) {
         // 先检查类是否已被加载,findLoadedClass往下跟是调用native方法
         Class<?> c = findLoadedClass(name);
         if (c == null) {
             long t0 = System.nanoTime();
             try {
                 //类加载器的parent属性不为空,即有父加载器
                 if (parent != null) {
                     //自己调自己,这里体现的是向上查找
                     c = parent.loadClass(name, false);
                 } else {
                     //去启动类加载器里找,往下跟是native方法
                     c = findBootstrapClassOrNull(name);
                 }
             } catch (ClassNotFoundException e) {
                 // ClassNotFoundException thrown if class not found
                 // from the non-null parent class loader
             }
			 //三个加载器用完了,c还是为空
             if (c == null) {
                 // If still not found, then invoke findClass in order
                 // to find the class.
                 long t1 = System.nanoTime();
  				 //那就调用findClass方法,它是LoadClass抽象类的空方法,给子类去实现,这是自定义类加载器的切入点和扩展点
                 c = findClass(name);

                 // this is the defining class loader; record the stats
                 PerfCounter.getParentDelegationTime().addTime(t1 - t0);
                 PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
                 PerfCounter.getFindClasses().increment();
             }
         }
         //resolve为false,则不执行resolveClass方法,即不要类生命周期里的连接阶段
         if (resolve) {
             resolveClass(c);
         }
         return c;
     }
}

源码摘要:

在这里插入图片描述

关于以上源码,做个简单的验证,上面提到loadClass源码传了false,导致没有进行类生命周期的连接阶段:

public class A02{

	static {
		System.out.println("类A02正在进行初始化阶段");
	}
}
public class LoaderTest {
    public static void main(String[] args) throws ClassNotFoundException, IOException {
        ClassLoader classLoader = LoaderTest.class.getClassLoader();
        Class<?> clazz = classLoader.loadClass("com.plat.pay.A02");
    }
}

在这里插入图片描述

发现A02类的static代码块没被执行,这就是因为这里的loadClass方法,其源码传入了false,导致resolveClass方法不执行,即后面的连接、初始化阶段都没了,而static代码块在初始化阶段执行,这和Class.forName是有本质区别的,后者连接和初始化阶段都执行。

2、打破双亲委派机制:自定义类加载器重写loadclass方法

创建一个类,继承ClassLoader抽象类,重写loadClass方法:

import org.apache.commons.io.IOUtils;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.security.ProtectionDomain;
import java.util.regex.Matcher;

/**
 * 打破双亲委派机制 - 自定义类加载器
 */

public class BreakClassLoader1 extends ClassLoader {

    private String basePath;
    private final static String FILE_EXT = ".class";

    public void setBasePath(String basePath) {
        this.basePath = basePath;
    }

    private byte[] loadClassData(String name)  {
        try {
            String tempName = name.replaceAll(".", Matcher.quoteReplacement(File.separator));
            FileInputStream fis = new FileInputStream(basePath + tempName + FILE_EXT);
            try {
                return IOUtils.toByteArray(fis);
            } finally {
                IOUtils.closeQuietly(fis);
            }

        } catch (Exception e) {
            System.out.println("自定义类加载器加载失败,错误原因:" + e.getMessage());
            return null;
        }
    }

    @Override
    public Class<?> loadClass(String name) throws ClassNotFoundException {
        byte[] data = loadClassData(name);
        return defineClass(name, data, 0, data.length);
    }

   
}

写个测试类:

在这里插入图片描述

跟进报错的第二行preDefineClass方法,发现自定义加载器的父类ClassLoader中做了校验,以java开头抛安全异常,也是安全的体现:

在这里插入图片描述

换一个普通命名的包:

在这里插入图片描述

报错找不到Object,加载A类前,会先加载其父类Object,此时可拷贝个Object的class到我这个目录,也可以修改自定义加载器的实现,java开头时,则交给父类去加载:

@Override
    public Class<?> loadClass(String name) throws ClassNotFoundException {
    	//如果全类名是java开头的类,就让父类加载器去办
        if(name.startsWith("java.")){
            return super.loadClass(name);
        }
        byte[] data = loadClassData(name);
        return defineClass(name, data, 0, data.length);
    }

再测试:

public class LoaderTest {
    public static void main(String[] args) throws Exception {
        BreakClassLoader1 classLoader = new BreakClassLoader1();
        classLoader.setBasePath("D:\\springboot\\pay\\target\\classes\\");
        Class<?> clazz = classLoader.loadClass("com.plat.pay.A02");
        System.out.println(clazz.getClassLoader());
    }
}

加载成功:

在这里插入图片描述

查看自定义加载器的父加载器:

BreakClassLoader1 classLoader = new BreakClassLoader1();
System.out.println(classLoader);
System.out.println(classLoader.getParent());
//System.out.println(BreakClassLoader1.getSystemClassLoader());

发现其父加载器是应用程序加载器:

在这里插入图片描述

在这里插入图片描述

3、自定义类加载器默认的父类加载器

复习super关键字:当构造方法的第一行,既没有this(……)又没有super(……)的时候,默认会有一个super(),表示通过当前子类的构造方法调用其父类的无参构造方法。自定义类加载器父类ClassLoader类的无参构造:

在这里插入图片描述

this是在调用本类的另一个构造方法:

在这里插入图片描述

传入的getSystemClassLoader值为一个AppClassLoader,因此,自定义类加载器默认的父类加载器。

4、两个自定义类加载器加载相同限定名的类,不会冲突吗?

不会冲突,在同一个Java虚拟机中,只有相同类加载器+相同的类限定名才会被认为是同一个类。

public class LoaderTest {
    public static void main(String[] args) throws Exception {
        BreakClassLoader1 classLoader1 = new BreakClassLoader1();
        classLoader1.setBasePath("D:\\springboot\\pay\\target\\classes\\");
        Class<?> clazz1 = classLoader1.loadClass("com.plat.pay.A02");

        BreakClassLoader1 classLoader2 = new BreakClassLoader1();
        classLoader2.setBasePath("D:\\springboot\\pay\\target\\classes\\");
        Class<?> clazz2 = classLoader2.loadClass("com.plat.pay.A02");
        //关于==:
        //如果是基本数据类型的比较,则比较的是值。
        //如果是包装类或者引用类的比较,则比较的是对象地址
        //关于equals:
        //equals方法没有重写还是比较对象地址
        //equals方法重写后比较啥,是看重写的逻辑是啥
        System.out.println(clazz1 == clazz2);
    }
}

结果为false,即同一个类,被两个自定义加载器加载,是两个不同的Class对象
在这里插入图片描述

采用Arthas验证,在上面程序后面加一句输入,卡着让程序别退出运行:

System.in.read();

出现两次,即一个类如果由两个自定义类加载器分别去加载,在程序中会出现两个不同的class对象:

在这里插入图片描述
小补充:

//设置线程上下文的类加载器
Thread.currentThread().setContextClassLoader(new BreakClassLoader1());
//com.plat.broken.BreakClassLoader1@6537cf78
System.out.println(Thread.currentThread().getContextClassLoader());

5、一点思考

上面提到的,一个类被两个自定义类加载器去加载,会有两个class对象,那问题来了,双亲委派机制呢?不查?这是因为上面我写的自定义类加载器,直接重写了loadClass方法,而重写的实现里,没有原来的查父类(参考上面loadClass本来的源码),而是直接去指定路径把class读成一个二进制流传入。因此,如果 想在不打破双亲委派机制的前提下自定义类加载器,那正确姿势应该是重写loadClass内部调用的findClass方法,且常规开发自定义类加载器,重写的也是findClass方法,而非loadClass方法

在这里插入图片描述

比如需要在数据库中去加载字节码文件,就重写findClass方法,将数据库中的数据获取到内存中,变成一个二进制的字节数组,然后传入到defineClass方法

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【Linux】进程查看|fork函数|进程状态

&#x1f984; 个人主页——&#x1f390;开着拖拉机回家_Linux,大数据运维-CSDN博客 &#x1f390;✨&#x1f341; &#x1fa81;&#x1f341;&#x1fa81;&#x1f341;&#x1fa81;&#x1f341;&#x1fa81;&#x1f341; &#x1fa81;&#x1f341;&#x1fa81;&am…

ADRC-跟踪微分器TD的Maltab实现及参数整定

目录 问题描述&#xff1a; 跟踪微分器TD基本概念&#xff1a; Matlab及其实现&#xff1a; 跟踪效果&#xff1a; 例1&#xff1a;跟踪信号 sin(t) 0.5*rand(1,1)。 例2&#xff1a;跟踪部分时段为方波的信号&#xff0c;具体形式见代码get_command。 参数整定&#xf…

⭐Unity 读取本地图片再区域裁剪

现在需求是将本地的图片读取之后再区域截图成新的图片 话不多说直接上代码 using UnityEngine; using System.IO;public class LocalRegionCapture : MonoBehaviour {public string fullScreenImagePath "Assets/SavedImages/fullScreenScreenshot.png";public str…

蓝桥杯2020年5月青少组Python程序设计国赛真题

1、 上边是一个算法流程图,最后输出的b的值是() A.377 B.987 C.1597 D.2584 2、 3、如果整个整数X本身是完全平方数,同时它的每一位数字也都是完全平方数我们就称X 是完美平方数。前几个完美平方数是0、1、4、9、49、100、144......即第1个完美平方数是0,第2个是 1,第3个…

问卷调查反应偏差消除技巧:提高数据准确性的实用方法

回应偏差会对您的调查结果产生不利影响。以下是您可以采取的措施来对抗偏差。调查之所以成为一个有力的工具&#xff0c;是因为它能够获取关于目标受众的真实信息和数据&#xff0c;而不是依靠试错方法。然而&#xff0c;为了使这些数据有用&#xff0c;它必须是无误差的&#…

SAP PP 配置学习(四)

分类 维护类的层次 分配 批量更改特征值

如何使用设计模式来解决类与类之间调用过深的问题。

我们将使用责任链模式和装饰者模式的组合。 考虑一个简化的餐厅订单处理系统&#xff0c;其中包括服务员&#xff08;Waiter&#xff09;、厨师&#xff08;Chef&#xff09;和收银员&#xff08;Cashier&#xff09;。订单从服务员开始&#xff0c;然后传递给厨师&#xff0c…

JUC并发编程 09——队列同步器AQS

目录 一.Lock接口 1.1Lock的使用 1.2Lock接口提供的 synchronized 不具备的主要特性 1.3Lock接口的所有方法 二.队列同步器(AQS) 2.1队列同步器的接口与示例 2.2AQS实现源码分析 ①同步队列 ②获取锁 ③释放锁 一.Lock接口 说起锁&#xff0c;你肯定会想到 synchron…

css 超过一行/多行显示省略号... - 附示例

效果 1、超过一行 2、超过多行 - 以两行为例 二、示例代码 1、超过一行 margin: 20px; width: 50px; border: 1px solid red; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; 2、超过多行 - 以两行为例 margin: 20px; width: 50px; border: 1px solid r…

docker小白第八天

docker小白第八天 redis常规安装 前期已经配好了阿里云容器镜像加速库 docker search redis docker pull redis:6.0.8 docker images redis:6.0.8启动容器,并进入容器的命令行界面 docker run -d -p 6379:6379 redis:6.0.8 docker ps docker exec -it 容器id /bin/bash验证…

202362读书笔记|《茉莉花》——时间滤去一切伤痛,让智慧和美好永垂不朽

202362读书笔记|《茉莉花》——时间滤去一切伤痛&#xff0c;让智慧和美好永垂不朽 《茉莉花》作者卓凌&#xff0c;插图美&#xff0c;文字也好读的一本诗集&#xff0c;很值得一读&#xff01; 部分节选如下&#xff1a; 生活是一列穿山越岭的火车&#xff1b;享受过程&#…

老用户可买:腾讯云轻量应用服务器2核2G4M带宽118元一年,3年540元

它急了&#xff0c;腾讯云急了&#xff0c;继阿里云推出99元新老用户同享的云服务器后&#xff0c;腾讯云轻量应用服务器2核2G4M配置也支持新老用户同享了&#xff0c;一年118元&#xff0c;3年540元&#xff0c;老用户也能买&#xff0c;50GB SSD系统盘&#xff0c;300GB 月流…

计算机毕业设计 基于SpringBoot的高校竞赛管理系统的设计与实现 Java实战项目 附源码+文档+视频讲解

博主介绍&#xff1a;✌从事软件开发10年之余&#xff0c;专注于Java技术领域、Python人工智能及数据挖掘、小程序项目开发和Android项目开发等。CSDN、掘金、华为云、InfoQ、阿里云等平台优质作者✌ &#x1f345;文末获取源码联系&#x1f345; &#x1f447;&#x1f3fb; 精…

模式识别与机器学习-特征选择和提取

模式识别与机器学习-特征选择和提取 特征选择一些距离测度公式独立特征的选择准则一般特征的散布矩阵准则 离散K-L变换 谨以此博客作为复习期间的记录。 常见分类问题的流程&#xff0c;数据预处理和特征选择提取时机器学习环节中最重要的两个流程。这两个环节直接决定了最终性…

初见 Amazon Q

前言 如果今年要写一篇年终总结的话&#xff0c;生成式 Ai 一定是绕不过的一个话题&#xff0c;自从去年的 chatGPT 火爆全球后&#xff0c;今年各种生成式 Ai 的产品络绎不绝地出现大众视线&#xff0c;版本迭代的速度也是非常快&#xff0c;大家甚至开始在自己的生活和工作中…

SpringBoot整合Spring-Security 认证篇(保姆级教程)

本文项目基于以下教程的代码版本&#xff1a; https://javaxbfs.blog.csdn.net/article/details/135195636 代码仓库: https://gitee.com/skyblue0678/springboot-demo 为了跟shiro区别开&#xff0c;新建了一个分支&#xff1a; 目录 &#x1f339;1、友善问候一下 Spring …

【HBase】——简介

1 HBase 定义 Apache HBase™ 是以 hdfs 为数据存储的&#xff0c;一种分布式、可扩展的 NoSQL 数据库。 2 HBase 数据模型 • HBase 的设计理念依据 Google 的 BigTable 论文&#xff0c;论文中对于数据模型的首句介绍。 Bigtable 是一个稀疏的、分布式的、持久的多维排序 m…

万字长文谈自动驾驶bev感知(一)

文章目录 prologuepaper listcamera bev :1. Lift, Splat, Shoot: Encoding Images from Arbitrary Camera Rigs by Implicitly Unprojecting to 3D2. M2BEV: Multi-Camera Joint 3D Detection and Segmentation with Unified Birds-Eye View Representation3. BEVDet: High-Pe…

Oracle开发经验总结

文章目录 1. 加注释2. 增加索引3. nvl(BOARDCODE&#xff0c;100)>004. 去掉distinct可以避免hash比较&#xff0c;提高性能5. like模糊查询优化(转化为instr()函数)6. SQL计算除数为0时&#xff0c;增加nullif判断7. 分页8. 查看执行计划9. <if test"productCode !…

231227-9步在RHEL8.8配置本地yum源仓库

Seciton 1&#xff1a;参考视频 RHEL8配置本地yum源仓库-安徽迪浮_哔哩哔哩_bilibili Seciton 2&#xff1a;具体操作 &#x1f3af; 第1步&#xff1a;查看光驱文件/dev/sr0是否已经挂载&#xff1f;此处已挂在 [lgklocalhost ~]$ df -h &#x1f3af; 第1步&#xff1a;查看…