Fork me on GitHub

自定义一个类加载器代码实现

自定义一个类加载器代码实现

目标:

  • 自定义一个类加载器,对硬盘上的某.class文件进行加载。
  • 代码验证此某.class文件是由那个类加载器完成加载的。
  • 代码验证,通过 new 类加载的操作,能够实现热加载的功能。
  1. 首先写一个 HelloWorld 类,javac 拿到的 .class文件放入指定路径(自定义)中。
1
2
3
4
5
6
7
8
9
// 路径:/Users/lee/Desktop/gg/HelloWorld.class
public class HelloWorld {
static{
System.out.println("starting...");
}
public String welcome(){
return "Hello lees";
}
}
  1. 自定义一个类加载器,代码如下:
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
50
51
52
53
54
55
56
57
58
59
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;

public class defineNewClassLoader extends ClassLoader {
// class 加载的默认路径
private final static Path DEFAULT_CLASS_DIR =
Paths.get("/Users/lee/Desktop/gg", "classloader1");
private final Path classDir;

// 三个重载的构造器
public defineNewClassLoader() {
super();
this.classDir = DEFAULT_CLASS_DIR;
}
public defineNewClassLoader(String dir) {
super();
this.classDir = Paths.get(dir);
}
public defineNewClassLoader(String dir, ClassLoader parent) {
super(parent);
this.classDir = Paths.get(dir);
}

// 重写 findClass 方法,将 class 文件转换成字节数组,
// 然后调用父类的 defineClass 方法。
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
byte[] classBytes = this.readClassBytes(name);
if (null == classBytes || classBytes.length == 0) {
throw new ClassNotFoundException("can not load the class: " + name);
}
return this.defineClass(name, classBytes, 0, classBytes.length);
}

// 转换成字节数组的方法
private byte[] readClassBytes(String name) throws ClassNotFoundException {
// 将包名分隔符转换成文件路径分隔符
String classPath = name.replace(".", "/");
Path classFullPath = classDir.resolve(Paths.get(classPath + ".class"));
if (!classFullPath.toFile().exists()) {
// path下不存在该文件
throw new ClassNotFoundException("The class " + name + " not found.");
}
try (ByteArrayOutputStream baos = new ByteArrayOutputStream()) {
Files.copy(classFullPath, baos); // 字节迁移,bytes 从 File 移动到 stream
return baos.toByteArray(); //stream 转字节数组
} catch (IOException e) {
throw new ClassNotFoundException("load the class " + name + " occur error.", e);
}
}

@Override
public String toString() {
return "My classLoader";
}
}
  1. 最后写一个测试类:
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
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

public class MyClassLoaderTest {
public static void main(String[] args) throws ClassNotFoundException,
IllegalAccessException,
InstantiationException,
NoSuchMethodException,
InvocationTargetException {
// new 出自定义的类加载器,拿到 Class<?> clazz 对象
defineNewClassLoader myClassLoader = new defineNewClassLoader();
Class<?> clazz = myClassLoader.loadClass("org.written.program.多线程.HelloWorld");

// 将所需类加载打印到 console
printClassLoader(myClassLoader,clazz);

// 执行 Method
executeMethod(clazz);

System.out.println("=====================");
// 二次加载,查看打印效果
defineNewClassLoader myClassLoader2 = new defineNewClassLoader();
Class<?> clazz2 = myClassLoader2.loadClass("org.written.program.多线程.HelloWorld");
printClassLoader(myClassLoader2,clazz2);
executeMethod(clazz2);
}


private static void printClassLoader(defineNewClassLoader classLoader,Class<?> clazz) {
System.out.println("自定义 cl 的 cl 是:" + classLoader.getClass().getClassLoader());
System.out.println("待处理的 clazz 的 cl 是:" + clazz.getClassLoader());
System.out.println("Test类的 cl 是:" + MyClassLoaderTest.class.getClassLoader());
}

private static void executeMethod(Class<?> clazz) throws IllegalAccessException,
InstantiationException,
NoSuchMethodException,
InvocationTargetException {

Object helloWorld = clazz.newInstance();
System.out.println(helloWorld);
Method welcomeMethod = clazz.getMethod("welcome");
String result = (String) welcomeMethod.invoke(helloWorld);
System.out.println("result:" + result);
}
}

console 输出结果为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
自定义 clcl 是:sun.misc.Launcher$AppClassLoader@18b4aac2
待处理的 clazz 的 cl 是:sun.misc.Launcher$AppClassLoader@18b4aac2
Test类的 cl 是:sun.misc.Launcher$AppClassLoader@18b4aac2
starting...
org.written.program.多线程.HelloWorld@34a245ab
result:Hello lees
=====================
自定义 clcl 是:sun.misc.Launcher$AppClassLoader@18b4aac2
待处理的 clazz 的 cl 是:sun.misc.Launcher$AppClassLoader@18b4aac2
Test类的 cl 是:sun.misc.Launcher$AppClassLoader@18b4aac2
org.written.program.多线程.HelloWorld@6e8cf4c6
result:Hello lees

Process finished with exit code 0
  • executeMethod方法被注释,那么 HelloWorld 类的静态代码块将不会被执行(仅完成加载,未进入初始化过程),即 starting不会被打印出来。
  • 做了两次 new MyCL 的操作,发现它们的类加载器没变过,但是得到的 Class 类完全不同。说明,同一份.class文件,同样的全限定名称,MyCL不同时,也会加载两种类文件,实现了热加载的功能。

整体流程如图:

分析:不管是 Test 主类、自定义类加载器类还是待处理的.class文件,这里都是通过 APPClassLoader 进行加载的,通过上边的 console 输出也能证明这一点。
我们定义的加载器,是作为初始类加载器使用的,真正发挥作用的还是 AppClassLoader。

参考文章:《Java 高并发编程详解》第10章164页

-------------The End-------------