簡體   English   中英

ByteBuddy 代理用另一個方法參數替換一個方法參數

[英]ByteBuddy agent to replace one method param with another

我有一個無法修改的大型第 3 方代碼庫,但我需要在許多不同的地方進行小而重要的更改。 我希望使用基於 ByteBuddy 的代理,但我不知道如何使用。 我需要替換的調用形式為:

SomeSystemClass.someMethod("foo")

我需要用

SomeSystemClass.someMethod("bar")

同時保持對同一方法的所有其他調用不變

SomeSystemClass.someMethod("ignore me")

由於SomeSystemClass是一個 JDK class,我不想建議它,但只建議包含對它的調用的類。 如何才能做到這一點?

注意:

  1. someMethod是 static 和
  2. 調用(至少其中一些)在 static 初始化程序塊內

Byte Buddy 有兩種方法:

  1. 您使用相關調用站點轉換所有類:

     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); } }

    這樣做,您可以設置斷點並實現任何復雜的邏輯,而無需在設置代理后過多考慮字節碼。 按照建議,您甚至可以獨立於代理發送替換方法。

  2. 檢測系統 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,您可以在其中定義自定義字節碼而不是委托)。 這樣,您可以避免添加替換方法的要求,只需在原地添加替換代碼。 但是,這確實承擔了您的嚴格要求:

  • 不要添加條件語句
  • 重新計算 class 的堆棧 map 幀

如果您添加條件語句並且 Byte Buddy(或任何人)無法在方法中優化它,則需要后者。 堆棧 map 幀重新計算非常昂貴,經常失敗,並且可能需要 class 加載鎖到死鎖。 Byte Buddy 優化了 ASM 的默認重新計算,試圖通過避免 class 加載來避免死鎖,但也不能保證,所以你應該記住這一點。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM