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

文章目录

  • 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://www.hqwc.cn/news/303488.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【量化】蜘蛛网策略复现

文章目录 蜘蛛网策略研报概述持仓数据整理三大商品交易所的数据统一筛选共有会员清洗数据计算研报要求数据全部代码 策略结果分析无参数策略有参数策略正做反做 MSD技术指标化 蜘蛛网策略 策略来自《东方证券-股指期货趋势交易之蜘蛛网策略——从成交持仓表中捕捉知情投资者行为…

Python - 深夜数据结构与算法之 Divide Conquer Backtrack

目录 一.引言 二.分治与回溯简介 1.Divide & Conquer 分治 2.BackTrack 回溯 三.经典算法实战 1.Combination-Of-Phone [17] 2.Permutations [46] 3.Permutations-2 [47] 4.Pow-X [50] 5.N-Queen [51] 6.Combinations [78] 7.Sub-Sets [78] 8.Majority-Elemen…

系列八、VMWare无法启动CentOS7问题排查 解决

一、VMWare无法启动CentOS7 1.1、问题描述 今天在测试代码的时候&#xff0c;需要用到Linux&#xff0c;然后就打开VMWare进行启动&#xff0c;但是启动的时候发现无法启动起来&#xff0c;报了一个如下的错误&#xff1a; 出现了问题那就要解决问题&#xff0c;然后想起来前几…

Kruskal(克鲁斯卡尔)算法总结

知识概览 克鲁斯卡尔算法适用于稀疏图求最小生成树&#xff0c;时间复杂度为O(mlogm)。 例题展示 题目链接 Kruskal算法求最小生成树 859. Kruskal算法求最小生成树 - AcWing题库https://www.acwing.com/problem/content/861/ 代码 #include <iostream> #include &l…

C语言实验1:C程序的运行环境和运行C程序的方法

一、算法原理 这是学C语言的入门&#xff0c;并不需要很高深的知识&#xff0c;一个hello world 或者一个简单的加法即可 二、实验要求 了解所用的计算机系统的基本操作方法&#xff0c;学会独立使用该系统。 了解在该系统上如何编辑、编译、连接和运行一个C程序。 通过运…

Sectigo和Certum的IP证书区别

IP证书是比较特别的一款数字证书。大多数SSL数字证书都是针对域名站点的数字证书&#xff0c;比如单域名SSL证书、多域名SSL证书和通配符SSL证书&#xff0c;而IP证书针对的是只拥有公网IP地址的站点。签发IP证书的CA认证机构并不多&#xff0c;Sectigo和Certum旗下都有IP证书&…

【51单片机系列】DS1302时钟模块

本文是关于DS1302时钟芯片的相关介绍。 文章目录 一、 DS1302时钟芯片介绍二、DS1302的使用2.1、DS1302的控制寄存器2.2、DS1302的日历/时钟寄存器2.3、片内RAM2.4、DS1302的读写时序 三、SPI总线介绍四、DS1302使用示例 一、 DS1302时钟芯片介绍 DS1302是DALLAS公司推出的涓流…

阿赵UE学习笔记——4、新建关卡

阿赵UE学习笔记目录 大家好&#xff0c;我是阿赵。   之前介绍了虚幻引擎的常用窗口功能&#xff0c;这次开始创建游戏内的世界了。首先先从创建关卡开始。 一、创建新关卡 在使用UE引擎制作游戏&#xff0c;首先要有一个场景作为基础&#xff0c;这个场景在UE里面成为关卡。…

python量化开发【中级进阶】

一、量化思想&#xff1a; 赌球&#xff1a;如果你是赌球老板&#xff0c;如何赚1个亿的小目标 二、量化交易 量化交易是指以先进的数学模型替代人为的主观判断&#xff0c;利用计算机技术从庞大的历史数据中海选能带来超额收益的多种“大”事件以制定策略&#xff0c;极大地减…

Fastjson中关于json的处理与配置

Fastjson是一个Java语言编写的高性能的JSON处理器,由阿里巴巴公司开发。 无依赖&#xff0c;不需要例外额外的jar&#xff0c;能够直接跑在JDK上。 FastJson在复杂类型的Bean转换Json上会出现一些问题&#xff0c;可能会出现引用的类型&#xff0c;导致Json转换出错&#xff0c…

SpringBoot源码搭建

文章目录 源码下载搭建项目构建学习博客 源码下载 需要环境 &#xff1a; JDK 1.8Maven 3.5Spring Boot 1.x.x: Gradle 版本建议为2.9或更高版本。Spring Boot 2.x.x: Gradle 版本建议为4.x.x或更高版本。 GitHub 从v2.3.x开始&#xff0c;SpringBoot开始强制用Gradle构建项…

Session的使用详解(创建,获取和销毁)

文章目录 Session的使用详解&#xff08;创建&#xff0c;获取和销毁&#xff09;1、为什么使用session,与cookie的区别2、session是什么3、session的常用方法4、session的构造和获取代码演示SetSessionServlet.javaGetSessionServlet.javaweb.xml运行结果如下: 5、销毁session…