gpt4 book ai didi

java - ByteBuddy 代理将一个方法参数替换为另一个方法参数

转载 作者:行者123 更新时间:2023-12-02 08:44:52 32 4
gpt4 key购买 nike

我有一个无法修改的大型第 3 方代码库,但我需要在许多不同的地方进行微小但重要的更改。我希望使用基于 ByteBuddy 的代理,但我不知道如何使用。我需要替换的调用的形式为:

SomeSystemClass.someMethod("foo")

我需要将其替换为

SomeSystemClass.someMethod("bar")

同时保持对同一方法的所有其他调用不变

SomeSystemClass.someMethod("ignore me")

由于 SomeSystemClass 是一个 JDK 类,因此我不想建议它,而只是建议包含对其调用的类。如何做到这一点?

请注意:

  1. someMethod 是静态的并且
  2. 调用(至少其中一些)位于静态初始化 block 内

最佳答案

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(...);

    在这种情况下,我建议您在类路径中实现一个类 MyAlternativeDispatcher (它也可以作为代理的一部分提供,除非您有更复杂的类加载器设置,例如 OSGi,其中您实现条件逻辑:

     public class MyAlternativeDispatcher {
    public static void substitution(String argument) {
    if ("foo".equals(argument)) {
    argument = "bar";
    }
    SomeSystemClass.someMethod(argument);
    }
    }

    这样做,您可以设置断点并实现任何复杂的逻辑,而无需在设置代理后考虑太多字节代码。您甚至可以按照建议独立于代理发送替代方法。

  2. 检测系统类本身并使其对调用者敏感:

     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(...);

    在这种情况下,您需要反射(reflection)调用者类,以确保您只更改要应用此更改的类的行为。这在 JDK 中并不罕见,并且由于 Advice 将建议类的代码内联(“复制粘贴”)到系统类中,因此您可以不受限制地使用 JDK 内部 API(Java 8 及更早版本)如果您无法使用 stack walker 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";
    }
    }
    }

您应该选择哪种方法?

第一种方法可能更可靠,但成本相当高,因为您必须处理包或子包中的所有类。如果这个包中有很多类,您将付出相当大的代价来处理所有这些类以检查相关的调用站点,从而延迟应用程序启动。一旦加载了所有类,您就已经付出了代价,并且一切都已就位,而无需更改系统类。然而,您确实需要处理类加载器,以确保您的替换方法对每个人都可见。在最简单的情况下,您可以使用 Instrumentation API 将包含此类的 jar 附加到引导加载程序,使其全局可见。

使用第二种方法,您只需要(重新)转换单个方法。这样做的成本非常低,但每次调用该方法都会增加(最小的)开销。因此,如果在关键执行路径上多次调用此方法,并且 JIT 没有发现避免它的优化模式,那么您将为每次调用付出代价。在大多数情况下,我更喜欢这种方法,我认为,单个转换通常更可靠和更高效。

作为第三个选项,您还可以使用 MemberSubstitution 并添加您自己的字节代码作为替换(Byte Buddy 在 replaceWith 步骤中公开 ASM,您可以在其中定义自定义字节代码而不是委托(delegate))。这样,您就可以避免添加替换方法的需要,而只需就地添加替换代码。然而,这确实对您提出了严格的要求:

  • 不要添加条件语句
  • 重新计算该类的堆栈映射帧

如果您添加条件语句并且 Byte Buddy(或任何人)无法在方法中对其进行优化,则需要后者。堆栈映射帧重新计算非常昂贵,经常失败,并且可能需要类加载锁来死锁。 Byte Buddy 优化了 ASM 的默认重新计算,试图通过避免类加载来避免死锁,但也不能保证,所以你应该记住这一点。

关于java - ByteBuddy 代理将一个方法参数替换为另一个方法参数,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/61148740/

32 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com