自定义类加载器的使用
根据前文,不管是Bootstrap ClassLoader还是ExtClassLoader等,这些类加载器都只是加载指定的目录下的jar包或者资源。如果在某种情况下,我们需要动态加载一些东西呢?
比如从D盘某个文件夹加载一个class文件,这个路径是输入的而非程序运行时配置参数。
或者从网络上下载class主内容然后再进行加载,这样可以吗?
包的,通过自定义类加载器完全可以实现。
我们使用两个栗子来感受自定义类加载器的使用场景。
本地动态指定加载类路径
如何自定义
- 编写一个类,笔者设置为
DiskClassLoader
,继承 ClassLoader(抽象类)
- 重写
ClassLoader.findClass()
-> DiskClassLoader.findClass()
- 在重写的
DiskClassLoader.findClass()
中调用ClassLoader.defineClass()
用以加载类
ClassLoader.defineClass()
,此方法在编写自定义classLoader的时候很重要,他的作用是将class字节码信息转化为Class对象
注:一个ClassLoader创建时如果没有指定parent,那么它的parent默认为AppClassLoaders
!!!注意代码中的注释喔!!!
测试用例
Test.java -> D:\Users\Dan\tmp\Test.java
1
2
3
4
5
6
7
|
package com.frank.test;
public class Test {
public void say(){
System.out.println("Say Hello");
}
}
|
使用java编译
1
|
D:\Users\Dan\tmp>"D:\Program Files\Java\jdk1.8.0_181\bin\javac.exe" -d . D:\Users\Dan\tmp\Test.java
|
此时D:\Users\Dan\tmp\
内容如下
1
2
3
4
5
6
|
./
├── Test.java
└── com
└── frank
└── test
└── Test.class
|
编写ClassLoader
DiskClassLoader
源码如下
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
|
package com.study.java;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
public class DiskClassLoader extends ClassLoader {
private String mLibPath;
public DiskClassLoader(String path) {
// TODO Auto-generated constructor stub
mLibPath = path;
}
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
// TODO Auto-generated method stub
// 此时因为路径人为控制,包名已知等条件,这里直接硬编码了
mLibPath = mLibPath + "\\com\\frank\\test";
String fileName = "Test.class";
File file = new File(mLibPath,fileName);
try {
FileInputStream is = new FileInputStream(file);
ByteArrayOutputStream bos = new ByteArrayOutputStream();
int len = 0;
try {
while ((len = is.read()) != -1) {
bos.write(len);
}
} catch (IOException e) {
e.printStackTrace();
}
byte[] data = bos.toByteArray();
is.close();
bos.close();
return defineClass(name,data,0,data.length);
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return super.findClass(name);
}
}
|
编写测试开启类
ClassLoaderTest
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
|
package com.study.java;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
public class ClassLoaderTest {
public static void main(String[] args) {
// TODO Auto-generated method stub
//创建自定义classloader对象。
DiskClassLoader diskLoader = new DiskClassLoader("D:\\Users\\Dan\\tmp");
try {
//加载class文件
Class c = diskLoader.loadClass("com.frank.test.Test");
if(c != null){
try {
Object obj = c.newInstance();
Method method = c.getDeclaredMethod("say",null);
//通过反射调用Test类的say方法
method.invoke(obj, null);
} catch (InstantiationException | IllegalAccessException | NoSuchMethodException | SecurityException | IllegalArgumentException | InvocationTargetException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
} catch (ClassNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
|
运行测试开启类

成功反射到目标say()
并运行
ClassLoader加载加密类
(假设)我们的Class字节码文件很值钱,但是容易被传播,我们又想要实现必须要给了钱的人才能解密
的方案,这个时候就可以对我们的字节码文件进行加密
测试用例
延续使用上方的测试用例
工具类
用来加密测试用例生成的字节码文件
FileUtils
,源码如下
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
|
package com.study.encode;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
public class FileUtils {
public static void test(String path){
File file = new File(path);
try {
FileInputStream fis = new FileInputStream(file);
FileOutputStream fos = new FileOutputStream(path+"en");
int b = 0;
int b1 = 0;
try {
while((b = fis.read()) != -1){
//每个byte都加密 加密密钥为数字2
//处理:每一个byte异或数字2
fos.write(b ^ 2);
}
fos.close();
fis.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
} catch (FileNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
|
测试一下
1
2
3
|
public static void main(String[] args) {
FileUtils.test("D:\\Users\\Dan\\tmp\\com\\frank\\test\\Test.class");
}
|
这时候目录结构中多出了一个被加密后的字节码文件你Test.classen
,删除原来的Test.class
就得到了
1
2
3
4
5
6
|
./
├── Test.java
└── com
└── frank
└── test
└── Test.classen
|
可解密的ClassLoader
DeClassLoader
源码如下
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
60
61
62
63
64
65
66
67
68
|
package com.study.encode;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
public class DeClassLoader extends ClassLoader {
private String mLibPath;
public DeClassLoader(String path) {
// TODO Auto-generated constructor stub
mLibPath = path;
}
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
// TODO Auto-generated method stub
String fileName = getFileName(name);
File file = new File(mLibPath,fileName);
try {
FileInputStream is = new FileInputStream(file);
ByteArrayOutputStream bos = new ByteArrayOutputStream();
int len = 0;
byte b = 0;
try {
while ((len = is.read()) != -1) {
//!!!!!!!
//解密过程在这里
//将数据异或一个数字2以进行解密
b = (byte) (len ^ 2);
bos.write(b);
}
} catch (IOException e) {
e.printStackTrace();
}
byte[] data = bos.toByteArray();
is.close();
bos.close();
return defineClass(name,data,0,data.length);
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return super.findClass(name);
}
//获取要加载 的class文件名
private String getFileName(String name) {
// TODO Auto-generated method stub
int index = name.lastIndexOf('.');
if(index == -1){
return name+".classen";
}else{
return name.substring(index+1)+".classen";
}
}
}
|
测试开启类
Main
源码如下
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
|
package com.study.encode;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
public class Main {
public static void main(String[] args) {
DeClassLoader diskLoader = new DeClassLoader("D:\\Users\\Dan\\tmp\\com\\frank\\test");
try {
//加载class文件
Class c = diskLoader.loadClass("com.frank.test.Test");
if(c != null){
try {
Object obj = c.newInstance();
Method method = c.getDeclaredMethod("say",null);
//通过反射调用Test类的say方法
try {
method.invoke(obj, null);
} catch (InvocationTargetException e) {
throw new RuntimeException(e);
}
} catch (InstantiationException | IllegalAccessException
| NoSuchMethodException
| SecurityException |
IllegalArgumentException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
} catch (ClassNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
// public static void main(String[] args) {
// FileUtils.test("D:\\Users\\Dan\\tmp\\com\\frank\\test\\Test.class");
// }
}
|
运行结果

将DeClassLoader
中的b = (byte) (len ^ 2);
注释掉,就可以删除解密过程,其他不动的情况下,解密自然失败了,失败截图如下

参考
https://blog.csdn.net/briblue/article/details/54973413