一般情况下对java源文件的编译均是在代码完成后使用javac编译的,不管是使用IDE还是直接使用命令行。这里要说的情况是比较特别的,就是在代码内动态的编译一些代码。比如你想通过在某个目录下通过放置一些源代码的方式来实现对程序功能的动态扩展,那么你的程序就需要具有一种对源代码的编译、加载、运行的能力,可能就需要本文介绍的3种方法。
可以和JAVA的类加载器结合使用,动态编译、动态加载。
方法1:通过调用本机的javac命令来编译
javac –encoding char_set –classpath/-cp classpath –d dir src
在java程序中调用javac命令可以通过调用Runtime类的exec或是ProcessBuilder类的start方法来完成,这两个类的功能 基本相同,用法也比较相似。如果是JDK1.5之前的版本请使用Runtime类完成相同的功能。
public ProcessBuilder(String... command)
利用指定的操作系统程序和参数构造一个进程生成器。这是一个有用的构造方法,它将进程生成器的命令设置为与 command 数组包含相同字符串的字符串列表,且顺序相同。不必检查 command 是否为一个有效的操作系统命令。
Process p = new ProcessBuilder("command", "arg1" [, arg2] [,arg3] [,……]).start();
Process proc = new ProcessBuilder("javac", "-d", "d:\\test\\bin", "d:\\test\\src\\Test.java").start();
利用修改过的工作目录和环境启动进程的例子:
ProcessBuilder pb = new ProcessBuilder("myCommand", "myArg1", "myArg2");
Map<String, String> env = pb.environment();
env.put("VAR1", "myValue");
env.remove("OTHERVAR");
env.put("VAR2", env.get("VAR1") + "suffix");
pb.directory(new File("myDir"));
Process p = pb.start();
pb.environment()返回此进程生成器环境的字符串映射视图。 无论进程生成器何时创建,都需要将环境初始化为一份当前进程环境的副本(请参阅 System.getenv())。由此对象的 start() 方法启动的后续子进程将使用这一映射作为它们的环境。
public class Runtime extends Object
每个 Java 应用程序都有一个 Runtime 类实例,使应用程序能够与其运行的环境相连接。可以通过 getRuntime 方法获取当前运行时。
runtime.exec("javac -d d:\\test\\dir d:\\test\\src\\Test.java");
方法2:使用Sun的
tools.jar
包的com.sun.tools.javac.Main类完成对代码的编译
其构造方法为public Main(OutputStream outputstream, String s)。使用tools.jar的com.sun.tools.javac.Main编译时,主要是调用该类的compile(String [] args)/compile(String [] args, PrintWirter pw)。
int returnValue = Main.compile(new String[] {"-classpath", compilepath, "-d", dir, src});
PrintWriter pw = new PrintWriter("d:\\test\\src\\test.txt");
int returnValue = Main.compile(new String[] {"-classpath", compilepath, "-d", dir, src}, pw);
//暂时没有弄明白pw在运行中的作用
public class DynamicCompileTest {
public static void main(String[] args) {
try {
String className = "RunTime";
String classDir = System.getProperty("user.dir");
File file = new File(classDir, className + ".java");
PrintWriter out = new PrintWriter(new FileOutputStream(file));
// 代码
StringBuffer sbf = new StringBuffer(128);
sbf.append("public class ");
sbf.append(className);
sbf.append("{");
sbf.append("public void hello () {");
sbf.append("System.out.println(\"DynamicCompile Success.\");");
sbf.append("}");
sbf.append("}");
String code = sbf.toString();
out.println(code);
out.flush();
out.close();
// 编译
com.sun.tools.javac.Main.compile(new String[] { "-d", classDir, file.getName() });// JAVA_HOME/jdk/lib/tools.jar
URL url = new URL("file:/" + classDir + File.separator);
// 动态加载/执行
URLClassLoader loader = new URLClassLoader(new URL[] { url });
Class<?> clazz = loader.loadClass(className);
Object obj = clazz.newInstance();
Method method = clazz.getMethod("hello");
method.invoke(obj);
} catch (Exception e) {
e.printStackTrace();
}
}
}
方法3:使用javax.tools包
从上面可以看到方法2的缺点就是tools.jar需要我们自行导入。而在Java SE6中为我们提供了标准的包来操作Java编译器,这就是javax.tools包。使用这个包,我们可以不用将jar文件路径添加到classpath中了。 使用这个类的方法和上面的类很相似,我只需要将
Main.compile(new String[]{"-encoding", "UTF-8","-d", binDir, filePath});
替换成
//将编译参数通过数组传递到编译方法中,该函数的方法和javac的参数完成一致
JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
compiler.run(null, null, null, "-encoding", "UTF-8","-d", binDir, filePath);
可以通过ToolProvider类的静态方法getSystemJavaCompiler来得到一个JavaCompiler接口的实例。
JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
JavaCompiler中最核心的方法是run。通过这个方法可以编译java源程序。这个方法有3个固定参数和1个可变参数(可变参数是从 Jave SE5开始提供的一个新的参数类型,用type… args表示)。前3个参数分别用来为java编译器提供参数、得到Java编译器的输出信息以及接收编译器的错误信息,后面的可变参数可以传入一个或多个Java源程序文件。如果run编译成功,返回0。
int run(InputStream in, OutputStream out, OutputStream err, String... arguments)
int result = compiler.run(null, null, null, "-encoding", "UTF-8", "-cp", "C:/Java/jdk1.6.0_18/lib/tools.jar", "-d", binDir, filePath);
<<To Be Continued>>