一篇參考博客:http://www.cnblogs.com/fangwenyu/archive/2011/10/12/2209051.html
在Python中有一個exec()函數,同樣在JavaScript中有一個eval()函數,這兩個函數有一個相似的特點,那就是可以在里面傳入一段Python代碼或者JavaScript代碼,發現竟然可以運行該代碼。
但是遺憾的是,Java中並不存在這樣的函數,於是突發奇想,我們可不可以在Java中實現一個類似的函數,用來執行Java代碼呢?
我們知道,Python和JavaScript屬於腳本語言,也就是非編譯型語言,它們並不存在先編譯后執行的過程的。而Java、C++這種編譯型的語言,一般都是先編譯,后執行。對於Java來說,編譯生成.class文件,然后JVM運行.class文件。而我們如果想要將Java代碼傳入方法中,然后運行,那么就不能采用傳統的編譯+運行了,采用Java提供的動態編譯和動態加載的機制。
1、動態編譯。
我們可以調用Process執行javac。但這種方式坦白來說不好。因為javac的命令參數寫法和操作系統有關,也就是windows和linux的寫法有少量不同。后來發現jdk提供一個動態編譯的類。
JavaCompiler javac;
javac = ToolProvider.getSystemJavaCompiler();
int compilationResult = javac.run(null,null,null, "-g","-verbose",javaFile);
這樣就可以動態進行編譯。前兩個參數是輸入參數、輸出參數,我覺得沒有什么用,第三個參數是編譯輸出信息,默認輸出到System.out.err里面。從第四個參數開始,就是javac的參數,可以用數組,也可以直接逗號分割。
2、動態加載。
動態加載實際就是調用ClassLoader。當然需要反射機制調用其中的一個內部方法,使之變成外部可調用的方法。
File file = new File("/Users/yangming/Work/DevWorkSpace/ssac/gx_hx/test/");
URLClassLoader classloader = (URLClassLoader) ClassLoader.getSystemClassLoader();
Method add = URLClassLoader.class.getDeclaredMethod("addURL", new Class[]{URL.class});
add.setAccessible(true); //表示Method在使用時應該取消Java語言的訪問權限檢查
add.invoke(classloader, new Object[]{file.toURI().toURL()});
Class c = classloader.loadClass("Test");
Object o = c.newInstance();
Method m = c.getDeclaredMethod("getString");
m.invoke(o, null);
這樣就完成了類的動態加載。
下面我通過一個簡單的例子加以說明,寫一個eval()方法,可以執行System.out.println("Hello, " + str);這行代碼:
Eval.java:
package com.darrenchan; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import javax.tools.JavaCompiler; import javax.tools.JavaFileObject; import javax.tools.SimpleJavaFileObject; import javax.tools.StandardJavaFileManager; import javax.tools.ToolProvider; public class Eval { /* * 從java6版本開始,已經支持動態編譯了,你可以在運行期直接編譯.java文件,執行.class文件,並且能夠獲得相關的輸入輸出, * 甚至還能監聽相關的事件。 * java的動態編譯提供了多種渠道,比如,可以動態編譯一個字符串,也可以是文本文件,也可以是編譯過的字節碼文件(.class文件), * 甚至可以是存放在數據庫中的明文代碼或字節碼,只要是符合java規范的就都可以在運行期動態加載,其實現方式就是實現JavaFileObject * 接口,重寫getCharContent、openInputStream、openOutputStream,或者實現JDK * 已經提供的兩個SimpleJavaFileObject、ForwardingJavaFileObject。下面我演示一下,如何動態編譯一個字符串。 */ /** * Java動態編譯演示 */ public static void main(String[] args) throws Exception { // Java源代碼 String sourceStr = "public class Hello{public String sayHello(String name){return \"Hello, \"+name;}}"; // 類及文件名 String clsName = "Hello"; // 方法名 String methodName = "sayHello"; /** * 當前編譯器:注意,如果是用的jdk1.6的版本(建議使用jdk1.7,1.7是沒有任何問題的),ToolProvider. * getSystemJavaCompiler()拿到的對象將會為null, * 原因是需要加載的Tools.jar不在jdk安裝目錄的jre目錄下,需要手動將lib目錄下的該jar包拷貝到jre下去,詳情請參考: * http://www.cnblogs.com/fangwenyu/archive/2011/10/12/2209051.html */ JavaCompiler cmp = ToolProvider.getSystemJavaCompiler(); // Java標准文件管理器 StandardJavaFileManager fm = cmp.getStandardFileManager(null, null, null); // Java文件對象 JavaFileObject jfo = new StringJavaObject(clsName, sourceStr); // 編譯參數,類似於javac <options> 中的options List<String> optionsList = new ArrayList<String>(); // 編譯文件的存放地方,注意:此處是為Eclipse工具特設的 optionsList.addAll(Arrays.asList(new String[] { "-d", "./bin" })); // 要編譯的單元 List<JavaFileObject> jfos = Arrays.asList(new JavaFileObject[] { jfo }); // 設置編譯環境 JavaCompiler.CompilationTask task = cmp.getTask(null, fm, null, optionsList, null, jfos); // 編譯成功 if (task.call()) { // 生成對象 Object obj = Class.forName(clsName).newInstance(); Class<? extends Object> cls = obj.getClass(); // 調用sayHello方法 Method m = cls.getMethod(methodName, String.class); // 第一個參數是執行該方法的主調,后面若干個參數是執行該方法時傳入該方法的實參 String str = (String) m.invoke(obj, "陳馳"); System.out.println(str); } } }
StringJavaObject.java:
package com.darrenchan; import java.io.IOException; import java.net.URI; import javax.tools.SimpleJavaFileObject; public class StringJavaObject extends SimpleJavaFileObject { /** * 源代碼 */ private String content = ""; /** * 遵循Java規范的類名及文件 */ public StringJavaObject(String javaFileName, String content){ super(_createStringJavaObjectUri(javaFileName), Kind.SOURCE); this.content = content; } /** * 產生一個URL資源路徑 */ private static URI _createStringJavaObjectUri(String javaFileName) { //注意此處未設置包名 return URI.create("String:///" + javaFileName + Kind.SOURCE.extension); } /** * 文本文件代碼 */ @Override public CharSequence getCharContent(boolean ignoreEncodingErrors) throws IOException { return content; } }
經過測試,最終的運行結果符合預期,如下所示: