Java动态编译

JDK1.6加入了编译API,开发者可以在代码中调用API,从而动态编译Java源代码,下面举个例子来讲解具体用法。

直接上源码

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
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
package cn.o.test;

import javax.tools.*;
import java.io.*;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.tools.JavaCompiler.CompilationTask;

public class DynamicCompiler {

/**
* 匹配包名的正则
*/
private static final String REG_EX = "(?<=package\\s).*(?=;)";
/**
* 缓存已编译的脚本的名称和Hash值,Map<类名, Hash值>
*/
private static Map<String, String> classCache = new HashMap<>();

/**
* 编译源码,返回Class
* @srcFilePath: 源文件绝对路径
*/
public static Class compile(String srcFilePath) throws Exception {

// 实例化编译器
JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
// 实例化源文件管理器
StandardJavaFileManager fileManager
= compiler.getStandardFileManager(null, null, null);
// 实例化日志管理器
DiagnosticCollector<JavaFileObject> diagnostics
= new DiagnosticCollector<JavaFileObject>();

// 通过文件名获取类名
File srcFile = new File(srcFilePath);
String className = srcFile.getName();
// 解析包名,获得类全名
String packageName = getPackageName(srcFilePath);
String fullClassName = packageName + "." + className;

// 获取源码的hash
String hash = String.valueOf(readFile(srcFilePath).hashCode());
// 通过比较类名对应的Hash,判断类是否已编译过,且是否更改
String originalHash = classCache.get(fullClassName);
// 如果源码未做改动,则直接返回已编译过的类
if (originalHash != null && hash.equals(originalHash)) {
return Class.forName(fullClassName);
}

// 获取要编译的编译单元
List<File> srcFileList = new ArrayList<File>();
srcFileList.add(srcFile);
Iterable<? extends JavaFileObject> compilationUnits
= fileManager.getJavaFileObjectsFromFiles(srcFileList);
/*
* 编译参数,
* -encoding:编码方式为utf-8
* -classpath:依赖包和依赖的classpath绝对路径,用半角冒号:隔开
* -d:编译的目标绝对路径
*/
String dependencies = "/lib/ABC.jar:/lib/DEF.jar:/lib/GHI.jar";
String targetPath = "/compile/source/to/this/path";
Iterable<String> options = Arrays.asList(
"-encoding", "utf-8",
"-classpath", dependencies,
"-d", targetPath);
// 获取编译任务
CompilationTask compilationTask = compiler.getTask(
null, fileManager, diagnostics, options, null, compilationUnits);
// 运行编译任务
Boolean result = compilationTask.call();
if (!result) {
// 此处可以DEBUG查看或打印diagnostics对象的内容,从而查看编译失败原因
throw new Exception(fullClassName + "编译失败。");
}
// 销毁文件管理器
fileManager.close();
// 储存编译的类的hash
classCache.put(fullClassName, hash);
return Class.forName(fullClassName);
}

/**
* 从源文件中获得包名
* @param srcPath
*/
private static String getPackageName(String srcPath) throws Exception {
String result = null;
BufferedReader br;
Pattern packagePattern = Pattern.compile(REG_EX);
try {
br = new BufferedReader(new FileReader(srcPath));
String data = br.readLine();
while (data != null) {
if (data.indexOf("package") != -1) {
Matcher m = packagePattern.matcher(data);
if (m.find()) {
result = m.group();
}
break;
}
data = br.readLine();
}
br.close();
} catch (IOException e) {
throw new Exception("获取包名失败");
}
return result;
}

/**
* 读取源码内容
* @srcFilePath: 源文件绝对路径
*/
private static String readFile(String srcFilePath) {
BufferedReader br = null;
String line = null;
StringBuffer buf = new StringBuffer();
try {
br = new BufferedReader(new InputStreamReader(new FileInputStream(srcFilePath), "UTF-8"));
while ((line = br.readLine()) != null) {
buf.append(line).append("\n");
}
} catch (Exception e) {

} finally {
// 关闭流
if (br != null) {
try {
br.close();
} catch (IOException e) {
br = null;
}
}
}
return buf.toString();
}
}

调用方式

1
2
3
4
5
public static void main(String[] args) {
Class klass = DynamicCompiler.compile("/absolute/path/of/java/source/file.java");
Object object = klass.newInstance();
// do something
}
分享到