类动态加载
类加载与反序列化
反序列是利用的readObject()
方法重写,而类加载是为什么 ?
类加载
Java 程序在运行前需要先编译成
class文件
,Java 类初始化的时候会调用java.lang.ClassLoader
加载类字节码,ClassLoader
会调用 JVM 的 native 方法(defineClass0/1/2
)来定义一个java.lang.Class
实例
Javac
javac是用于将源码文件.java
编译成对应的字节码文件.class
具体实现步骤 : 源码 -> 词法分析器组件(生成token流)-> 词法分析器组件(语法树)-> 语义分析器组件(注解语法树)-> 代码生成器组件(字节码)
类加载过程
先在方法区找.class
信息,有的话直接调用,没有的话则使用类加载器加载到方法区(静态成员放在静态区,非静态成员放在非静态区)静态代码快在类加载时自动执行代码,非静态的不执行,先父类后子类,先静态后飞静态,静态方法和飞静态方法都是被动调用(不调就不执行)
当运行某个类的时候,如果发现这个类还没有被加载,那么就会如上过程进行加载
Java 程序是由 class 文件组成的一个完整的应用程序,在程序运行时,并不会一次性加载所有的 class 文件进入内存,而是通过 Java 的类加载机制(ClassLoader)进行动态加载,从而转换成 java.lang.Class 类的一个实例
动态加载
Java 类加载方式分为 显式
和 隐式
显式
即我们通常使用Java反射
或者ClassLoader
来动态加载一个类对象隐式
指的是类名.方法名()
或new
类实例
显式
类加载方式也可以理解为类动态加载,可以自定义类加载器去加载任意的类
Class.forname加载
public class main {
public static void main(String[] args) throws ClassNotFoundException {
Class.forName("Persion");
/*
这是静态代码快
*/
}
}
我们发现,当使用Class.forName()
调用时,也会进行初始化,可以查看一下底层代码实现
它底层调用了一个forName0方法
它的forName0方法是一个native方法,它是用C或C++实现的,但是上方还存在一个forName的重载方法,它就可以判断是否需要初始化(存在三个参数,第一个是类,第二个是否初始化,第三个是类加载器)
public class main {
public static void main(String[] args) throws ClassNotFoundException {
Class<?> c = Class.forName("Persion", false, ClassLoader.getSystemClassLoader()); // 此时就不会在初始化了 这里使用的是系统类加载器
// c.newInstance(); // 这是初始化操作
}
}
ClassLoader
通过ClassLoader
来实现类加载
public class main {
public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException {
ClassLoader cl = ClassLoader.getSystemClassLoader();
Class<?> aClass = cl.loadClass("Persion");
aClass.newInstance();
}
}
这里通过aClass.newInstance()
来实现类初始化,那么可以考虑的一个问题是能否通过在别的地方写的一些类,然后通过ClassLoader来加载,那么这就是可以执行任意类(比反射实现的效果还要多,反射在一些私有的是不允许调用的)
类加载器的种类
Bootstarp ClassLoader
启动类加载器 : 这个类加载器负责将一些核心的、被JVM识别的类加载进来,用C++实现,与JVM一体的**,**用于加载一些java.lang 目录下的
是无法直接获取到的
Extension ClassLoader
扩展类加载器 : 这个类加载器用来加载 Java 的扩展库,其目录在
jre\lib\ext
Application ClassLoader
App类加载器/系统类加载器 : 用于加载当前目录下我们自己定义编写的类
User ClassLoader
用户自己实现的加载器 : 当实际需要自己掌控类加载过程时才会用到,一般没有用到
双亲委托机制
如果一个类加载器收到了类加载的请求,首先不会自己去尝试加载这个类,而是把这个请求委派给父类加载器去完成,每一层次的类加载器都是如此,因此所有的加载请求最终都应该传送到顶层的启动类加载器中,只有当父加载器反馈自己无法完成这个加载请求时(它的搜索范围中没有找到所需要的类)子加载器才会自己去加载
- 首先,检查请求的类是否已经加载过了
- 未加载,则请求父类加载器去加载对应路径下的类
- 如果加载不到,才由下面的子类依次去加载
java.lang.String -> 本地加载器 -> 扩展加载器 -> 根(顶层)加载器
类加载机制
- 在调用loadClass处打入断点
可以看到它的默认类加载器是AppClassLoader
- 由于传入的是一个参数,它会调用到本应该调用的是Launcher类中的loadClass方法,但是它斌没有接收一个参数的loadClass方法,就会去调用父类的loadClass方法
-
ClassLoader
中的loadClass(String name)
会返回一个两个参数的loadClass方法
- 之后将两个参数的返回给子类(Launcher类)
- 子类执行完成后,又会调用父类的loadClass方法
这里就相当于是执行了双亲委派的流程
先会判断是否是ExtClassLoader
的父属性,如果是的话就回去指定它的loadClass方法,很明显这里是会进入ExtClassLoader
类中执行它的loadClass
但是通过搜索会发现它并不存在loadClass方法,去父类找URLClassLoader
中找,它又会去调用它的父类loadClass方法
又回到了ClassLoader.loadClass()
但是此时的加载类已经变成了ExtClassLoader
- 继续往下执行,当它再次判断时,由于它类加载的父属性是
BootstropClassLoader
,它是由C编写的,在java中是null,所以不会在进入执行else
之后又会去findBootstrapClassOrNull()中寻找,还是为false
- 之后继续执行findClass(),而它是一个重写的方法,正常在子类中执行
而当前的加载类还是ExtClassLoader
,但是它也没有该方法,只能去它对应的父类URLClassLoader
中找
因为这里使用的是ExtClassLoader
,它是用来加载java的扩展库,所以这里是找不到的,为null,到此ExtClassLoader
执行完成,退回ApplicationClassLoader
- 程序退回到ClassLoader.loadClass()判断处,继续往下执行
这里就是以ApplicationClassLoader
身份去执行findClass
- 由于
ApplicationClassLoader
用于加载我们自己定义编写的类,所以这里肯定是可以找到的
ucp是一个URLClassPath类,通过它在当前的ClassLoader加载路径中找
- 通过
upc.getResource()
找到之后会再去调用一个defindClass()
URLClassLoader
类重写了defineClass()
最终还是调用了一个多参数的defindClass()
- 继续执行查看,发现会调到
SecureClassLoader
类中,而它是URLClassLoader
的父类
它也返回了一个调用defineClass()
- 继续追踪,查看其最终落脚
最终在ClassLoader
中的defineClass
停止,它又执行了一个defineClass1()
,它的definClass()方法中可以看到存在5个参数,第一个是类名、第二个参数是字节码、第三个参数是起始位置、第四个是长度、第五个是保护域
而defineClass1
是一个native类型,最终通过它来完成整个类的加载
任意类加载
ClassLoader.defineClass
import main.Test;
import java.lang.reflect.Method;
import java.net.URL;
import java.net.URLClassLoader;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.security.ProtectionDomain;public class classLoaderTest {
public static void main(String[] args) throws Exception {
ClassLoader cl = ClassLoader.getSystemClassLoader();Method defineClassLoader = ClassLoader.class.getDeclaredMethod("defineClass", String.class, byte[].class, int.class, int.class); // 反射获取defineClass方法 defineClassLoader.setAccessible(true); // 关闭安全检测 byte[] code = Files.readAllBytes(Paths.get("H:\\tmp\\ss.class")); // 将class转为字节 Class c = (Class) defineClassLoader.invoke(cl, "ss", code, 0, code.length); // 加载类 c.newInstance(); // 创建类 }
}
URLClassLoader
可使用jar/file/http
协议进行加载类
import main.Test;
import java.net.URL;
import java.net.URLClassLoader;public class classLoaderTest {
public static void main(String[] args) throws Exception {
URLClassLoader file = new URLClassLoader(new URL[]{new URL("file:///H:\tmp\")}); // 通过file协议
Class<?> aClass = file.loadClass("ss");
aClass.newInstance();// 使用file协议 URLClassLoader file = new URLClassLoader(new URL[]{new URL("file:///H:\\tmp\\")}); // 通过file协议 Class<?> aClass = file.loadClass("ss"); aClass.newInstance(); // 使用http协议 URLClassLoader http = new URLClassLoader(new URL[]{new URL("http://localhost:80/")}); Class<?> aClass = http.loadClass("ss"); aClass.newInstance(); //使用jar协议 }
}
Unsafe.defineClass
import main.Test;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.net.URL;
import java.net.URLClassLoader;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.security.ProtectionDomain;public class classLoaderTest {
public static void main(String[] args) throws Exception {ClassLoader cl = ClassLoader.getSystemClassLoader(); byte[] code = Files.readAllBytes(Paths.get("H:\\tmp\\ss.class")); Class c = Unsafe.class; Field theUnsafe = c.getDeclaredField("theUnsafe"); theUnsafe.setAccessible(true); Unsafe unsafe = (Unsafe) theUnsafe.get(null); Class c2 = (Class) unsafe.defineClass("ss", code, 0, code.length,cl,null); c2.newInstance(); }
}
标签:java,quot,ClassLoader,import,动态,class,加载 From: https://www.cnblogs.com/yuxingjiu/p/dynamic-load-2moceb.html