使用Byte Buddy+Java Agent增强String实现AutoCloseable遇异常求助
问题描述
参考Byte Buddy忽略类相关实现,尝试让java.lang.String实现AutoCloseable接口并重写close方法,但运行时出现异常及VM断开问题,具体情况如下:
尝试的代码
初始AgentBuilder实现
new AgentBuilder.Default() .with(new AgentBuilder.InitializationStrategy.SelfInjection.Eager()) .ignore(ElementMatchers.none()) .type((ElementMatchers.named("String"))) .transform((builder, typeDescription, classLoader, module) -> builder .implement(AutoCloseable.class) .method(named("close")) .intercept(Advice.to(closeInterceptor.class)) ).installOn(instrumentation);
初始拦截器代码
class closeInterceptor { public void intercept(@Origin Class<?> type) { try { Field field = type.getDeclaredField("value"); field.setAccessible(true); Object ref = field.get(null); // 尝试处理java.lang.String的内部字符存储 } catch (NoSuchFieldException | IllegalAccessException e) { throw new RuntimeException(e); } } }
premain方法代码
public static void premain(String arguments, Instrumentation instrumentation) { System.out.println("Agent for instrument ignored class like java class "); ByteBuddyAgent.install(); new ByteBuddy() .ignore(ElementMatchers.none()) .redefine(String.class) .ignoreAlso(ElementMatchers.none()) .implement(AutoCloseable.class) .method(named("close")) .intercept(MethodDelegation.to(closeInterceptor.class)) .make() .load(ClassLoader.getSystemClassLoader()) .getLoaded(); new AgentBuilder.Default() .with(new AgentBuilder.InitializationStrategy.SelfInjection.Eager()) .ignore(ElementMatchers.none()) .type((ElementMatchers.any())) .transform((builder, typeDescription, classLoader, module) -> builder .method(ElementMatchers.any()) .intercept(Advice.to(IgnoreClass.class)) ).installOn(instrumentation); }
更新后的拦截器代码
class closeInterceptor { private final Logger logger = LoggerFactory.getLogger(closeInterceptor.class); public void intercept(@Origin Class<?> type) { try { Field field = type.getDeclaredField("value"); field.setAccessible(true); Object ref = field.get(null); // 尝试处理java.lang.String的内部字符存储 } catch (NoSuchFieldException | IllegalAccessException e) { throw new RuntimeException(e); } } }
遇到的问题
- 拦截器内的日志/输出无任何内容
- 运行时出现VM断开错误:
Application Server was not connected before run configuration stop, reason: Unable to ping server at localhost:1099,更换端口无效 - 抛出绑定异常:
Agent for instrument ignored class like java class java.lang.reflect.InvocationTargetException 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 sun.instrument.InstrumentationImpl.loadClassAndStartAgent(InstrumentationImpl.java:386) at sun.instrument.InstrumentationImpl.loadClassAndCallPremain(InstrumentationImpl.java:401) Caused by: java.lang.IllegalArgumentException: None of [] allows for delegation from public abstract void java.lang.AutoCloseable.close() throws java.lang.Exception at net.bytebuddy.implementation.bind.MethodDelegationBinder$Processor.bind(MethodDelegationBinder.java:1026) ... Exception in thread "main" FATAL ERROR in native method: processing of -javaagent failed Disconnected from the target VM, address: '127.0.0.1:49960', transport: 'socket' Disconnected from server
本人对字节码操作和Byte Buddy了解有限,希望解决上述异常,成功实现需求。(新年快乐!)
解决方案
1. 修复方法绑定异常
报错核心是AutoCloseable.close()无法与拦截器方法绑定,原因是签名不匹配:
AutoCloseable.close()声明抛出Exception,但拦截器方法未声明该异常@Origin Class<?> type参数不符合接口方法实现的绑定规则,应使用@This获取当前String实例
修改后的拦截器代码:
class closeInterceptor { private final Logger logger = LoggerFactory.getLogger(closeInterceptor.class); public void intercept(@This String stringInstance) throws Exception { try { Field field = String.class.getDeclaredField("value"); field.setAccessible(true); char[] value = (char[]) field.get(stringInstance); // 此处添加字符存储处理逻辑 logger.info("处理String实例: {}", stringInstance); } catch (NoSuchFieldException | IllegalAccessException e) { throw new RuntimeException(e); } } }
2. 修正premain中的错误操作
- 无需重复调用
ByteBuddyAgent.install(),直接使用premain传入的Instrumentation实例即可 java.lang.String属于核心系统类,不能用普通redefine方式修改,必须通过AgentBuilder结合Instrumentation操作- 全局拦截所有类的所有方法会导致VM崩溃,仅需处理
String类即可
修正后的premain代码:
public static void premain(String arguments, Instrumentation instrumentation) { System.out.println("Agent started: modifying String to implement AutoCloseable"); new AgentBuilder.Default() // 关闭默认忽略规则,允许修改系统类 .ignore(ElementMatchers.none()) // 定位到java.lang.String类 .type(ElementMatchers.named("java.lang.String")) .transform((builder, typeDesc, classLoader, module) -> builder // 实现AutoCloseable接口 .implement(AutoCloseable.class) // 将close方法绑定到拦截器 .method(ElementMatchers.named("close")) .intercept(MethodDelegation.to(closeInterceptor.class)) ) // 添加控制台错误监听,便于排查问题 .with(new AgentBuilder.Listener.FallbackToConsole()) .installOn(instrumentation); }
3. 解决VM断开问题
VM断开的根源是错误的字节码修改导致VM崩溃,按上述方案修正后即可解决。另外需注意:
- 运行时添加参数
--add-opens java.base/java.lang=ALL-UNNAMED,允许Agent访问String的内部字段 - 避免用应用服务器配置运行,直接以普通Java应用启动,防止端口冲突
验证方式
编写测试代码验证功能:
public class TestStringClose { public static void main(String[] args) { try (String testStr = "test") { System.out.println("try-with-resources内的字符串: " + testStr); } catch (Exception e) { e.printStackTrace(); } } }
运行时添加JavaAgent参数:-javaagent:你的AgentJar路径.jar,同时带上--add-opens java.base/java.lang=ALL-UNNAMED,此时应能看到拦截器的日志输出,且VM正常运行。
内容的提问来源于stack exchange,提问作者luceat-lux-vestra
相关产品推荐
相关产品推荐

