Java实现从http中获取java源码进行编译并获取结果

原理

  • JavaCompiler

    Java编译器对象,这是核心对象,如果求简单的话,直接使用JavaCompiler.run(Object[] args)方法就可以了

    该对象的获取通过ToolProvider.getSystemJavaCompiler();

  • JavaCompiler.CompilationTask

    这个对象相比上面直接使用run方法来的强大,它可以对我们的Java编译过程进行更多的控制操作,比如源文件从哪里来,编译后的文件,编译过程出现的错误以及警告信息;同时该对象继承了Callable<Boolean>接口,因此,该对象在执行时其实是开启了一个线程在后台运行的,当我们调用该对象的call方法时开启,

    该对象的获取通过compiler.getTask(null, fileManager, diagnostics, options, null, fileObjects);,其中fileManager参数是控制源文件以及编译后的类文件的位置,diagnostics参数是记录编译过程中的信息,options参数是设置一些编译时的参数信息(可以参考javac命令),fileObjects参数是向task传入待compiler的java文件对象

  • DiagnosticCollector

    一个监听器,用于监听在编译过程中产生的信息

  • ForwardingJavaFileManager

    该对象是CompilationTask的一个参数,这个是用于控制源文件和类文件的位置

实现代码

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
/**
* @author liaochuntao
*/
public class ClassFileManager extends ForwardingJavaFileManager {

private JavaClassObject classObject;

protected ClassFileManager(JavaFileManager fileManager) {
super(fileManager);
}

// 更改编译后的文件的输出位置,将其输出到自定义的对象
@Override
public JavaFileObject getJavaFileForOutput(Location location, String className, JavaFileObject.Kind kind, FileObject sibling)
throws IOException {
if (classObject == null) {
classObject = new JavaClassObject(className, kind);
}
return classObject;
}

public JavaClassObject getJavaClassObject() {
return classObject;
}
}
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
/**
* @author liaochuntao
*/
public class JavaClassObject extends SimpleJavaFileObject {

protected ByteArrayOutputStream bos = new ByteArrayOutputStream();

protected JavaClassObject(String name, Kind kind) {
super(URI.create("string:///" + name.replace('.', '/') + kind.extension), kind);
}

public byte[] getBytes() {
return bos.toByteArray();
}

@Override
public OutputStream openOutputStream() throws IOException {
return bos;
}

//当被回收时关闭流
@Override
protected void finalize() throws Throwable {
super.finalize();
bos.close();
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
/**
* @author liaochuntao
*/
public class JavaSourceObject extends SimpleJavaFileObject {

private String code;
private ByteArrayOutputStream os;

protected JavaSourceObject(String name, String code) {
super(URI.create("string:///" + name.replace('.','/') + Kind.SOURCE.extension), Kind.SOURCE);
this.code = code;
this.os = new ByteArrayOutputStream();
}

@Override
public CharSequence getCharContent(boolean ignoreEncodingErrors) throws IOException {
return code;
}

@Override
public OutputStream openOutputStream() throws IOException {
return os;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
/**
* @author liaochuntao
*/
public class TensorClassLoader extends URLClassLoader {

public TensorClassLoader(ClassLoader parent) {
super(new URL[0], parent);
}

public Class loadClass(String name, JavaClassObject jco) {
byte[] classData = jco.getBytes();
return this.defineClass(name, classData, 0, classData.length);
}
}
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
/**
* @author liaochuntao
*/
@Slf4j
@Component
@Scope("singleton")
public final class CompilerPool {

// 获取机器的可用的CPU核心数
private final static int COMPILER_POOL_SIZE = Runtime.getRuntime().availableProcessors();
private final static ConcurrentLinkedQueue<TensorJavaCompiler> COMPILER_POOL = new ConcurrentLinkedQueue<>();
private final static ClassLoader parentClassloader = CompilerPool.class.getClassLoader();

static {
for (int i = 0; i < COMPILER_POOL_SIZE; i ++) {
COMPILER_POOL.add(new TensorJavaCompiler());
}
}

public CompilerResult work(String javaName, String javaSourceCode) {
TensorJavaCompiler compiler = COMPILER_POOL.poll();
if (compiler == null) {
return new CompilerResult();
}
return compiler.buildTask(javaName, javaSourceCode).compile(new CompilerResult());
}

private final static class TensorJavaCompiler {

private volatile boolean isRunning;
private JavaCompiler compiler;
private DiagnosticCollector<JavaFileObject> diagnostics;
private String javaName;
private JavaCompiler.CompilationTask task;
private TensorClassLoader classLoader;
private String classpath;
private ClassFileManager fileManager;

private TensorJavaCompiler() {
isRunning = false;
compiler = ToolProvider.getSystemJavaCompiler();
diagnostics = new DiagnosticCollector<>();
classLoader = new TensorClassLoader(parentClassloader);
fileManager = new ClassFileManager(compiler.getStandardFileManager(diagnostics, null, null));
buildClassPath();
}

// 构建classpath地址
void buildClassPath() {
this.classpath = null;
StringBuilder sb = new StringBuilder();
for (URL url : this.classLoader.getURLs()) {
String p = url.getFile();
sb.append(p).append(File.pathSeparator);
}
this.classpath = sb.toString();
}

// 构建JavaCompiler.CompilationTask对象
TensorJavaCompiler buildTask(String name, String source) {
javaName = name;
List<JavaFileObject> fileObjects = new ArrayList<>();
fileObjects.add(new JavaSourceObject(javaName, source));
List<String> options = new ArrayList<>();
options.add("-encoding");
options.add("UTF-8");
options.add("-classpath");
options.add(this.classpath);
task = compiler.getTask(null, fileManager, diagnostics, options, null, fileObjects);
return this;
}

CompilerResult compile(CompilerResult result) {
isRunning = true;
String compilerErr = "";
// task.call()开启了编译
if (task.call()) {
try {
// 此处更改System.out流的输出位置,以获取代码中的System.out\err等流的输出信息
PrintStream old = System.out;
ByteArrayOutputStream bos = new ByteArrayOutputStream(1024);
PrintStream print = new PrintStream(bos);
System.setOut(print);
JavaClassObject classObject = fileManager.getJavaClassObject();
Class cls = classLoader.loadClass(javaName, classObject);
result.setResult(cls.getDeclaredMethod("main", String[].class).invoke(null, new Object[]{null}));
//恢复System流的原输出位置
System.setOut(old);
result.setPrintMsg(bos.toString());
} catch (IllegalAccessException | InvocationTargetException | NoSuchMethodException e) {
log.error("compiler exception is ", e);
}
} else {
// 如果编译失败,获取错误信息
String compilerErr = "";
for (Diagnostic diagnostic : diagnostics.getDiagnostics()) {
compilerErr += compilePrint(diagnostic);
}
result.setCompilerErr(compilerErr);
}
result.setTimeout(false);
release();
return result;
}

public boolean isRunning() {
return isRunning;
}

private void release() {
isRunning = false;
COMPILER_POOL.add(this);
}

private String compilePrint(Diagnostic diagnostic) {
StringBuffer res = new StringBuffer();
res.append("Code:[").append(diagnostic.getCode()).append("]\n");
res.append("Kind:[").append(diagnostic.getKind()).append("]\n");
res.append("Position:[").append(diagnostic.getPosition()).append("]\n");
res.append("Start Position:[").append(diagnostic.getStartPosition()).append("]\n");
res.append("End Position:[").append(diagnostic.getEndPosition()).append("]\n");
res.append("Source:[").append(diagnostic.getSource()).append("]\n");
res.append("Message:[").append(diagnostic.getMessage(null)).append("]\n");
res.append("LineNumber:[").append(diagnostic.getLineNumber()).append("]\n");
res.append("ColumnNumber:[").append(diagnostic.getColumnNumber()).append("]\n");
return res.toString();
}
}
}

测试的返回结果

1
2
3
4
5
6
7
8
9
10
11
12
{
"code": 0,
"value": {
"taskId": 1,
"sourceName": "HelloWorld",
"sourceCode": "public class HelloWorld {\n public static void main (String[] args) {\n System.out.println(\"HelloWorld\");\n }\n}",
"startTime": 1551008298654,
"endTime": 1551008298989,
"taskOwnerId": "test-2",
"result": "{\"timeout\":false,\"compilerErr\":\"\",\"printMsg\":\"HelloWorld\\n\"}"
}
}

哔哩哔哩演示

哔哩哔哩视频地址