java动态类加载学习
文章目录
静态代码块加载
整一个Person类,里面放静态代码块和动态代码块等等,看不同操作下,他们的调用情况
实例化一个类对象
new Person();
调用静态方法
Person.staticAction();
调用了静态代码块和静态方法
赋值
Person.id = 1;
这里只调用了静态代码块
所以静态代码块在类初始化
的时候就调用了,而其他的在类实例化
的时候才调用(比如构造代码块,无参构造函数)
Class c = Person.class;
这里只加载不初始化,所以不调用静态代码块
使用Class.forName
Class.forName("cc3.Person");
这里进行了初始化,调用了静态代码块
双亲委派
java有三种类加载器
1、启动类加载器(Bootstrap ClassLoader)
它是属于虚拟机自身的一部分,用C++实现的,主要负责加载<JAVA_HOME>\lib
目录中或被-Xbootclasspath指定的路径中的并且文件名是被虚拟机识别的文件。加载String java.lang这些
2、扩展类加载器(Extension ClassLoader)
它是Java实现的,独立于虚拟机,主要负责加载<JAVA_HOME>\lib\ext
目录中或被java.ext.dirs系统变量所指定的路径的类库。加载扩展包 ext
3、应用程序类加载器(Application ClassLoader)
它是Java实现的,独立于虚拟机。主要负责加载用户类路径(classPath)
上的类库,如果我们没有实现自定义的类加载器那这玩意就是我们程序中的默认加载器。加载用户自定义类
双亲委派的意思是如果一个类加载器需要加载类,那么首先它会把这个类请求委派给父类加载器去完成,每一层都是如此。一直递归到顶层,当父加载器无法完成这个请求时,子类才会尝试去加载。这里的双亲其实就指的是父类,没有mother。父类也不是我们平日所说的那种继承关系,只是调用逻辑是这样。
理解:加载一个类时,先一直委派到顶层,从最顶层开始,去相应的文件夹中找相应的class文件,没有就往子类中找,其中第三层Application ClassLoader
就加载我们用户自己写的类
类的继承关系
ctrl+aalt+u查看类的继承关系
ClassLoader-->SecureClassLoader-->URLClassLoader-->AppClassLoader
loadClass-->findClass(重写的方法)-->defineClass(从字节码加载类)
加载类测试
注意,其中第二步加载类的时候不进行初始化
ClassLoader cl = ClassLoader.getSystemClassLoader();
Class<?> c = cl.loadClass("cc3.Person");// 不进行初始化
c.newInstance();
URLClassLoader任意类加载
先整一个Hello.java
import java.io.IOException;
public class Hello {
static {
System.out.println("hello sk1y");
try {
Runtime.getRuntime().exec("calc");
} catch (IOException e) {
e.printStackTrace();
}
}
}
然后javac
编译,将Hello.class文件移走,然后将源码中的Hello.java删除
file协议
注意第二行加载类的时候不进行初始化
URLClassLoader urlClassLoader = new URLClassLoader(new URL[]{new URL("file:///D:\\project\\java\\classes\\")});
Class<?> c = urlClassLoader.loadClass("Hello");
c.newInstance();
http协议
本地起一个http服务
URLClassLoader urlClassLoader = new URLClassLoader(new URL[]{new URL("http://127.0.0.1:9999/")});
Class<?> c = urlClassLoader.loadClass("Hello");
c.newInstance();
调用成功
所以如果我们可以控制URLClassLoader类的参数,那么我们就可以进行任意类的调用
jar协议
将Hello.class打包为jar包
jar cf Hello.jar Hello.class
jar协议和http协议也可以配合着使用
URLClassLoader urlClassLoader = new URLClassLoader(new URL[]{new URL("jar:file:///D:\\project\\java\\classes\\Hello.jar!/")});
Class<?> c = urlClassLoader.loadClass("Hello");
c.newInstance();
ClassLoader类的defineClass任意方法调用
这个方法是protected属性的,需要通过反射调用
ClassLoader cl = ClassLoader.getSystemClassLoader();
Method defineClass = ClassLoader.class.getDeclaredMethod("defineClass", String.class, byte[].class, int.class, int.class);
defineClass.setAccessible(true);
//这里要指定加载的类
byte[] code = Files.readAllBytes(Paths.get("D:\\project\\java\\classes\\Hello.class"));
Class c = (Class) defineClass.invoke(cl,"Hello",code,0,code.length);
c.newInstance();
运行结果
Unsafe类的defineClass
不能直接获取类对象,会有一个安全检查
但是可以通过theUnsafe
成员变量来得到类对象
使用Unsafe类的defineClass方法
ClassLoader cl = ClassLoader.getSystemClassLoader();
Class c = Unsafe.class;
Field theUnsafeField = c.getDeclaredField("theUnsafe");
theUnsafeField.setAccessible(true);
//获取Unsafe类对象
Unsafe o = (Unsafe) theUnsafeField.get(null);
byte[] code = Files.readAllBytes(Paths.get("D:\\project\\java\\classes\\Hello.class"));
//加载
Class<?> hello = o.defineClass("Hello", code, 0, code.length, cl, null);
hello.newInstance();
运行结果
(在Spring里面Unsafe类对象可以直接生成)