如何通过ByteBuddy在JavaAgent中为Dog类新增getAge方法
问题描述
现有如下Dog类:
public class Dog { private String name = "Spike"; private int age = 3; public Dog() {} public String getName() { return name; } }
需要在JavaAgent中使用ByteBuddy重定义Dog类,新增一个返回int类型的getAge方法,使得最终类可以通过dog.getAge()调用,期望结果如下:
public class Dog { private String name = "Spike"; private int age = 3; public Dog() {} public String getName() { return name; } public int getAge() { return age; } }
三次尝试及错误
尝试1
代码:
new AgentBuilder.Default() .redefine(Dog.class) .defineMethod("getAge", int.class, Method.PUBLIC) .intercept(MethodDelegation.to(PitBullInterceptor.class)) .installOn(instrumentation);
报错:
The method redefine(Class) is undefined for the type AgentBuilder.Default
属于语法错误,不清楚该用哪些DynamicTypes、MethodDelegations或ElementMatchers配置AgentBuilder实现重定义/重基操作。
尝试2
结合ByteBuddyAgent.install使用:
public static void main(String[] args) throws Exception { ByteBuddyAgent.install(); new ByteBuddy() .redefine(Dog.class) .defineMethod("getAge", int.class, Method.PUBLIC) .intercept(MethodDelegation.to(PitBullInterceptor.class)) .make() .load(Dog.class.getClassLoader(), ClassReloadingStrategy.fromInstalledAgent()) .getLoaded() .newInstance(); } public static class PitBullInterceptor { private static int age = 1; public static int getAge() { return age; } }
报错:
java.lang.UnsupportedOperationException: class redefinition failed: attempted to add a method
尝试3
通过构建的Agent实现:
public static void premain(String arguments, Instrumentation instrumentation) { new AgentBuilder.Default() .disableClassFormatChanges().with(RetransformationStrategy.RETRANSFORM) .with(new ByteBuddy() .redefine(PitBull.class) .method(ElementMatchers.named("getAge")) .intercept(MethodDelegation.to(PitBullInterceptor.class)) .make() .load(Dog.class.getClassLoader())) .installOn(instrumentation); } public static class PitBull { private String name = "Kujo"; public String getName() { return name; } } public static class PitBullInterceptor { private static int age = 3; public static int getAge() { return age; } }
执行命令java -javaagent:agent.jar -jar app.jar后报错:
Exception in thread "main" java.lang.reflect.InvocationTargetException at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77) at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.base/java.lang.reflect.Method.invoke(Method.java:568) at java.instrument/sun.instrument.InstrumentationImpl.loadClassAndStartAgent(InstrumentationImpl.java:491) at java.instrument/sun.instrument.InstrumentationImpl.loadClassAndCallPremain(InstrumentationImpl.java:503) Caused by: java.lang.IllegalStateException: Class already loaded: class io.xxx.agent.boss.Agent$PitBull at net.bytebuddy.dynamic.loading.ByteArrayClassLoader.load(ByteArrayClassLoader.java:363) at net.bytebuddy.dynamic.loading.ClassLoadingStrategy$Default$WrappingDispatcher.load(ClassLoadingStrategy.java:367) at net.bytebuddy.dynamic.loading.ClassLoadingStrategy$Default.load(ClassLoadingStrategy.java:148) at net.bytebuddy.dynamic.TypeResolutionStrategy$Passive.initialize(TypeResolutionStrategy.java:101) at net.bytebuddy.dynamic.DynamicType$Default$Unloaded.load(DynamicType.java:6317) at net.bytebuddy.dynamic.DynamicType$Default$Unloaded.load(DynamicType.java:6305) at io.xxx.agent.boss.Agent.premain(Agent.java:21) ... 6 more *** java.lang.instrument ASSERTION FAILED ***: "result" with message agent load/premain call failed at t:\workspace\open\src\java.instrument\share\native\libinstrument\JPLISAgent.c line: 422 FATAL ERROR in native method: processing of -javaagent failed, processJavaStart failed
解决方案
错误原因分析
- 尝试1:
AgentBuilder.Default没有直接的redefine(Class)方法,正确方式是通过type()匹配目标类,再结合transform()进行修改。 - 尝试2:JVM类重定义机制不允许在类已加载后新增方法,必须在类加载前通过Agent增强,或者使用ByteBuddy的
rebase机制,但新增方法更适合在类加载阶段拦截处理。 - 尝试3:代码逻辑混乱,错误对
PitBull类进行重定义,AgentBuilder用法完全错误,且PitBull作为Agent内部类已被加载,导致重复加载报错。
正确实现代码
要实现新增getAge方法,需在premain中通过AgentBuilder拦截Dog类的加载过程,使用defineMethod新增方法,并通过FieldAccessor直接返回已有age字段:
import net.bytebuddy.agent.builder.AgentBuilder; import net.bytebuddy.implementation.FieldAccessor; import net.bytebuddy.matcher.ElementMatchers; import java.lang.instrument.Instrumentation; public class DogAgent { public static void premain(String args, Instrumentation instrumentation) { new AgentBuilder.Default() // 匹配目标Dog类,替换为实际全限定名 .type(ElementMatchers.named("com.yourpackage.Dog")) // 对匹配到的类进行转换 .transform((builder, typeDescription, classLoader, module) -> builder.defineMethod("getAge", int.class, java.lang.reflect.Modifier.PUBLIC) // 直接返回age字段的值 .intercept(FieldAccessor.ofField("age"))) // 安装Agent .installOn(instrumentation); } }
说明
ElementMatchers.named("com.yourpackage.Dog")精准匹配目标类,需替换成实际的Dog类全限定名。defineMethod用于新增public int getAge()方法。FieldAccessor.ofField("age")直接绑定到Dog类的age私有字段,ByteBuddy会自动生成返回该字段值的方法体,无需额外编写拦截器。- 这种方式在类加载前进行增强,完全符合JVM类修改规则,不会出现新增方法不被允许的问题。
打包与运行
- 确保项目依赖ByteBuddy:
<!-- Maven依赖 --> <dependency> <groupId>net.bytebuddy</groupId> <artifactId>byte-buddy-agent</artifactId> <version>1.14.12</version> </dependency>
- 打包Agent时,在
MANIFEST.MF中指定Premain-Class: com.yourpackage.DogAgent。 - 运行应用:
java -javaagent:dog-agent.jar -jar your-app.jar
内容的提问来源于stack exchange,提问作者stackoverflow
相关产品推荐
相关产品推荐

