运行时拦截方法到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
这样的简单工具,这没有任何效果。
我正在尝试更改一些方法代码,使其 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
这样的简单工具,这没有任何效果。