ClassLoader和类加载机制
时间:2022-05-03
本文章向大家介绍ClassLoader和类加载机制,主要内容包括类加载:、类加载器:、2、Extention Class Loader:加载JAVA_HOME/lib/ext目录下的或-Djava.ext.dirs指定目录下的jar包。、3、System Class Loader:加载classpath或者-Djava.class.path指定目录下的类或jar包。、ClassLoader各司其职,加载在不同路径下的class文件,值得注意的是,类加载采用的是双亲委托的设计模式,即传入一个类限定名,逐层向上到Bootstrap Class Loader中查找,如果找到即返回,若没有找到,则在Extention Class Loader中找,若还没有找到则在System Class Loader下找,即classpath中,如果还没有找到,则调用findClass(name)方法,执行用户自己的类加载逻辑(可能在其他的地方)、ClassLoader中的几个重要的方法:、1、loadClass(String name, boolean resolve):加载类的方法,在jdk1.2以前需要重写该方法实现用户自己的逻辑,1.2以后为了向下兼容,仍然可以重写该方法,但是建议用户将自己的加载逻辑实现在findName(name)中。这样系统先向上寻找能否加载到该类,如果加在不到,将调用用户自定义的findName函数加载对象.、2、ClassLoader getParent() :可以返回委托的父类加载器。在你自定义加载器找不到相应类的时候,可以调用此方法,不过在ClassLoader的默认实现中,ClassLoader先判断父类加载器是否可以加载,然后再调用用户自定义的findClass方法。、3、 resolveClass():若resolve参数为true的时候,我们需要调用该函数,resolve我们的classLoader。、4、ClassLoader getSystemClassLoader():提供了一个直接访问系统classloader的方法。、下面我将以一个例子来阐述如何使用ClassLoader,自定义的ClassLoader将加载被加密的类,而且这个类存储的路径不在ClassPath中,也不可以被Bootstrap Class Loader和Extention Class Loader加载,在实际应用中,可以是网络中传递过来的加密字节流,抑或着是实现脚本的热部署操作。、在main函数中先将一个编译好的class文件加密后存储在非classpath路径下,然后用自定义classLoader进行加载,加密为了简单起见,使用的是异或加密,利用的原理是二进制的数经过两次异或操作后得到的值是相同的。路径也使用的绝对路径,大家可以根据需要自行进行修改,有什么问题可以继续交流,谢谢。、基本概念、基础应用、原理机制和需要注意的事项等,并结合实例形式分析了其使用技巧,希望通过本文能帮助到大家理解应用这部分内容。
最近在做项目的过程中,由于系统需要提供一个对外接口,使系统使用者可以以脚本的形式提交自己的代码,每个用户可以在系统规范的约束下编写脚本,由系统去执行用户的代码,实现了热部署。
什么叫热部署呢?简单来说就是把代码当成U盘或者外设一样即插即用,每个用户可以维护自己的解决方案(也就是一段脚本,一个单独的类),在更新修改解决方案的过程中而不需要重新编译启动整个系统。我们采用的方案就是GroovyClassLoader,我主要讲一讲自己对ClassLoader的理解和使用。
02
类加载与类加载器
类加载:
类加载的过程就是将Class文件中描述的各种信息加载到虚拟机中,供程序后期运行和使用的。
类加载的生命周期主要分为五个步骤:
1、加载:
通过一个类的全限定名来获取描述此类的二进制字节流
将这个字节流所代表的静态存储结构转化为方法去的运行时数据结构
在内存中生成一个代表这个类的java.lang.Class 对象,作为方法区的各种数据类型的入口
2、验证
为了确保Class文件的字节流中包含的信息符合当前虚拟机的要求,并且不会危害到自身的安全。包括文件格式验证,元数据验证,字节码验证,符号引用验证。
3、准备
为变量分配内存,设置类变量的初始值。
4、解析
将常量池中的符号应用替代为直接引用。
5、初始化
是类加载生命周期的最后一个过程,执行类中定义的java程序代码
类加载器:
在前面的类加载过程中,大部分动作都是完全由虚拟机主导和控制的。而类加载器使得用户可以在加载的过程中参与进来,结合前面的内容,类加载器就是将“通过一个类的全限定名来获取描述此类的二进制字节流”这个动作放到java虚拟机外部来实现。将主动权交给程序猿。
类加载器和这个类本身确定了其在java虚拟机中的唯一性,每一个类加载器都有一个独立的类命名空间,也就意味着,如果比较两个类是否相等,只有在这两个类是由同一个类加载器加载的前提下才有意义,否则,即使这两个类来源于同一个Class文件,被同一个虚拟机加载,只要加载他们的类加载器不同,那么这两个类就注定不相同。
1、Bootstrap Class Loader:负责加载JAVA_HOME/lib目录下或-Xbootclasspath指定目录的jar包;
2、Extention Class Loader:加载JAVA_HOME/lib/ext目录下的或-Djava.ext.dirs指定目录下的jar包。
3、System Class Loader:加载classpath或者-Djava.class.path指定目录下的类或jar包。
ClassLoader各司其职,加载在不同路径下的class文件,值得注意的是,类加载采用的是双亲委托的设计模式,即传入一个类限定名,逐层向上到Bootstrap Class Loader中查找,如果找到即返回,若没有找到,则在Extention Class Loader中找,若还没有找到则在System Class Loader下找,即classpath中,如果还没有找到,则调用findClass(name)方法,执行用户自己的类加载逻辑(可能在其他的地方)
ClassLoader中的几个重要的方法:
1、loadClass(String name, boolean resolve):加载类的方法,在jdk1.2以前需要重写该方法实现用户自己的逻辑,1.2以后为了向下兼容,仍然可以重写该方法,但是建议用户将自己的加载逻辑实现在findName(name)中。这样系统先向上寻找能否加载到该类,如果加在不到,将调用用户自定义的findName函数加载对象.
/**
* @param name 类名字
* @param resolve 是否解析,如果只是想知道该class是否存在可以设置该参数为false
* @return 返回一个class泛型
* @throws ClassNotFoundException
*/
protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException {
/**
* getClassLoadingLock(name)
* 为类的加载操作返回一个锁对象。为了向后兼容,这个方法这样实现:如果当前的classloader对象注册了并行能力,
* 方法返回一个与指定的名字className相关联的特定对象,否则,直接返回当前的ClassLoader对象。
*/
synchronized (getClassLoadingLock(name)) {
// 首先查看class是否已经被加载过
Class<?> c = findLoadedClass(name);
if (c == null) {
long t0 = System.nanoTime();
try {
//如果父加载器不为空,则委托给父加载器去加载
if (parent != null) {
c = parent.loadClass(name, false);
} else {
/**
* 如果父加载器为空,说明父加载器已经是Bootstrap ClassLoader了,则直接使用根加载器加载,也就是使用虚拟机加
* 载器加载
*/
c = findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) {
// ClassNotFoundException thrown if class not found
// from the non-null parent class loader
}
//如果以上的加载器在自己的路径上面都没有加载到,则调用findClass(name)调用用户自定义的加载器
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();
}
}
//根据resolve参数决定是否解析该类
if (resolve) {
resolveClass(c);
}
return c;
}
}
2、ClassLoader getParent() :可以返回委托的父类加载器。在你自定义加载器找不到相应类的时候,可以调用此方法,不过在ClassLoader的默认实现中,ClassLoader先判断父类加载器是否可以加载,然后再调用用户自定义的findClass方法。
3、 resolveClass():若resolve参数为true的时候,我们需要调用该函数,resolve我们的classLoader。
4、ClassLoader getSystemClassLoader():提供了一个直接访问系统classloader的方法。
03
废话少说上代码!
下面我将以一个例子来阐述如何使用ClassLoader,自定义的ClassLoader将加载被加密的类,而且这个类存储的路径不在ClassPath中,也不可以被Bootstrap Class Loader和Extention Class Loader加载,在实际应用中,可以是网络中传递过来的加密字节流,抑或着是实现脚本的热部署操作。
package com.siyu;
import java.io.*;
public class ClassLoaderTest extends ClassLoader {
//自定义加载器加载该路径下面的文件 private String directory;
public ClassLoaderTest(String directory) {
this.directory = directory;
}
/**
* 重写findClass,用户可以做以下的事情
* 1.可以加载boot、ext、system加载器所加载不了的路径下的文件
* 2.可以解密加密后的class文件
*/
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
//解密密钥
byte key = (byte) 1;
//加密文件的路径
String fileName = directory + name + ".class";
File file = new File(fileName);
byte[] decryptedByte = readFromFile(file);
//解密为原始的class文件
for (int i = 0; i < decryptedByte.length; i++) {
decryptedByte[i] = (byte) (decryptedByte[i] ^ key);
}
//defineClass实现了链接阶段的验证等
return defineClass(null, decryptedByte, 0, decryptedByte.length);
}
private byte[] readFromFile(File fileName) {
try {
byte[] bytes = null;
FileInputStream fin = new FileInputStream(fileName);
int i;
if ((i = fin.read()) != -1) {
//初始化数组大小和文件大小一样
bytes = new byte[fin.available()];
fin.read(bytes);
}
return bytes;
} catch (FileNotFoundException e) {
e.printStackTrace();
return null;
} catch (IOException e) {
e.printStackTrace();
return null;
}
}
private byte[] encrypt(byte[] bytes) {
byte key = (byte) 1;
//依次加密的代码
for (int i = 0; i < bytes.length; i++) {
bytes[i] = (byte) (bytes[i] ^ key);
//利用异或加密
}
return bytes;
}
public void encryptFile(String fileName, String directory) {
try {
String name = fileName.substring(fileName.lastIndexOf("\") + 1, fileName.length() - 6);
//加密文件的路径
String destFileName = directory + "encryted" + name + ".class";
//如果加密文件不存在则创建加密文件
File f = new File(destFileName);
if (f == null) {
f.createNewFile();
}
//加密
byte[] encryptedByte = encrypt(readFromFile(new File(fileName)));
FileOutputStream fos = new FileOutputStream(destFileName);
//把加密后的字节写入到加密文件中
fos.write(encryptedByte);
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
//设置加密路径
ClassLoaderTest classLoaderTest=new ClassLoaderTest("C:\EncryptedClass\");
//将test.class加密后存储到EncryptedClass目录下 classLoaderTest.encryptFile("C:\Users\jasonchu.zsy\IdeaProjects\BoKeTest\out\production\BoKeTest\com\siyu\test.class"
,"C:\EncryptedClass\");
try {
Class<?> t=classLoaderTest.loadClass("encrytedtest");
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
在main函数中先将一个编译好的class文件加密后存储在非classpath路径下,然后用自定义classLoader进行加载,加密为了简单起见,使用的是异或加密,利用的原理是二进制的数经过两次异或操作后得到的值是相同的。路径也使用的绝对路径,大家可以根据需要自行进行修改,有什么问题可以继续交流,谢谢。
- JavaWeb10-request&response你不得不学(1)
- dg的奇怪问题终结和分区问题答疑 (r7笔记第77天)
- 最近让我焦灼的四个问题(有解) (r7笔记第76天)
- JavaWeb10-reques;response你不得不学(2)
- 很多人比较纠结的约束和索引的关系(r7笔记第75天)
- JavaWeb09-Servlet(Java真正的全栈开发)
- JavaWeb08-XML,tomcat,HTTP轻松入门
- JavaWeb07-JDBC(Java真正的全栈开发)
- 编程思想 之「操作符」
- Hyperledger fabric Chaincode 开发详解
- 字节码文件的内部结构之谜
- 基因功能富集分析-R语言
- CRM, C4C和Hybris的工作流简介
- 想进大厂?50个多线程面试题,你会多少?(一)
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- C++核心准则T.2:使用模板表现可以适用于多种参数类型的算法
- C++核心准则T.3:使用模板表现容器和范围
- Markdown的时序图、流程图、和甘特图+Hexo的相关配置
- 记一次Fcitx5的安装
- 解决KDE下KDE Wallet重装系统后每次登陆需要输入密码
- KDE下完美的Unity桌面体验,扔掉active window control
- C++核心准则T.5:结合使用泛型和面向对象技术应该增强它们的效果而不是成本
- C++核心准则T.10:为所有的模板参数定义概念
- C语言二级指针用法之模拟句柄用途
- Linux解压缩文件
- C++核心准则T.11:只要可能就使用标准概念
- 给pugjs的stun主题添加canvas时钟
- C++核心准则T.12:声明局部变量类型时,概念比auto更好
- Arch Linux切换rEFInd开机引导程序
- C++核心准则T.13:对于简单的,单类型参数概念,使用缩略记法更好