1 前言
Java的动态编译知识,真真在实际开发中并不是经常遇到。但是学习java动态编译有助于我们从更深一层次去了解java。对掌握jdk的动态代理模式,这样我们在学习其他一些开源框架的时候就能够知其然也知其所以然。下面我们来使用java的动态编译。有关java动态编译的API都在javax.tools包中
2 使用JavaCompiler接口来编译java源程序
使用 Java API来编译 Java源程序有非常多方法,目前让我们来看一种最简单的方法,通过JavaCompiler进行编译。我们能通过ToolProvider类的静态方法getSystemJavaCompiler来得到一个JavaCompiler接口的实例。
JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
JavaCompiler中最核心的方法是run。通过这个方法能编译 java源程序。这个方法有 3个固定参数和1个可变参数(可变参数是从Jave SE5开始提供的一个新的参数类型,用type… argu表示)。前3个参数分别用来为 java编译器提供参数、得到 Java编译器的输出信息及接收编译器的错误信息,后面的可变参数能传入一个或多个Java源程式文件。如果run编译成功,返回0。
int run(InputStream in, OutputStream out, OutputStream err, String... arguments)
如果前3个参数传入的是null,那么run方法将以标准的输入、输出代替,即System.in、System.out和 System.err。如果我们要编译一个 hello.java文件,并将使用标准输入输出,run的使用方法如下:
int results = tool.run(null, null, null, "Hello.java");
完整的代码如下(用的是Eclipse工具)
2.1 Eclipse工程截图

2.2 Hello.java
首先写好这个java文件,用来演示java动态编译Hello.java文件
package my.xyz.hello; public class Hello { public static void main(String[] args) { System.out.println("helloworld!"); } }
2.3 DynamicCompileTest.java
package my.xyz.test; import java.io.BufferedReader; import java.io.InputStream; import java.io.InputStreamReader; import javax.tools.JavaCompiler; import javax.tools.ToolProvider; public class DynamicCompileTest { public static void main(String[] args) throws Exception { // 任务:编译temp目录下的Hello.java文件 JavaCompiler javaCompiler = ToolProvider.getSystemJavaCompiler();// 取得当前系统编译器 int result = javaCompiler.run(null, null, null, "-d", "./temp", "./temp/Hello.java"); // int result=javaCompiler.run(null, null, null, "Hello.java"); System.out.println(result == 0 ? "恭喜编译成功!" : "对不起编译失败"); // 在程序中来运行Hello.classs Runtime run = Runtime.getRuntime(); Process process = run.exec("java -cp ./temp my.xyz.hello.Hello"); InputStream in = process.getInputStream(); BufferedReader reader = new BufferedReader(new InputStreamReader(in)); String info = ""; while ((info = reader.readLine()) != null) { System.out.println(info); } } }
3 使用StandardJavaFileManager编译Java源程序
在第一部分我们讨论调用java编译器的最容易的方法。这种方法能非常好地工作,但他确不能更有效地得到我们所需要的信息,如标准的输入、输出信息。而在Java SE6中最佳的方法是使用 StandardJavaFileManager类。这个类能非常好地控制输入、输出,并且 能通过DiagnosticListener得到诊断信息,而DiagnosticCollector类就是listener的实现。使用 StandardJavaFileManager需要两步。首先建立一个 DiagnosticCollector实例及通过JavaCompiler的getStandardFileManager()方法得到一个StandardFileManager对象。最后通过CompilationTask中的call方法编译源程序。每个类的具体方法参数可以查看jase6 API文档。上面有很详细的解释。完整的演示代码如下
3.1 Eclipse工程截图

3.2 DynamicCompileTest.java
package my.xyz.test; import java.io.File; import java.io.FileWriter; import java.util.Arrays; import javax.tools.JavaCompiler; import javax.tools.JavaCompiler.CompilationTask; import javax.tools.StandardJavaFileManager; import javax.tools.ToolProvider; public class DynamicCompileTest { public static void main(String[] args) throws Exception { //1.创建需要动态编译的代码字符串 String nr="\r\n";//回车换行 String source="package my.xyz.hello;"+nr+ " public class Hello{"+nr+ " public static void main(String[] args){"+nr+ " System.out.println(\"helloworld!\");"+nr+ "}"+nr+ "}" ; //2.将预动态编译的代码写入文件中1:创建临时目录 2:写入临时文件 File dir=new File(System.getProperty("user.dir")+"/temp");//临时目录 //如果/temp目录不存在创建temp目录 if(!dir.exists()){ dir.mkdir(); } FileWriter writer=new FileWriter(new File(dir,"Hello.java")); writer.write(source);//将字符串写入文件中 writer.flush(); writer.close(); //3:取得当前系统java编译器 JavaCompiler javaCompiler=ToolProvider.getSystemJavaCompiler(); //4:获取一个文件管理器StandardJavaFileManage StandardJavaFileManager javaFileManager=javaCompiler.getStandardFileManager(null, null, null); //5.文件管理器根与文件连接起来 Iterable it=javaFileManager.getJavaFileObjects(new File(dir,"Hello.java")); //6.创建编译的任务 //CompilationTask task=javaCompiler.getTask(null, javaFileManager, null,null, null, it); CompilationTask task=javaCompiler.getTask(null, javaFileManager, null,Arrays.asList("-d","./temp"), null, it); //执行编译 task.call(); javaFileManager.close(); } }
4 从内存中动态编译java程序并动态加载执行
JavaCompiler不仅能编译硬盘上的 Java文件,而且还能编译内存中的 Java代码,然后使用reflection来运行他们。完整演示代码如下
4.1 Eclipse工程截图

4.2 JavaStringObject.java
package my.xyz; import java.io.IOException; import java.net.URI; import javax.tools.SimpleJavaFileObject; public class JavaStringObject extends SimpleJavaFileObject { private String code; public JavaStringObject(String name, String code) { //super(URI.create("string:///" + name.replace('.', '/') + Kind.SOURCE.extension), Kind.SOURCE); super(URI.create(name+".java"), Kind.SOURCE); this.code = code; } @Override public CharSequence getCharContent(boolean ignoreEncodingErrors) throws IOException { return code; } }
4.3 DynamicCompileTest.java
package my.xyz.test; import java.io.PrintWriter; import java.io.StringWriter; import java.lang.reflect.Method; import java.net.URL; import java.net.URLClassLoader; import java.util.Arrays; import javax.tools.JavaCompiler; import javax.tools.JavaCompiler.CompilationTask; import javax.tools.JavaFileObject; import javax.tools.ToolProvider; import my.xyz.JavaStringObject; public class DynamicCompileTest { public static void main(String[] args) throws Exception { /* * 编译内存中的java代码 */ // 将代码写入内存中 StringWriter writer = new StringWriter();// 内存字符串输出流 PrintWriter out = new PrintWriter(writer); out.println("package my.xyz.hello;"); out.println("public class Hello{"); out.println("public static void main(String[] args){"); out.println("System.out.println(\"helloworld!\");"); out.println("}"); out.println("}"); out.flush(); out.close(); // 开始编译 JavaCompiler javaCompiler = ToolProvider.getSystemJavaCompiler(); JavaFileObject fileObject = new JavaStringObject("Hello", writer.toString()); CompilationTask task=javaCompiler.getTask(null, null, null, Arrays.asList("-d","./bin"), null, Arrays.asList(fileObject)); boolean success=task.call(); if(!success){ System.out.println("编译失败!"); } else{ System.out.println("编译成功!"); //利用反射调用其中的main()方法 // Class class1=Class.forName("com.ynxinhua.hello.Hello"); //ClassLoader是自动去从当前工作目录下的classpath路径下去找 也就是bin目录下 //Class class1=ClassLoader.getSystemClassLoader().loadClass("my.xyz.hello.Hello"); //利用URLClassLoader去实例化一个Class类 类文件可以放在任意位置,这样就很方便了 URL[] urls=new URL[]{new URL("file:/"+"./bin/")}; URLClassLoader classLoader=new URLClassLoader(urls); Class class1=classLoader.loadClass("my.xyz.hello.Hello"); Method method=class1.getDeclaredMethod("main",String[].class); String[] args1={null}; method.invoke(class1.newInstance(),args1); } } }