1. 程式人生 > 其它 >java 利用反射模擬動態語言的 eval 函式

java 利用反射模擬動態語言的 eval 函式

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