有没有办法在 ByteBuddy 代理之前对加载的类的方法调用advice()?

问题描述

我已经实现了一个简单的代理,如下所示。它适用于我的自定义 Foo.class,但我无法为 advice 类分配 java.net.URL

示例测试代码

    public class AgentTest {

    @Test
    public void advice() throws IOException {
        Foo foo = new Foo();
        File temp = Files.createTempDirectory("tmp").toFile();
        Map<TypeDescription,byte[]> map = new HashMap<>();
        map.put(new TypeDescription.ForLoadedType(URL.class),ClassFileLocator.ForClassLoader.read(URL.class));
        ClassInjector.UsingInstrumentation.of(temp,ClassInjector.UsingInstrumentation.Target.BOOTSTRAP,ByteBuddyAgent.install()).inject(map);

        new AgentBuilder.Default()
                .disableClassFormatChanges()
                .with(AgentBuilder.RedeFinitionStrategy.RETRANSFORMATION)
                .with(AgentBuilder.Typestrategy.Default.REBASE)
                .type(is(URL.class).or(is(Foo.class)))
                .transform(
                        new AgentBuilder.Transformer.ForAdvice()
                                .advice(
                                        isMethod().and(isPublic()).and(named("openConnection")).or(named("myMethod")),FooAdvice.class.getName()
                                )
                )
                .installOnByteBuddyAgent();

        foo.myMethod();
    }

    public static class FooAdvice {

        @Advice.OnMethodEnter
        public static void enter() {
            System.out.println("1- method entered !");
        }

        @Advice.OnMethodExit
        public static void exit() {
            System.out.println("2- method exited");
        }
    }
}

是否有任何特定方法可以绑定 advice 的 java.net.URL 类方法,以便在 ByteBuddy 代理之前加载该类?

解决方法

默认情况下,Byte Buddy 会忽略引导和扩展/平台加载程序。您需要设置自定义忽略匹配器来检测 URL。

请注意,如果您检测 Byte Buddy 需要自己的类,这可能会导致问题,特别是如果 Byte Buddy 在检测另一个类期间自行加载这些类。

,

像往常一样,拉斐尔是对的。不过,我想详细说明一下您的问题:您在这里遇到了引导问题:

  • 通知需要在引导类路径上,否则不能与引导类 URL 一起使用。
  • 由于注释的原因,建议需要 ByteBuddy。所以 BB 也必须在引导类路径上。
  • 如果您将示例代码放入单个类中并从那里使用 BB 代理库,则该类也需要位于引导类路径中。 IE。如果你想运行这个代码
import net.bytebuddy.agent.ByteBuddyAgent;
import net.bytebuddy.agent.builder.AgentBuilder;
import net.bytebuddy.asm.Advice;

import java.io.IOException;
import java.lang.reflect.Method;
import java.net.URL;

import static net.bytebuddy.matcher.ElementMatchers.*;

class AgentTest {
  public static void main(String[] args) throws IOException {
    ByteBuddyAgent.install();
    new AgentBuilder.Default()
      .disableClassFormatChanges()
      .ignore(none())
      .with(AgentBuilder.RedefinitionStrategy.RETRANSFORMATION)
      .with(AgentBuilder.RedefinitionStrategy.Listener.StreamWriting.toSystemError())
      .with(AgentBuilder.Listener.StreamWriting.toSystemError().withTransformationsOnly())
      .with(AgentBuilder.InstallationListener.StreamWriting.toSystemError())
      .type(is(URL.class).or(is(Foo.class)))
      .transform(
        new AgentBuilder.Transformer.ForAdvice()
          .advice(
            isMethod().and(isPublic()).and(named("openConnection")).or(named("myMethod")),FooAdvice.class.getName()
          )
      )
      .installOnByteBuddyAgent();

    new Foo().myMethod();
  }

  public static class Foo {
    public void myMethod() throws IOException {
      new URL("https://google.de").openConnection();
    }
  }

  public static class FooAdvice {
    @Advice.OnMethodEnter
    public static void enter(@Advice.Origin Method method) {
      System.out.println("Entering " + method);
    }

    @Advice.OnMethodExit
    public static void exit(@Advice.Origin Method method) {
      System.out.println("Exiting " + method);
    }
  }

}

您需要在 Java 命令行中添加类似这样的内容:

java -Xbootclasspath/a:/path/to/my-classes;/path/to/byte-buddy-1.10.13.jar;/path/to/byte-buddy-agent-1.10.13.jar ... AgentTest

然后你得到这个输出:

[Byte Buddy] BEFORE_INSTALL net.bytebuddy.agent.builder.AgentBuilder$Default$ExecutingTransformer@27bc2616 on sun.instrument.InstrumentationImpl@3941a79c
[Byte Buddy] REDEFINE BATCH #0 [2 of 2 type(s)]
[Byte Buddy] TRANSFORM AgentTest$Foo [null,null,loaded=true]
[Byte Buddy] TRANSFORM java.net.URL [null,loaded=true]
[Byte Buddy] REDEFINE COMPLETE 1 batch(es) containing 2 types [0 failed batch(es)]
[Byte Buddy] INSTALL net.bytebuddy.agent.builder.AgentBuilder$Default$ExecutingTransformer@27bc2616 on sun.instrument.InstrumentationImpl@3941a79c
Entering public void AgentTest$Foo.myMethod() throws java.io.IOException
Entering public java.net.URLConnection java.net.URL.openConnection() throws java.io.IOException
Entering public java.net.URLConnection java.net.URL.openConnection() throws java.io.IOException
Exiting public java.net.URLConnection java.net.URL.openConnection() throws java.io.IOException
Entering public java.net.URLConnection java.net.URL.openConnection() throws java.io.IOException
Entering public java.net.URLConnection java.net.URL.openConnection() throws java.io.IOException
Exiting public java.net.URLConnection java.net.URL.openConnection() throws java.io.IOException
Exiting public java.net.URLConnection java.net.URL.openConnection() throws java.io.IOException
Exiting public java.net.URLConnection java.net.URL.openConnection() throws java.io.IOException
Exiting public void AgentTest$Foo.myMethod() throws java.io.IOException

作为替代方案,您可以使用跳板 Java 代理,它动态地将 BB 和您的转换器放到引导类路径中,而无需直接引用它们的任何类。随后您可以通过反射开始转换。