[英]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.