当使用实现受 ByteBuddy 的 onMethodEnter 影响的接口的 class 时,我没有看到检测代码 运行

I do not see instrumented code run when using a class that implements an interface affected by ByteBuddy's onMethodEnter

下面用一些伪代码详细说明了情况。

Class implements Interface {
  method() {}
}

@Advice.OnMethodEnter()
public static methodEnter(@Advice.Argument(0) final Interface) {
  System.out.println("I don't end up seeing this print message when calling methods from Class")
}

有人知道这是为什么吗?主要是寻找一般性的“这应该有效”与“这不起作用,我遗漏了一些关于 ByteBuddy 如何工作的关键信息”。我正在查看一些看起来像黑匣子的开源代码,它们没有按预期运行,所以我试图了解 AOP 的工作原理。学习资源也很受欢迎,尽管 ByteBuddy 似乎有点特别,所以到目前为止我还没有能够从他们的文档中找到答案。

如果你想学习AOP基础知识,我建议你使用AspectJ,它比ByteBuddy(BB)这样的相对低级的字节码检测框架更容易学习和应用。

BB 的问题是你需要先设置检测,你不能只写一个方面,编译代码并期望它能工作。为此,您需要了解一些有关 Java 检测的基础知识,并且根据您的情况,还需要了解 Java 代理。例如,如果你想转换一个已经加载的 class ,你只能以一种避免引入新方法或字段或改变现有方法或字段的签名和修饰符的方式来转换它。如果您将 byte-buddybyte-buddy-agent 添加到您的 class 路径,您可能需要执行如下操作:

package de.scrum_master.Whosebug.q62521099;

import net.bytebuddy.agent.ByteBuddyAgent;
import net.bytebuddy.agent.builder.AgentBuilder;
import net.bytebuddy.asm.Advice;

import java.lang.instrument.Instrumentation;
import java.lang.reflect.Method;
import java.util.Arrays;

import static net.bytebuddy.agent.builder.AgentBuilder.RedefinitionStrategy.RETRANSFORMATION;
import static net.bytebuddy.implementation.bytecode.assign.Assigner.Typing.DYNAMIC;
import static net.bytebuddy.matcher.ElementMatchers.*;

class ByteBuddyDemo {
  public static void main(String[] args) {
    weaveAspect();
    MyInterface myInterface = new MyClass();
    myInterface.doSomething();
    System.out.println("7 + 8 = " + myInterface.add(7, 8));
  }

  private static void weaveAspect() {
    Instrumentation instrumentation = ByteBuddyAgent.install();
    new AgentBuilder.Default()
      .disableClassFormatChanges()
      .with(RETRANSFORMATION)
      .type(isSubTypeOf(MyInterface.class))
      .transform((builder, typeDescription, classLoader, module) ->
        builder.visit(
          Advice.to(MyByteBuddyAspect.class).on(isMethod())
        )
      )
      .installOn(instrumentation);
  }

  interface MyInterface {
    void doSomething();

    int add(int a, int b);
  }

  static class MyClass implements MyInterface {
    @Override
    public void doSomething() {
      System.out.println("Doing something");
    }

    @Override
    public int add(int a, int b) {
      return a + b;
    }
  }

  static abstract class MyByteBuddyAspect {
    @Advice.OnMethodEnter
    public static void before(
      @Advice.This(typing = DYNAMIC, optional = true) MyInterface target,
      @Advice.Origin Method method,
      @Advice.AllArguments(readOnly = false, typing = DYNAMIC) Object[] args
    )
    {
      System.out.println("MyByteBuddyAspect.before");
      System.out.println("  Method: " + method);
      System.out.println("  Instance: " + target);
      System.out.println("  Arguments: " + Arrays.deepToString(args));
    }
  }
}

看到了吗?这里有很多样板代码。我不想解释所有细节,最好在网上搜索教程。 BB 网站也有一个,尽管在某些方面已经过时。 BB 功能丰富到让人不知所措

我上面的解决方案的控制台日志是:

MyByteBuddyAspect.before
  Method: public void de.scrum_master.Whosebug.q62521099.ByteBuddyDemo$MyClass.doSomething()
  Instance: de.scrum_master.Whosebug.q62521099.ByteBuddyDemo$MyClass@61544ae6
  Arguments: []
Doing something
MyByteBuddyAspect.before
  Method: public int de.scrum_master.Whosebug.q62521099.ByteBuddyDemo$MyClass.add(int,int)
  Instance: de.scrum_master.Whosebug.q62521099.ByteBuddyDemo$MyClass@61544ae6
  Arguments: [7, 8]
7 + 8 = 15

所以你看到了如何访问目标实例、方法信息(仅在需要时使用,否则忽略它)和参数列表。

如果你想使用enter/exit建议的组合,你可以添加更多选项,以便动态决定是执行还是跳过原始方法,是否操纵参数或结果等。所有这是一个相当大的学习曲线,但我最近也经历了它。所以这绝对是可能的。对于 AOP,我仍然更喜欢 AspectJ,并且仅将 BB 用于更底层的东西,例如创建特殊类型的测试替身(模拟)。但那是另外一回事了。

这取决于你在别处做什么。 Byte Buddy(就像 AspectJ 一样)没有内置到 Java 平台中。方面(或建议)需要以某种方式附加到 JVM。 Byte Buddy(就像 AspectJ)提供构建时检测。如果您想检测作为项目一部分的 class,构建时检测是最简单的方法。

构建插件为 Maven 和 Gradle 提供开箱即用的支持。您可以通过在 net.bytebuddy.build.Plugin 中应用它来实现建议。匹配您要更改的所有 classes,然后使用 DynamicType.Builder::visit 在您注册 Advice.to(...).on(...).

的构建器中应用建议

如果您想检测库代码或 JVM 代码,这需要在运行时使用 Java 代理来完成。这样做,注册一个 AgentBuilder 或多或少应用相同的动态类型构建器 API.