[英]ByteBuddy agent to replace one method param with another
我有一個無法修改的大型第 3 方代碼庫,但我需要在許多不同的地方進行小而重要的更改。 我希望使用基於 ByteBuddy 的代理,但我不知道如何使用。 我需要替換的調用形式為:
SomeSystemClass.someMethod("foo")
我需要用
SomeSystemClass.someMethod("bar")
同時保持對同一方法的所有其他調用不變
SomeSystemClass.someMethod("ignore me")
由於SomeSystemClass
是一個 JDK class,我不想建議它,但只建議包含對它的調用的類。 如何才能做到這一點?
注意:
someMethod
是 static 和Byte Buddy 有兩種方法:
您使用相關調用站點轉換所有類:
new AgentBuilder.Default().type(nameStartsWith("my.lib.pkg.")).transform((builder, type, loader, module) -> builder.visit(MemberSubstitution.relaxed().method(SomeSystemClass.class.getMethod("someMethod", String.class)).replaceWith(MyAlternativeDispatcher.class.getMethod("substitution", String.class).on(any())).installOn(...);
在這種情況下,我建議您將 class MyAlternativeDispatcher
實現到您的 class 路徑(它也可以作為代理的一部分運送,除非您有更復雜的 class 安裝程序,例如 OSGi
public class MyAlternativeDispatcher { public static void substitution(String argument) { if ("foo".equals(argument)) { argument = "bar"; } SomeSystemClass.someMethod(argument); } }
這樣做,您可以設置斷點並實現任何復雜的邏輯,而無需在設置代理后過多考慮字節碼。 按照建議,您甚至可以獨立於代理發送替換方法。
檢測系統 class 本身並使其對調用者敏感:
new AgentBuilder.Default().with(RedefinitionStrategy.RETRANSFORMATION).disableClassFormatChanges().type(is(SomeSystemClass.class)).transform((builder, type, loader, module) -> builder.visit(Advice.to(MyAdvice.class).on(named("someMethod").and(takesArguments(String.class))))).installOn(...);
在這種情況下,您需要考慮調用者 class 以確保您只更改要應用此更改的類的行為。 這在 JDK 中並不少見,並且由於Advice
將您的建議 class 的代碼內聯(“復制粘貼”)到系統 class 中,因此如果您不能使用堆棧步行器,則可以不受限制地使用 JDK 內部 API(Java 8 和更早版本) API(Java 9 及更高版本):
class MyAdvice { @Advice.OnMethodEnter static void enter(@Advice.Argument(0) String argument) { Class<?> caller = sun.reflect.Reflection.getCallerClass(1); // or stack walker if (caller.getName().startsWith("my.lib.pkg.") && "foo".equals(argument)) { argument = "bar"; } } }
您應該選擇哪種方法?
第一種方法可能更可靠,但成本相當高,因為您必須處理 package 或子包中的所有類。 如果這個 package 中有很多類,您將付出相當大的代價來處理所有這些類以檢查相關呼叫站點,從而延遲應用程序啟動。 一旦加載了所有類,您就已經付出了代價,並且一切都准備就緒,而無需更改系統 class。 但是,您確實需要注意 class 加載程序,以確保每個人都可以看到您的替換方法。 在最簡單的情況下,您可以使用Instrumentation
API 到 append 和 jar 與此 ZA2F2ED4F8EBC2CBB4C21A 全局可見。
使用第二種方法,您只需要(重新)轉換一個方法。 這樣做非常便宜,但是您將為每次調用該方法添加(最小)開銷。 因此,如果在關鍵執行路徑上多次調用此方法,如果 JIT 沒有發現避免它的優化模式,那么每次調用都會付出代價。 在大多數情況下,我更喜歡這種方法,我認為,單個轉換通常更可靠且性能更高。
作為第三種選擇,您還可以使用MemberSubstitution
並添加您自己的字節碼作為替換(Byte Buddy 在replaceWith
步驟中公開 ASM,您可以在其中定義自定義字節碼而不是委托)。 這樣,您可以避免添加替換方法的要求,只需在原地添加替換代碼。 但是,這確實承擔了您的嚴格要求:
如果您添加條件語句並且 Byte Buddy(或任何人)無法在方法中優化它,則需要后者。 堆棧 map 幀重新計算非常昂貴,經常失敗,並且可能需要 class 加載鎖到死鎖。 Byte Buddy 優化了 ASM 的默認重新計算,試圖通過避免 class 加載來避免死鎖,但也不能保證,所以你應該記住這一點。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.