[英]How can I modify a java.lang class on the fly?
我正在尋找一種通過重寫字節代碼並重新加載類來動態添加字段到線程的方法,不確定它是否完全可能。 歡迎任何指示。 我發現了一些關於修改和加載類的信息,我知道JRebel可以無縫地交換你的代碼,但不確定這里是否適用相同的方法/工具。
這里的動機是探索理論上更好的替代線程本地對象。 如果方法有效,我應該能夠用注釋替換本地線程,結果應該優於當前的JDK實現。
PS:請救我“所有邪惡言論的根源”
澄清用例:
想象一下,我有一個ThreadLocal類:
class A {
ThreadLocal<Counter> counter;
...
counter.get().inc()
}
我想用注釋替換它:
class A {
@ThreadLocal
Counter counter;
...
counter.inc()
}
但是代替上面的代碼生成我想改變Thread,這樣Thread現在將有一個Acounter字段,實際的代碼將是:
class A {
// Nothing here, field is now in Thread
...
Thread.currentThread().Acounter.inc()
}
目前,不可能在運行時重新定義類,以便重新定義將導致新的方法或字段。 這是因為掃描所有現有實例的堆並轉換它們及其引用+潛在的不安全字段偏移基本更新程序(如AtomicFieldUpdater)所涉及的復雜性。
這個限制可以作為JEP-159的一部分解除,但正如並發利益郵件組所討論的那樣,這是一個很大的影響變化,所以可能永遠不會發生。
使用Javaassist / similar將允許使用新方法/字段將類轉換為新類。 此類可以由ClassLoader加載並在運行時使用,但它的定義不會替換現有實例。 因此,不可能將此方法與代理結合使用來重新定義類,因為檢測重新定義受到限制,因此:“重新定義可能會更改方法主體,常量池和屬性。重定義不得添加,刪除或重命名田野......“看到這里 。
所以現在,沒有。
我見過動態重新加載JAR的自定義類加載解決方案 - 您為每個JAR文件定義一個ClassLoader
並使用它來加載來自該JAR的類; 要重新加載整個JAR,只需“殺死”其ClassLoader
實例並創建另一個實例(在替換JAR文件之后)。
我認為不可能以這種方式調整Java的內部Thread
類,因為您無法控制System ClassLoader
。 一種可能的解決方案是使用CustomThreadWeaver
類生成一個新類,該類使用您需要的變量擴展Thread
,並使用自定義DynamicWeavedThreadClassLoader
來加載它們。
祝你好運,並在你成功時告訴我們你的怪物 ;-)
可能使用檢測,可能還有像javassist這樣的庫來修改代碼。 (但是,目前無法添加和刪除字段,方法或構造函數)
//Modify code using javassist and call CtClass#toBytecode() or load bytecode from file
byte[] nevcode;
Class<?> clz = Class.forName("any.class.Example");
instrumentationInstace.redefineClasses(new ClassDefinition(clz, nevcode));
不要忘記添加Can-Redefine-Classes: true
對於java代理的清單是Can-Redefine-Classes: true
。
真實的例子 - 使用javassist優化java <9 string.replace(CharSequence, CharSequence)
:
String replace_src =
"{String str_obj = this;\n"
+ "char[] str = this.value;\n"
+ "String find_obj = $1.toString();\n"
+ "char[] find = find_obj.value;\n"
+ "String repl_obj = $2.toString();\n"
+ "char[] repl = repl_obj.value;\n"
+ "\n"
+ "if(str.length == 0 || find.length == 0 || find.length > str.length) {\n"
+ " return str_obj;\n"
+ "}\n"
+ "int start = 0;\n"
+ "int end = str_obj.indexOf(find_obj, start);\n"
+ "if(end == -1) {\n"
+ " return str_obj;\n"
+ "}\n"
+ "int inc = repl.length - find.length;\n"
+ "int inc2 = str.length / find.length / 512;\ninc2 = ((inc2 < 16) ? 16 : inc);\n"
+ "int sb_len = str.length + ((inc < 0) ? 0 : (inc * inc2));\n"
+ "StringBuilder sb = (sb_len < 0) ? new StringBuilder(str.length) : new StringBuilder(sb_len);\n"
+ "while(end != -1) {\n"
+ " sb.append(str, start, end - start);\n"
+ " sb.append(repl);\n"
+ " start = end + find.length;\n"
+ " end = str_obj.indexOf(find_obj, start);\n"
+ "}\n"
+ "if(start != str.length) {\n"
+ " sb.append(str, start, str.length - start);\n"
+ "}\n"
+ "return sb.toString();\n"
+"}";
ClassPool cp = new ClassPool(true);
CtClass clz = cp.get("java.lang.String");
CtClass charseq = cp.get("java.lang.CharSequence");
clz.getDeclaredMethod("replace", new CtClass[] {
charseq, charseq
}).setBody(replace_src);
instrumentationInstance.redefineClasses(new ClassDefinition(Class.forName(clz.getName(), false, null), clz.toBytecode()));
這似乎是使用正確的工具來完成工作的問題。 這里也提出了類似的問題: 另一個Stack Overflow問題和Javaassist字節代碼操作庫是一種可能的解決方案。
但是沒有進一步詳細說明為什么要嘗試這樣做的原因,似乎真正的答案是使用正確的工具來完成工作。 例如,使用Groovy 可以動態地向該語言添加方法 。
您可以嘗試創建一個使用java.lang.instrument API的JVM代理,更具體地說,使用“促進已加載類的檢測”的重新轉換方法 ,然后使用所提到的Javassist(或ASM)處理字節碼。
要做你想做的事,更簡單的替代方法是使用Thread的子類,運行它,然后在該線程內執行你的例子中的代碼(連同你的子類的currentThread()的強制轉換)。
你試圖做的是不可能的。
由於您已經了解ThreadLocal,因此您已經知道建議的解決方案是什么。
或者,您可以對Thread進行子類化並添加自己的字段; 但是,只有那些明確創建該類的線程才會擁有這些字段,因此您仍然必須能夠“回退”使用本地線程。
真正的問題是“為什么?”,如“為什么本地線程不足以滿足您的要求?”
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.