繁体   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