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

文章目录

  • 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 statsPerfCounter.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;}}@Overridepublic 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开头时,则交给父类去加载:

@Overridepublic 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方法

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

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

相关文章

【Python基础篇】【19.异常处理】(附案例,源码)

异常处理 异常处理常见异常elsefinallyraise获取异常信息sys.exc_info()traceback 处理异常基本原则assert断点调试两种方式Debugger窗口各图标的含义1.Show Execution Point &#xff08;Alt F10&#xff09;2.Step Over&#xff08;F8&#xff09;3.Step Into &#xff08;F…

【线性代数】通过矩阵乘法得到的线性方程组和原来的线性方程组同解吗?

一、通过矩阵乘法得到的线性方程组和原来的线性方程组同解吗&#xff1f; 如果你进行的矩阵乘法涉及一个线性方程组 Ax b&#xff0c;并且你乘以一个可逆矩阵 M&#xff0c;且产生新的方程组 M(Ax) Mb&#xff0c;那么这两个系统是等价的&#xff1b;它们具有相同的解集。这…

k8s二进制部署2

部署 Worker Node 组件 //在所有 node 节点上操作 #创建kubernetes工作目录 mkdir -p /opt/kubernetes/{bin,cfg,ssl,logs} #上传 node.zip 到 /opt 目录中&#xff0c;解压 node.zip 压缩包&#xff0c;获得kubelet.sh、proxy.sh cd /opt/ unzip node.zip chmod x kubelet.…

CodeWhisperer——轻松使用一个超级强大的工具

CodeWhisperer 简介 CodeWhisperer是亚⻢逊云科技出品的一款基于机器学习的通用代码生成器&#xff0c;可实时提供代码建议。 CodeWhisperer有以下几个主要用途&#xff1a; 解决编程问题&#xff0c;提供代码建议&#xff0c;学习编程知识等等&#xff0c;并且CodeWhisper…

2022 年全国职业院校技能大赛高职组云计算正式赛卷第二场-容器云

2022 年全国职业院校技能大赛高职组云计算赛项试卷 云计算赛项第二场-容器云 目录 2022 年全国职业院校技能大赛高职组云计算赛项试卷 【赛程名称】云计算赛项第二场-容器云 【任务 1】容器云平台搭建[5 分] 【任务 2】容器云应用部署&#xff1a; Docker Compose 编排部署[7.0…

【ZYNQ】教你用 Vivado HLS 快速设计一个 IP

Xilinx 推出的 Vivado HLS 工具可以直接使用 C、C或 System C 来对 Xilinx 系列的 FPGA 进行编程&#xff0c;从而提高抽象的层级&#xff0c;大大减少了使用传统 RTL 描述进行 FPGA 开发所需的时间。 Vivado HLS 的功能简单地来说就是把 C、C 或 SystemC 的设计转换成 RTL 实…

Python自动化测试:选择最佳的自动化测试框架

在开始学习python自动化测试之前&#xff0c;先了解目前市场上的自动化测试框架有哪些&#xff1f; 随着技术的不断迭代更新&#xff0c;优胜劣汰也同样发展下来。从一开始工具型自动化&#xff0c;到现在的框架型&#xff1b;从一开始的能用&#xff0c;到现在的不仅能用&…

element el-table实现可进行横向拖拽滚动

【问题】表格横向太长&#xff0c;表格横向滚动条位于最底部&#xff0c;需将页面滚动至最底部才可左右拖动表格&#xff0c;用户体验感不好 【需求】基于elment的el-table组件生成的表格&#xff0c;使其可以横向拖拽滚动 【实现】灵感来源于这篇文章【Vue】表格可拖拽滚动&am…

每周一算法:区间覆盖

问题描述 给定 N N N个闭区间 [ a i , b i ] [a_i,b_i] [ai​,bi​]&#xff0c;以及一个线段区间 [ s , t ] [s,t] [s,t]&#xff0c;请你选择尽量少的区间&#xff0c;将指定线段区间完全覆盖。 输出最少区间数&#xff0c;如果无法完全覆盖则输出 − 1 -1 −1。 输入格式…

androj环境搭建_AS安装及运行源码

1、 jdk安装 安卓项目也是java开发的&#xff0c;运行在虚拟机上&#xff0c;安装jdk及运行的时候&#xff0c;就会自动生成虚拟机&#xff0c; jdk前面已经讲过&#xff0c;这里不在讲解 2、下载安装androj studio https://developer.android.google.cn/studio?hlzh-cn 下…

mysql原理---InnoDB统计数据是如何收集的

以下聚焦于 InnoDB 存储引擎的统计数据收集策略。 1.两种不同的统计数据存储方式 InnoDB 提供了两种存储统计数据的方式&#xff1a; (1). 永久性的统计数据 这种统计数据存储在磁盘上&#xff0c;也就是服务器重启之后这些统计数据还在。 (2). 非永久性的统计数据 这种统计数…

Java生态系统的进化:从JDK 1.0到今天

目录 前言 JDK 1.0&#xff1a;开启Java时代 JDK 1.1&#xff1a;Swing和内部类 JDK 1.2&#xff1a;Collections框架和JIT编译器 JDK 1.5&#xff1a;引入泛型和枚举 JDK 1.8&#xff1a;Lambda表达式和流 JDK 11以后&#xff1a;模块化和新特性 未来展望 总结 作者简…

Smartbi获工信部旗下赛迪网“2023行业信息技术应用创新产品”奖

近日&#xff0c;由工信部旗下的赛迪网、《数字经济》杂志共同主办的2023行业信息技术应用创新大会上&#xff0c;“信息技术应用创新成果名单”重磅揭晓&#xff0c;思迈特软件凭借“Smartbi 自然语言分析引擎”斩获“2023行业信息技术应用创新产品”大奖。 据了解&#xff0c…

JavaWeb——监听器Listener 过滤器Filter——韩顺平学习笔记

文章目录 JavaWeb 三大组件之监听器 ListenerListenerJavaWeb 的监听器ServletContextListener 监听器ServletContextAttributeListener 监听器其它监听器-使用较少HttpSessionListener 监听器HttpSessionAttributeListener 监听器ServletRequestListener 监听器ServletRequest…

YOLOv5算法进阶改进(8)— 引入GSConv + Slim Neck相结合的方式降低模型复杂性

前言:Hello大家好,我是小哥谈。在文章中,作者提出了一种新方法 GSConv 来减轻模型的复杂度并保持准确性。GSConv可以更好地平衡模型的准确性和速度。并且,提供了一种设计范式Slim Neck,以实现检测器更高的计算成本效益。实验过程中,与原始网络相比,改进方法获得了最优秀…

软件测试/测试开发丨Selenium的常用元素定位方法

Selenium是一个流行的开源框架&#xff0c;目前在 Web 自动化方面运用最为广泛的一个开源、无浏览器要求、可支持多语言、设计测试用例非常灵活的自动化测试框架。支持多种编程语言&#xff0c;并且能够模拟用户操作&#xff0c;例如点击、输入、提交等等。 在Selenium中&…

《深入理解JAVA虚拟机笔记》Java 运行时内存区域

程序计数器&#xff08;线程私有&#xff09; 程序计数器&#xff08;Program Counter Register&#xff09;是一块较小的内存空间&#xff0c;它可以看做是当前线程所执行的字节码的行号指示器。在 Java 虚拟机的概念模型里&#xff0c; 字节码解释器工作时就是通过改变这个计…

【Linux--多线程同步与互斥】

目录 一、线程互斥1.1相关概念介绍1.2互斥量mutex1.3互斥量接口1.3.1初始化互斥量1.3.2销毁互斥量1.3.3互斥量加锁1.3.4互斥量解锁1.3.5使用互斥量解决上面分苹果问题 1.4互斥原理 二、可重入与线程安全2.1相关概念2.2常见线程不安全的情况2.3常见不可重入的情况2.4 可重入与线…

深度解析 | 什么是超融合数据中心网络?

数据中心网络连接数据中心内部通用计算、存储和高性能计算资源&#xff0c;服务器间的所有数据交互都要经由网络转发。当前&#xff0c;IT架构、计算和存储技术都在发生重大变革&#xff0c;驱动数据中心网络从原来的多张网络独立部署向全以太化演进。而传统的以太网无法满足存…

Pycharm引用其他文件夹的py

Pycharm引用其他文件夹的py 方式1&#xff1a;包名设置为Sources ROOT 起包名的时候&#xff0c;需要在该文件夹上&#xff1a;右键 --> Mark Directory as --> Sources ROOT 标记目录为源码目录&#xff0c;就可以了。 再引用就可以了 import common from aoeweb impo…