使用 Byte Buddy 拦截 Object.class toString() 方法

Intercepting Object.class toString() method with Byte Buddy

我正在使用 Byte Buddy 来拦截一些 JDK 方法,System.class 和 Thread.class 很好,但不适用于 java.lang.Object。我是 运行 我在 JDK8 上的测试,它没有抛出任何错误。

final public class AgentBootstrap {
  public static void premain(String agentArgs, Instrumentation inst) throws Exception {
    try {
      new AgentBuilder.Default()
        .with(AgentBuilder.RedefinitionStrategy.RETRANSFORMATION)
        .with(AgentBuilder.InitializationStrategy.NoOp.INSTANCE)
        .with(AgentBuilder.TypeStrategy.Default.REBASE)
        .enableNativeMethodPrefix("$$mynative_")
        .ignore(ElementMatchers.none())
        .with(
          new AgentBuilder.Listener.Filtering(
            new StringMatcher("java.lang.Object", StringMatcher.Mode.EQUALS_FULLY),
            AgentBuilder.Listener.StreamWriting.toSystemOut()))
        .type(ElementMatchers.is(Object.class))
        .transform(new Transformer() {
          @Override
          public Builder<?> transform(Builder<?> builder, TypeDescription typeDescription, ClassLoader classLoader, JavaModule module) {
            return builder.method(ElementMatchers.named("toString")).intercept(FixedValue.value("HELLO BYTE BUDDY!"));
          }
        })
        .installOn(inst);
    }
    catch (Exception e) {
      e.printStackTrace();
    }
  }
}

并且我尝试使用Javassist,改造java.lang.Object的方法是成功的。 谁能知道为什么它在 Object.class 上不起作用?

您想使用

  • disableClassFormatChanges() 为了避免结构性 class 文件更改违反重新转换规则,
  • 建议 API 以便将代码插入现有方法而不添加新方法。
import net.bytebuddy.agent.ByteBuddyAgent;
import net.bytebuddy.agent.builder.AgentBuilder;
import net.bytebuddy.asm.Advice;

import java.lang.instrument.Instrumentation;

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 Scratch {

  public static class ToStringAdvice {
    @Advice.OnMethodEnter(skipOn = Advice.OnDefaultValue.class)
    public static boolean before() {
      // Skip original method execution (false is the default value for boolean)
      return false;
    }

    @Advice.OnMethodExit
    public static void after(@Advice.Return(readOnly = false, typing = DYNAMIC) Object returnValue) {
      // Set fixed return value
      returnValue = "HELLO BYTE BUDDY!";
    }
  }

  public static void premain(String agentArgs, Instrumentation inst) {
    new AgentBuilder.Default()
      .disableClassFormatChanges()
      .with(RETRANSFORMATION)
      .with(AgentBuilder.RedefinitionStrategy.Listener.StreamWriting.toSystemError())
      .with(AgentBuilder.Listener.StreamWriting.toSystemError().withTransformationsOnly())
      .with(AgentBuilder.InstallationListener.StreamWriting.toSystemError())
      .ignore(none())
      .type(is(Object.class))
      .transform((builder, typeDescription, classLoader, module) ->
        builder.visit(
          Advice
            .to(ToStringAdvice.class)
            .on(named("toString"))
        )
      )
      .installOn(inst);
  }

  public static void main(String[] args) throws Exception {
    Instrumentation instrumentation = ByteBuddyAgent.install();
    premain("", instrumentation);
    instrumentation.retransformClasses(Object.class);
    System.out.println(new Object());
  }

}

这行得通,...

[Byte Buddy] BEFORE_INSTALL net.bytebuddy.agent.builder.AgentBuilder$Default$ExecutingTransformer@3bfdc050 on sun.instrument.InstrumentationImpl@1bce4f0a
[Byte Buddy] REDEFINE BATCH #0 [1 of 1 type(s)]
[Byte Buddy] TRANSFORM java.lang.Object [null, null, loaded=true]
[Byte Buddy] REDEFINE COMPLETE 1 batch(es) containing 1 types [0 failed batch(es)]
[Byte Buddy] INSTALL HELLO BYTE BUDDY! on HELLO BYTE BUDDY!
[Byte Buddy] TRANSFORM java.lang.Object [null, null, loaded=true]
HELLO BYTE BUDDY!

...但我认为您在操纵 JDK 核心 classes 之前应该三思。看上面的日志。你有没有注意到日志行

[Byte Buddy] INSTALL HELLO BYTE BUDDY! on HELLO BYTE BUDDY!

正在打印两个明显使用您刚才建议的方法的对象?所以要小心!