java 利用反射模擬動態語言的 eval 函式
阿新 • • 發佈:2022-04-28
import java.io.File; import java.io.FileWriter; import java.io.PrintWriter; import java.lang.reflect.Method; import java.net.URL; import java.net.URLClassLoader; public class Eval { public static Object eval(String str) throws Exception { StringBuffer sb = new StringBuffer(); sb.append("public class Temp"); sb.append("{"); sb.append(" public Object getObject()"); sb.append(" {"); sb.append(" " + str + "return new Object();"); sb.append(" }"); sb.append("}"); // 呼叫自定義類載入器載入編譯在記憶體中class檔案 // 說明:這種方式也需要些資料落地寫磁碟的 // 為毛一定要落地呢,直接記憶體里加載不就完了嘛 // 應該也是可以的,它從磁碟讀了也是進記憶體 // 只不過java不允許直接操作記憶體 // 寫jni估計是可以 Class clazz = new MyClassLoader().findClass(sb.toString()); Method method = clazz.getMethod("getObject"); // 通過反射呼叫方法 return method.invoke(clazz.newInstance()); } public static void main(String[] args) throws Exception { Object rval = eval("System.out.println("Hello World");"); System.out.println(rval); } } /*http://hi.baidu.com/rqzmvfodkxadine/item/a789f2117af5474ee65e0657 http://blog.csdn.net/leeyohn/article/details/5179422 http://www.99inf.net/SoftwareDev/Java/33737.htm */ /*public class Eval { public static void main(String[] args)throws Exception { Object rval = eval("System.out.println("Hello World");return 5;"); System.out.println(rval); } public static Object eval(String str)throws Exception { //生成Java原始檔 StringBuilder s = new StringBuilder("public class Temp{"); s.append(" public Object rt(){"); s.append(" " + str); s.append(" }"); s.append("}"); //在當前目錄生成Java原始檔 File f = new File("Temp.java"); PrintWriter pw = new PrintWriter(new FileWriter(f)); pw.println(s.toString()); pw.close(); //動態編譯(此處可直接編譯記憶體中的Java原始碼,二進位制碼也放在記憶體中) //使用這些動態編譯的方式的時候,需要確保JDK中的tools.jar在應用的 CLASSPATH中。 com.sun.tools.javac.Main javac = new com.sun.tools.javac.Main(); // 這裡eclipse尋找class的路徑就是在bin下面找的, 需要把class編譯到專案的 bin目錄下 String[] cpargs = new String[] {"-d", "./bin" ,"Temp.java"}; //動態編譯 int status = javac.compile(cpargs); if(status != 0 ) { System.out.println("您給的Java程式碼有錯!"); return null; } //建立一個URL陣列 URL[] urls = {new URL("file:Temp.class")}; //以預設的ClassLoader作為父ClassLoader,建立URLClassLoader URLClassLoader myClassLoader = new URLClassLoader(urls); //載入Temp類(如果要載入記憶體中的class檔案-二進位制碼,需要自己寫類載入器) Class clazz = myClassLoader.loadClass("Temp"); //獲取rt方法 Method rt = clazz.getMethod("rt"); //動態呼叫rt方法 return rt.invoke(clazz.newInstance()); } }*/
import java.util.Arrays; import javax.tools.JavaCompiler.CompilationTask; import javax.tools.SimpleJavaFileObject; import javax.tools.JavaFileObject; import javax.tools.JavaCompiler; import javax.tools.StandardJavaFileManager; import javax.tools.ToolProvider; import javax.tools.DiagnosticCollector; import java.net.URI; import java.net.URISyntaxException; public class MyClassLoader extends ClassLoader { @Override public Class<?> findClass(String str) throws ClassNotFoundException { JavaCompiler compiler = ToolProvider.getSystemJavaCompiler(); // 用於診斷原始碼編譯錯誤的物件 DiagnosticCollector diagnostics = new DiagnosticCollector(); // 記憶體中的原始碼儲存在一個從JavaFileObject繼承的類中 JavaFileObject file = new JavaSourceFromString("Temp", str.toString()); // System.out.println(file); Iterable compilationUnits = Arrays.asList(file); // 關於報:Exception in thread "main" java.lang.ClassNotFoundException: Temp // 的解決方法:http://willam2004.iteye.com/blog/1026454 // 需要為compiler.getTask方法指定編譯路徑: // 執行過程如下: // 1、定義類的字串表示。 // 2、編譯類 // 3、載入編譯後的類 // 4、例項化並進行呼叫。 // 在eclipse下如果按照上述的方式進行呼叫,會在第三步中載入編譯的類過程丟擲“ClassNotFoundException”。 // 因為預設的Eclipse的java工程編譯後的檔案是放在當前工程下的bin目錄下。而第二步編譯輸出的路徑是工程目錄下, // 所以載入時會丟擲類找不到的錯誤。 String flag = "-d"; String outDir = System.getProperty("user.dir") + "/" + "bin"; Iterable<String> stringdir = Arrays.asList(flag, outDir); // 指定-d dir 引數 // 建立一個編譯任務 JavaCompiler.CompilationTask task = compiler.getTask(null, null, null, stringdir, null, compilationUnits); // 編譯源程式 boolean result = task.call(); if (result) { return Class.forName("Temp"); } return null; } } class JavaSourceFromString extends SimpleJavaFileObject { private String name; private String code; public JavaSourceFromString(String name, String code) { super(URI.create("string:///" + name.replace('.', '/') + Kind.SOURCE.extension), Kind.SOURCE); this.code = code; } public CharSequence getCharContent(boolean ignoreEncodingErrors) { return code; } }
Refer:
[1] Java 類的熱替換 —— 概念、設計與實現
https://www.ibm.com/developerworks/cn/java/j-lo-hotswapcls/index.html