记录生活中的点点滴滴

0%

Java动态编译

在早期的版本中(Java SE5及以前版本)中只能通过tools.jar中的 com.sun.tools.javac 包来调用Java编译器。

而在Java SE6中为我们提供了标准的包来操作Java编译器,这就是javax.tools包。

编译Java文件

使用Java API来编译Java源代码有非常多方法,目前让我们来看一种最简单的方法,通过JavaCompiler进行编译。

使用ToolProvider.getSystemJavaCompiler来得到一个JavaCompiler接口的实例。

1
JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();

JavaCompiler中最核心的方法是run(),通过这个方法能编译java源代码:

1
int run(InputStream in, OutputStream out, OutputStream err, String... arguments)

参数分别用来为:

  1. java编译器提供参数
  2. 得到Java编译器的输出信息
  3. 接收编译器的错误信息,
  4. 一个或多个Java源程式文件

如果编译成功,则返回0。

如果前3个参数传入的是null,那么run方法将以标准的输入、输出代替,即System.inSystem.outSystem.err

我们先整一个 Test 的 Java 类:

1
2
3
4
5
6
7
package cn.gs.compiler;

public class Test {
public static void main(String[] args) {
System.out.println("this is a test.java file ,thank you very much");
}
}

完整的编译例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public static void main(String[] args) throws IOException {
//编译java文件
JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
int res = compiler.run(null, null, null, "src/cn/gs/compiler/Test.java");
System.out.println(res==0 ? "编译成功" : "编译失败");

//执行java命令,空参数
Process process = Runtime.getRuntime().exec("java Test.java", null, new File("src/cn/gs/compiler/"));
BufferedReader br = new BufferedReader(new InputStreamReader(process.getInputStream()));
String str = null;
while ((str = br.readLine())!=null){
System.out.println(str);
}
}

先看看当前包下的文件:

当我们执行 Demo1 的main方法之后,控制台输出如下:

文件也会多出来一个编译后的 class 文件,如下所示:

动态编译并执行

我们可以对上面的代码稍加改进,使我们的程序在运行过程中也可以动态编译Java文件并执行它们。

我们先写一个写文件的方法,传入一个文件路径的参数,往其中写入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
public void writeMyDemo(String path){
//创建文件及相关流
File file = new File(path);
FileOutputStream fos = null;
BufferedOutputStream bos = null;
//StringBuffer对象sb为写入的内容
StringBuffer sb = new StringBuffer();
sb.append("package cn.gs.compiler;\n");
sb.append("public class MyDemo {\n");
sb.append("public void myMethod(){\n");
sb.append("System.out.println(\"俺的方法执行了...\");\n");
sb.append("}\n");
sb.append("}\n");

try {
fos = new FileOutputStream(file);
bos = new BufferedOutputStream(fos);
//写入sb中的内容
bos.write(sb.toString().getBytes());
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
bos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}

主方法中遵循下列步骤:

  1. 获取当前类加载路径,并写入我们的 MyDemo
  2. 动态编译 MyDemo
  3. 通过反射执行 MyDemo 类的 myMethod 方法

代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
@Test
public void Test1() throws Exception {
String path = "";//文件路径
File classPath = new File(Thread.currentThread().getContextClassLoader().getResource("").toURI());

path = classPath.getAbsolutePath() + "/cn/gs/compiler/MyDemo.java";

//写入MyDemo类
writeMyDemo(path);

//动态编译
JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
int res = compiler.run(null, null, null, path);
System.out.println(res==0 ? "编译成功" : "编译失败");

//执行myMethod方法
if(res==0){
Class<?> aClass = Class.forName("cn.gs.compiler.MyDemo");
Method myMethod = aClass.getMethod("myMethod");
//获取一个对象
Object o = aClass.getConstructor().newInstance();
myMethod.invoke(o);//调用方法
}
}

执行结果如下: