ClassLoader与热加载

非工程用,仅作原理展示。下面示例代码为同一个包”package lyyljs.hotclassloader;”。

1
2
3
public interface Printer {
public boolean print();
}
1
2
3
4
5
6
7
8
//测试用类
public class Test implements Printer{

public boolean print(){
System.out.println("Test Out");
return false;
}
}
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 HotClassLoader extends ClassLoader{

private String path;

public HotClassLoader(String path){
this.path = path;
}

/**
* 实现查找类
* @param name 类名,如果属于package,则为package.class
* @return 加载的class
*/
protected Class<?> findClass(String name){
//转换class名称为全路径,
String fullPath = path +
name.replace('.', File.separatorChar) + ".class";

byte[] cls = loadFile(fullPath);

//加载实际使用defineClass,加载字节码、解析、验证
return defineClass(name, cls, 0, cls.length);
}

/**
* 读取文件转换为byte[]
* @param filePath 文件路径,包含文件名
* @return
*/
private byte[] loadFile(String filePath){
try {
InputStream is = new FileInputStream(filePath);
ByteArrayOutputStream bos = new ByteArrayOutputStream();
byte[] buffer = new byte[2048];
int num = 0;
while ((num = is.read(buffer)) != -1) {
bos.write(buffer, 0, num);
}
return bos.toByteArray();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
return null;
}

}
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
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
public class ClassCompiler {

//JavaFileObject接口实现类,用于JavaCompiler编译源码
static class StrJavaFileObject extends SimpleJavaFileObject {

final String code;

protected StrJavaFileObject(String name, String code) {
//Kind枚举类位于JavaFileObject接口,Kind.SOURCE.extension是java文件的后缀(".java")
super(URI.create("string:///" + name.replace('.','/') + Kind.SOURCE.extension), Kind.SOURCE);
this.code = code;
}

@Override
//SimpleJavaFileObject未实现该方法,FileObject获取内容用
public CharSequence getCharContent(boolean ignoreEncodingErrors) {
return code;
}
}

//使用当前线程的资源绝对路径
public static String getDefaultOutPath(){
String outDir = "";
try {
File classPath = new File(Thread.currentThread().getContextClassLoader()
.getResource("").toURI());
outDir = classPath.getAbsolutePath() + File.separator;
} catch (URISyntaxException e1) {
e1.printStackTrace();
}
return outDir;
}

//编译Java文件,filePath是文件路径
public boolean compileJavaFile(String filePath){

JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();

Iterable<String> options = Arrays.asList("-d", getDefaultOutPath());

Iterable<? extends JavaFileObject> compilationUnits = compiler
.getStandardFileManager(null, null, null)
.getJavaFileObjectsFromFiles(Arrays.asList(new File(
filePath)));

return compiler.getTask(null, null,
null, options, null, compilationUnits).call();
}
// 将JavaCompiler.getTask搬过来
// CompilationTask getTask(Writer out, //additional output
// JavaFileManager fileManager,//null使用编译器标准文件管理器
// DiagnosticListener<? super JavaFileObject> diagnosticListener,//诊断监听器,null时使用默认方法报告诊断信息
// Iterable<String> options,//编译参数,null时没有编译参数
// Iterable<String> classes,//待编译类名,可为null
// Iterable<? extends JavaFileObject> compilationUnits);//待编译的代码
// getTask创建了一个编译任务,task.call()执行该编译任务

//编译类
//className是类名(无需带包名),javaCodes是该类的代码
public boolean compile(String className, String javaCodes) {
JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();

StrJavaFileObject srcObject = new StrJavaFileObject(className, javaCodes);
Iterable<? extends JavaFileObject> fileObjects = Arrays.asList(srcObject);

Iterable<String> options = Arrays.asList("-d", getDefaultOutPath());

return compiler.getTask(null, null,
null, options, null, fileObjects).call();
}

public static void main(String[] args){
String classPath = ClassCompiler.getDefaultOutPath();
String javaFilePath = "****\\Test.java";

String code = "package lyyljs.hotclassloader;"
+ "public class Test implements Printer {"
+ "public boolean print() {"
+ "System.out.println(\"Test1 Out\");"
+ "return true;"
+ "}}";

ClassCompiler cc = new ClassCompiler();

//一个ClassLoader不能重复加载某个class
HotClassLoader clsLoader1 = new HotClassLoader(classPath);
HotClassLoader clsLoader2 = new HotClassLoader(classPath);

boolean result = cc.compileJavaFile(javaFilePath);

if (!result){
System.out.println("compile Test failed!");
return;
}

Class clazz = clsLoader1.findClass("lyyljs.hotclassloader.Test");
try {
Printer p1 = (Printer) clazz.newInstance();
p1.print();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}

result = cc.compile("Test", code);
clazz = clsLoader2.findClass("lyyljs.hotclassloader.Test");

try {
Printer p2 = (Printer) clazz.newInstance();
p2.print();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
}
1
2
3
输出:
Test Out
Test1 Out

关于热加载方案可参见漫谈JVM热加载技术