2010-10-31 122 views
3

說我有這個代碼,使用一些輸入(例如,URL路徑),以確定要運行的方法,通過反射:運行時代碼生成和編譯

// init 
map.put("https://stackoverflow.com/users/*", "viewUser"); 
map.put("/users", "userIndex"); 

// later 
String methodName = map.get(path); 
Method m = Handler.class.getMethod(methodName, ...); 
m.invoke(handler, ...); 

此使用反射所以性能可以得到改善。它可以這樣做:

// init 
map.put("https://stackoverflow.com/users/*", new Runnable() { public void run() { handler.viewUser(); } }); 
map.put("/users", new Runnable() { public void run() { handler.userIndex(); } }); 

// later 
Runnable action = map.get(path); 
action.run(); 

但手動創建所有Runnable s就像它有它自己的問題。 我想知道,我可以在運行時生成它們嗎?所以我會像第一個例子那樣有一個輸入映射,並且會動態地創建第二個例子的映射。 當然,生成它只是建立一個字符串的問題,但編譯和加載它呢?

注意:我知道性能提升是如此之少,它是過早優化的完美例子。因此這是一個學術問題,我對運行時生成和代碼編譯感興趣。

回答

3

動態生成代碼的唯一方法是生成源代碼並對其進行編譯或生成字節代碼並在運行時加載它。前者有模板解決方案,後者有字節碼操作庫。沒有一個真實的案例和一些分析,我不認爲你真的可以說哪個會更好。從維護的角度來看,我認爲反射是可用時的最佳選擇。

0

那麼,您可以編寫代碼到.java文件,使用javac編譯它(how to do that)並使用Reflection將其加載到Java中。

但也許,作爲一種權衡,您也可以在初始化期間獲取Method對象 - 因此您只需要爲每個請求調用invoke()方法。

2

我想你可以通過代碼here來實現這一點。就在前一段時間,我嘗試了這一點,而且我不知道在哪裏找到了我正在使用的代碼,但似乎這是相同的。

基本上,您使用1.6編譯器API,而是使用「非傳統」的方式來查找源文件和編寫類文件:Compiler需要一個Iterable<JavaFileObject>,你在你的記憶支持的實施插上,和JavaFileManager該手柄編寫類文件,在那裏你將二進制編譯器輸出保存在內存中。

現在,您的代碼編譯,你只需要一個定製ClassLoader可以從你的內存字節碼閱讀並正確FQCN等

而且,幸運的是,所有這似乎是加載類準備好;)

1

實際上,如果反覆調用相同的方法,反射引擎會在內部生成類似的調用存根。 (只需使用相同的Method對象,而不是一次又一次地重新創建它們。)