运行时拦截方法到returnclass实例

Intercept method at runtime to return class instance

我正在尝试更改一些方法代码,使其 return 成为固定值,而不是实际执行原始代码。 这个想法是创建一个加载器,使用指定一些 class 方法的 json,指定它应该 return 的结果(另一个 json 序列化实例)。类似的东西:

{
    "overrides": [
        {
            "className": "a.b.C",
            "method": "getUser",
            "returns": "{ \"name\": \"John\" }"
        }
    }
}

结果应该是,每次调用 C.getUser() 时,序列化的实例应该被 returned 而不是实际执行该方法(所有这一切都没有改变源代码)。

我试过这样的事情:

ByteBuddyAgent.install();
final ClassReloadingStrategy classReloadingStrategy = ClassReloadingStrategy.fromInstalledAgent();
new ByteBuddy().redefine(className).method(ElementMatchers.named(methodName))
    .intercept(FixedValue.nullValue()).make()
    .load(clase.getClassLoader(), classReloadingStrategy);

它 return 是 null 而不是执行方法体但是,我怎样才能 return 反序列化的结果呢?当我尝试使用 FixedValue.value(deserializatedInstance) 时,它会抛出以下异常:

> java.lang.RuntimeException: java.lang.IllegalStateException: Error invoking java.lang.instrument.Instrumentation#retransformClasses
    at es.abanca.heracles.ServiceLauncher.addTiming2(ServiceLauncher.java:129)
    at es.abanca.heracles.ServiceLauncher.establecerMocks(ServiceLauncher.java:65)
    at es.abanca.heracles.ServiceLauncher.run(ServiceLauncher.java:40)
    at es.abanca.heracles.MifidServiceLauncher.main(MifidServiceLauncher.java:6)
Caused by: java.lang.IllegalStateException: Error invoking java.lang.instrument.Instrumentation#retransformClasses
    at net.bytebuddy.dynamic.loading.ClassReloadingStrategy$Dispatcher$ForJava6CapableVm.retransformClasses(ClassReloadingStrategy.java:503)
    at net.bytebuddy.dynamic.loading.ClassReloadingStrategy$Strategy.apply(ClassReloadingStrategy.java:568)
    at net.bytebuddy.dynamic.loading.ClassReloadingStrategy.load(ClassReloadingStrategy.java:225)
    at net.bytebuddy.dynamic.TypeResolutionStrategy$Passive.initialize(TypeResolutionStrategy.java:100)
    at net.bytebuddy.dynamic.DynamicType$Default$Unloaded.load(DynamicType.java:6156)
    at es.abanca.heracles.ServiceLauncher.addTiming2(ServiceLauncher.java:127)
    ... 3 more
Caused by: java.lang.UnsupportedOperationException: class redefinition failed: attempted to change the schema (add/remove fields)
    at sun.instrument.InstrumentationImpl.retransformClasses0(Native Method)
    at sun.instrument.InstrumentationImpl.retransformClasses(InstrumentationImpl.java:144)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:498)
    at net.bytebuddy.dynamic.loading.ClassReloadingStrategy$Dispatcher$ForJava6CapableVm.retransformClasses(ClassReloadingStrategy.java:495)
    ... 8 more

提前谢谢大家

您的方法仅在目标 class 尚未加载时才有效。

当尝试修改(即重新转换)已经加载的 class 而不是刚刚加载(即重新定义)的 class 时,您需要确保

  • 使用retransform代替redefine
  • 避免通过 .disableClassFormatChanges().
  • 操纵目标 class 结构

我不是 ByteBuddy 专家,但我知道您可以通过 Advice API 在不更改 class 结构的情况下修改方法。我通常这样做:

import net.bytebuddy.agent.ByteBuddyAgent;
import net.bytebuddy.agent.builder.AgentBuilder;
import net.bytebuddy.asm.Advice;
import net.bytebuddy.dynamic.ClassFileLocator;
import org.acme.Sub;

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.is;
import static net.bytebuddy.matcher.ElementMatchers.named;

class Scratch {
  public static void main(String[] args) {
    System.out.println(new Sub("original name").getName());

    Instrumentation instrumentation = ByteBuddyAgent.install();
    new AgentBuilder.Default()
      .with(RETRANSFORMATION)
      .with(AgentBuilder.Listener.StreamWriting.toSystemError().withTransformationsOnly())
      .disableClassFormatChanges()
      .type(is(Sub.class))
      .transform((builder, typeDescription, classLoader, module) ->
        builder.visit(
          Advice
            .to(MyAspect.class, ClassFileLocator.ForClassLoader.ofSystemLoader())
            .on(named("getName"))
        )
      )
      .installOn(instrumentation);

    System.out.println(new Sub("original name").getName());
  }

  static class MyAspect {
    @Advice.OnMethodEnter(skipOn = Advice.OnDefaultValue.class)
    public static boolean before() {
      // Default value for boolean is false -> skip original method execution
      return false;
    }

    @Advice.OnMethodExit(onThrowable = Throwable.class, backupArguments = false)
    public static void after(
      @Advice.Return(readOnly = false, typing = DYNAMIC) Object returnValue
    )
    {
      System.out.println("MyAspect");
      // Here you can define your return value of choice, null or whatever else 
      returnValue = "dummy name";
    }
  }

}

控制台日志类似于:

original name
[Byte Buddy] TRANSFORM org.acme.Sub [sun.misc.Launcher$AppClassLoader@18b4aac2, null, loaded=true]
MyAspect
dummy name

可能有更简单的方法。如果是这样,Rafael Winterhalter 肯定比我更清楚。

您的问题是,您正在增强 class 的方式使其不符合重新转换的条件,因为您正在更改 class 的签名。您可以通过添加以下内容强制 Byte Buddy 尝试保留原始形状:

new ByteBuddy().with(Implementation.Context.Disabled.Factory.INSTANCE)

这样做,Byte Buddy 会禁用一些功能,但对于 FixedValue 这样的简单工具,这没有任何效果。