类加载器简述
类加载器 | 继承 | 加载类路径 |
---|---|---|
(引导)Bootstrap ClassLoader | %JAVA_HOME%\jre\lib\下的jar(jre核心库) | |
(扩展)ExtClassLoader | extends URLClassLoader | %JAVA_HOME%\jre\lib\ext目录下的jar |
(应用)AppClassLoader | extends URLClassLoader | 加载当前引用的classpath的所有类 |
Bootstrap ClassLoader是由C/C++编写的,它本身是虚拟机的一部分,所以它并不是一个JAVA类
。
双亲委托机制
某个特定的类加载器接收到类加载的请求时,会将加载任务委托给自己的父类,直到最高级父类引导类加载器(Bootstrap ClassLoader),如果父类能够加载就加载,不能加载则返回到子类进行加载。如果都不能加载则报错:ClassNotFoundException。
类加载器加载类对象的时候是
先逆向(查询)委托
是否已经加载
,已加载就采用,未加载就继续逆向去问父加载器;直到加载到引导类加载器(Bootstrap ClassLoader);
再顺序去尝试加载
,加载不到就继续顺下去给子加载器尝试加载的过程;最后都没有就报:ClassNotFoundException;
本质上是一个递归的过程,看下面的类加载器的三个方法的说明。
图片引用:https://blog.csdn.net/briblue/article/details/54973413
三个重要函数
(问:自定义类加载器怎么实现,其中哪个方法走双亲委派模型,(实现findclass方法,一般用defineclass用来加载外部类类对象),如何才能不走双亲委派。(重写loadclass方法))
三个重要函数:loadClass,findClass,defineClass
- loadClass:调用父类加载器的loadClass,加载失败则调用自己的findClass方法。(
对应逆序委托
) - findClass:根据名称读取文件存入字节数组。(
对应顺序加载
) - defineClass:把一个字节数组(二进制字节码类文件)转为Class对象
执行流程
最开始我们通过main中的(My)Classloader.loadClass()去加载类对象(不会执行静态代码块代码),loadClass这个方法会不断递归
去调用(父类类加载器)parent.loadClass(对应双亲委托机制的逆向查询委托过程
),loadClass一直向父类询问,直到找到或者父类为Bootstrap ClassLoader或空,然后执行findClass方法。如果父类没有抛出FileNotFoundException,这个时候返回其子类,已经找到;如果爸爸没有,抛出了FileNotFoundException,那么子类将会执行findClass,直到最开始发起loadClass方法的类加载器。这个过程本质上是一个递归的过程。
对应流程图:
对应源码:
为什么要自定义ClassLoader
因为系统的ClassLoader只会加载指定目录下的class文件,如果你想加载自己的class文件,那么就可以自定义一个ClassLoader.
而且我们可以根据自己的需求,对class文件进行加密和解密
。
(1)加密:Java代码可以轻易的被反编译,如果你需要把自己的代码进行加密以防止反编译,可以先将编译后的代码用某种加密算法加密,类加密后就不能再用Java的ClassLoader去加载类了,这时就需要自定义ClassLoader在加载类的时候先解密类,然后再加载。
(2)从非标准的来源加载代码:如果你的字节码是放在数据库、甚至是在云端,就可以自定义类加载器,从指定的来源加载类。
(3)以上两种情况在实际中的综合运用:比如你的应用需要通过网络来传输 Java 类的字节码,为了安全性,这些字节码经过了加密处理。这个时候你就需要自定义类加载器来从某个网络地址上读取加密后的字节代码,接着进行解密和验证,最后定义出在Java虚拟机中运行的类。
加密/解密类对象
加密工具类
采取二进制Class文件进行异或运算进行加密。
/**
* 类Class加密工具类
* **/
public class EncryptedClassUtil {
public static void main(String[] args) {
// TODO Auto-generated method stub
encrpt("C:\\Users\\cbry\\Desktop\\Hello.class", "C:\\Users\\cbry\\Desktop\\files\\Hello.class");
}
public static void encrpt(String src, String dest) {
FileInputStream fis = null;
FileOutputStream fos = null;
try {
fis = new FileInputStream(src);
fos = new FileOutputStream(dest);
int tempData = -1;
while((tempData = fis.read()) != -1) {
fos.write(tempData^0xff); //异或,0xff即为:11111111 , int8个字节对每个字节按位取反
}
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
if(fis != null) {
fis.close();
}
if(fos != null) {
fos.close();
}
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
加密前
使用jd-gui反编译Class文件, jd-gui下载地址:
加密后
位运算加密后输出的Hello.class文件无法被反编译。
自定义类加载器
解密二进制类文件
重写findClass()方法以及增加getClassData方法:
package com.cbry.classloader;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
public class MyClassLoader extends ClassLoader {
//指定路径
private String rootPath;
public MyClassLoader(String rootPath) {
this.rootPath = rootPath;
}
/**
* 重写findClass方法
*
* @param name The binary name of the class
* @return
* @throws ClassNotFoundException
*/
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
System.out.println("findClass: " + name);
Class<?> c = null;
// 获取该class文件字节码数组
byte[] classData = getClassData(name);
if (classData != null) {
try {
// 将class的字节码数组转换成Class类的实例
c = defineClass(name, classData, 0, classData.length);
} catch (ClassFormatError e){ // 加载错误
e.printStackTrace();
c = null;
}
}else{
throw new ClassNotFoundException(); // 如果字节数组为空,抛出异常
}
return c;
}
/**
* 将class文件转化为字节码数组
* @return
*/
private byte[] getClassData(String name) {
System.out.println("getClassData: " + name);
name = name.replaceAll("\\.", "/");
File file = new File(rootPath + "/" + name + ".class");
if (file.exists()) {
FileInputStream fis = null;
ByteArrayOutputStream bos = null;
try {
fis = new FileInputStream(file);
bos = new ByteArrayOutputStream();
int tempData = -1;
while((tempData = fis.read()) != -1) {
bos.write(tempData^0xff); //异或,0xff即为:11111111 , int8个字节对每个字节按位取反
}
return bos.toByteArray();
} catch (IOException e) {
e.printStackTrace();
return null;
} finally {
try {
if(fis != null) {
fis.close();
}
if(bos != null) {
bos.close();
}
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
} else {
return null;
}
}
}
主进程
public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IllegalAccessException, InstantiationException {
MyClassLoader mc = new MyClassLoader("C:\\Users\\cbry\\Desktop\\files\\");
Class<?> c = mc.loadClass("com.test.Hello");
Object obj = c.newInstance();
Method helloMethod = c.getDeclaredMethod("sayHello", null);
helloMethod.invoke(obj, null);
}
使用Hello直接在根目录下加载文件报错:
D:\eclipse-java-oxygen-R-win32-x86_64\JDK\bin\java.exe "-javaagent:D:\IntelliJ\IntelliJ IDEA Community Edition 2021.1\lib\idea_rt.jar=57447:D:\IntelliJ\IntelliJ IDEA Community Edition 2021.1\bin" -Dfile.encoding=UTF-8 -classpath D:\eclipse-java-oxygen-R-win32-x86_64\JDK\jre\lib\charsets.jar;D:\eclipse-java-oxygen-R-win32-x86_64\JDK\jre\lib\deploy.jar;D:\eclipse-java-oxygen-R-win32-x86_64\JDK\jre\lib\ext\access-bridge-64.jar;D:\eclipse-java-oxygen-R-win32-x86_64\JDK\jre\lib\ext\cldrdata.jar;D:\eclipse-java-oxygen-R-win32-x86_64\JDK\jre\lib\ext\dnsns.jar;D:\eclipse-java-oxygen-R-win32-x86_64\JDK\jre\lib\ext\jaccess.jar;D:\eclipse-java-oxygen-R-win32-x86_64\JDK\jre\lib\ext\jfxrt.jar;D:\eclipse-java-oxygen-R-win32-x86_64\JDK\jre\lib\ext\localedata.jar;D:\eclipse-java-oxygen-R-win32-x86_64\JDK\jre\lib\ext\nashorn.jar;D:\eclipse-java-oxygen-R-win32-x86_64\JDK\jre\lib\ext\sunec.jar;D:\eclipse-java-oxygen-R-win32-x86_64\JDK\jre\lib\ext\sunjce_provider.jar;D:\eclipse-java-oxygen-R-win32-x86_64\JDK\jre\lib\ext\sunmscapi.jar;D:\eclipse-java-oxygen-R-win32-x86_64\JDK\jre\lib\ext\sunpkcs11.jar;D:\eclipse-java-oxygen-R-win32-x86_64\JDK\jre\lib\ext\zipfs.jar;D:\eclipse-java-oxygen-R-win32-x86_64\JDK\jre\lib\javaws.jar;D:\eclipse-java-oxygen-R-win32-x86_64\JDK\jre\lib\jce.jar;D:\eclipse-java-oxygen-R-win32-x86_64\JDK\jre\lib\jfr.jar;D:\eclipse-java-oxygen-R-win32-x86_64\JDK\jre\lib\jfxswt.jar;D:\eclipse-java-oxygen-R-win32-x86_64\JDK\jre\lib\jsse.jar;D:\eclipse-java-oxygen-R-win32-x86_64\JDK\jre\lib\management-agent.jar;D:\eclipse-java-oxygen-R-win32-x86_64\JDK\jre\lib\plugin.jar;D:\eclipse-java-oxygen-R-win32-x86_64\JDK\jre\lib\resources.jar;D:\eclipse-java-oxygen-R-win32-x86_64\JDK\jre\lib\rt.jar;D:\A学习代码\leetcode\JVM\bin com.cbry.classloader.MyClassLoader
Exception in thread "main" java.lang.NoClassDefFoundError: Hello (wrong name: com/test/Hello)
at java.lang.ClassLoader.defineClass1(Native Method)
at java.lang.ClassLoader.defineClass(ClassLoader.java:763)
at java.lang.ClassLoader.defineClass(ClassLoader.java:642)
at com.cbry.classloader.MyClassLoader.findClass(MyClassLoader.java:35)
at java.lang.ClassLoader.loadClass(ClassLoader.java:424)
at java.lang.ClassLoader.loadClass(ClassLoader.java:357)
at com.cbry.classloader.MyClassLoader.main(MyClassLoader.java:86)
forName和ClassLoader
Class.forName(“className”)
其实这种方法调运的是:Class.forName(className,true,ClassLoader.getCallerClassLoader())方法
-
参数1:classNmae,需要加载类的名称
-
参数2:true,是否对class进行初始化(需要序列化initialize)
-
参数3:classLoader,对应的类加载器
ClassLoader.laodClass(“className”)
其实这种方法调用的是:ClassLoader.loadClass(name,false)方法
-
参数1:name,需要加载的类的名称
-
参数2: false,这个类加载以后是否需要去连接(不需要linking)
可见Class.forName除了将类的.class文件加载到jvm中之外,还会对类进行解释,执行类中的static块
、
而ClassLoader只干了一件事情,就是讲.class文件加载到jvm中,不会执行static中的内容,只有在newInstance才会去执行static