ByteBuddy Advice无法访问静态方法与字段的解决方法咨询
问题:ByteBuddy Advice增强FileInputStream构造方法时无法访问静态方法与字段
使用ByteBuddy的Advice对FileInputStream的单参String构造方法进行增强,onEnter方法可以被调用,但访问自定义静态方法JustPrint.sayHello()和静态字段iveBeenHere时抛出NoClassDefFoundError,代码及运行报错如下:
测试代码
package com.example.javaagent.instrumentation; import net.bytebuddy.agent.ByteBuddyAgent; import net.bytebuddy.agent.builder.AgentBuilder; import net.bytebuddy.asm.Advice; import org.junit.jupiter.api.Test; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import static net.bytebuddy.matcher.ElementMatchers.*; public class FileInputStreamConstructorInstrumentationTests2 { @Test public void testByteBuddyfileInputStreamConstructor() { ByteBuddyAgent.install(); AgentBuilder.Identified.Extendable extendableAgentBuilder = new AgentBuilder.Default() .with(AgentBuilder.RedefinitionStrategy.RETRANSFORMATION) .with(AgentBuilder.RedefinitionStrategy.Listener.StreamWriting.toSystemError()) .with(AgentBuilder.Listener.StreamWriting.toSystemError().withTransformationsOnly()) .with(AgentBuilder.InstallationListener.StreamWriting.toSystemError()) .ignore(none()) .type(named("java.io.FileInputStream")) .transform(new AgentBuilder.Transformer.ForAdvice()); extendableAgentBuilder = extendableAgentBuilder.transform( new AgentBuilder.Transformer.ForAdvice() .include(Thread.currentThread().getContextClassLoader()) .advice( isConstructor().and(takesArguments(1)).and(takesArgument(0, String.class)), this.getClass().getName() + "$FileInputStreamCtorString")); extendableAgentBuilder.installOnByteBuddyAgent(); String filename = "example.txt"; createFile(filename); try { FileInputStream inputStream = new FileInputStream(filename); int data = inputStream.read(); while (data != -1) { System.out.print((char) data); data = inputStream.read(); } inputStream.close(); } catch (IOException e) { e.printStackTrace(); } deleteFile(filename); } public static class FileInputStreamCtorString { public static boolean iveBeenHere = false; @Advice.OnMethodEnter(suppress = Throwable.class) public static void onEnter(@Advice.Argument(0) String name) { System.out.println("FileInputStreamCtorString: entered with param "+ name); System.out.println("FileInputStreamCtorString: second log line"); try{ JustPrint.sayHello(); }catch (Throwable e){ System.out.println("FileInputStreamCtorString: inside catch: " + e.getMessage()); e.printStackTrace(); } System.out.println("FileInputStreamCtorString: after calling static method"); try { iveBeenHere = true; } catch (Throwable e) { System.out.println("FileInputStreamCtorString: inside catch: " + e.getMessage()); e.printStackTrace(); } System.out.println("FileInputStreamCtorString: after setting static field"); } } public static class JustPrint{ public static void sayHello(){ System.out.println("Did I say 'hello'?"); } } private static void deleteFile(String filename) { File file = new File(filename); if (file.exists()) { if (file.delete()) { System.out.println("File deleted successfully"); } else { System.out.println("Failed to delete file"); } } else { System.out.println("File does not exist"); } } private static void createFile(String filename) { File file = new File(filename); if (!file.exists()) { try { file.createNewFile(); System.out.println("File created successfully"); } catch (IOException e) { e.printStackTrace(); } } else { System.out.println("File already exists"); } } }
运行报错输出
[Byte Buddy] BEFORE_INSTALL net.bytebuddy.agent.builder.AgentBuilder$Default$ExecutingTransformer$ByteBuddy$ModuleSupport@3af17be2 on sun.instrument.InstrumentationImpl@f9879ac [Byte Buddy] REDEFINE BATCH #0 [1 of 1 type(s)] [Byte Buddy] TRANSFORM java.io.FileInputStream [null, module java.base, Thread[Test worker,5,main], loaded=true] [Byte Buddy] REDEFINE COMPLETE 1 batch(es) containing 1 types [0 failed batch(es)] [Byte Buddy] INSTALL net.bytebuddy.agent.builder.AgentBuilder$Default$ExecutingTransformer$ByteBuddy$ModuleSupport@3af17be2 on sun.instrument.InstrumentationImpl@f9879ac File created successfully FileInputStreamCtorString: entered with param example.txt FileInputStreamCtorString: second log line FileInputStreamCtorString: inside catchcom/example/javaagent/instrumentation/FileInputStreamConstructorInstrumentationTests2$JustPrint java.lang.NoClassDefFoundError: com/example/javaagent/instrumentation/FileInputStreamConstructorInstrumentationTests2$JustPrint at java.base/java.io.FileInputStream.<init>(FileInputStream.java:111) at com.example.javaagent.instrumentation.FileInputStreamConstructorInstrumentationTests2.testByteBuddyfileInputStreamConstructor(FileInputStreamConstructorInstrumentationTests2.java:49) 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 worker.org.gradle.process.internal.worker.GradleWorkerMain.run(GradleWorkerMain.java:69) at worker.org.gradle.process.internal.worker.GradleWorkerMain.main(GradleWorkerMain.java:74) FileInputStreamCtorString: after calling static method FileInputStreamCtorString: inside catchcom/example/javaagent/instrumentation/FileInputStreamConstructorInstrumentationTests2$FileInputStreamCtorString java.lang.NoClassDefFoundError: com/example/javaagent/instrumentation/FileInputStreamConstructorInstrumentationTests2$FileInputStreamCtorString at java.base/java.io.FileInputStream.<init>(FileInputStream.java:111) at com.example.javaagent.instrumentation.FileInputStreamConstructorInstrumentationTests2.testByteBuddyfileInputStreamConstructor(FileInputStreamConstructorInstrumentationTests2.java:49) 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 org.gradle.process.internal.worker.child.SystemApplicationClassLoaderWorker.call(SystemApplicationClassLoaderWorker.java:71) at worker.org.gradle.process.internal.worker.GradleWorkerMain.run(GradleWorkerMain.java:69) at worker.org.gradle.process.internal.worker.GradleWorkerMain.main(GradleWorkerMain.java:74) FileInputStreamCtorString: after setting static field File deleted successfully
解决方案
问题根源
FileInputStream属于JDK核心模块java.base,Java 9+的模块系统默认限制模块间的访问权限。增强后的FileInputStream代码运行在java.base模块的上下文里,无法自动访问你自定义的类(位于未命名模块),导致类加载失败。
解决步骤
1. 添加JVM参数开放模块访问
在测试运行时添加以下JVM参数,允许java.base模块的java.io包访问未命名模块(你的测试类所在模块):
--add-opens java.base/java.io=ALL-UNNAMED
如果是Gradle项目,可以在build.gradle的测试配置中添加:
test { jvmArgs '--add-opens', 'java.base/java.io=ALL-UNNAMED' }
2. 优化ByteBuddy配置(可选)
调整AgentBuilder的transform逻辑,合并重复的Transformer.ForAdvice配置,避免冗余:
AgentBuilder.Identified.Extendable extendableAgentBuilder = new AgentBuilder.Default() .with(AgentBuilder.RedefinitionStrategy.RETRANSFORMATION) .with(AgentBuilder.RedefinitionStrategy.Listener.StreamWriting.toSystemError()) .with(AgentBuilder.Listener.StreamWriting.toSystemError().withTransformationsOnly()) .with(AgentBuilder.InstallationListener.StreamWriting.toSystemError()) .ignore(none()) .type(named("java.io.FileInputStream")) .transform(new AgentBuilder.Transformer.ForAdvice() .include(Thread.currentThread().getContextClassLoader()) .advice(isConstructor().and(takesArguments(1)).and(takesArgument(0, String.class)), FileInputStreamCtorString.class.getName()));
原理说明
添加--add-opens参数后,java.base模块的java.io包对未命名模块开放访问权限,增强后的FileInputStream代码就能正常加载并访问你自定义的静态类和字段。同时,确保ByteBuddy的include方法指定了包含自定义类的类加载器,保证Advice代码能正确找到依赖类。
内容的提问来源于stack exchange,提问作者Meir Benayoun
相关产品推荐
相关产品推荐

