OutputStream Instrumentation:从 write 方法读取字节数组
OutputStream Instrumentation: Read byte Array from write Method
对于一个监控项目,我尝试使用 Javassist 检测 OutputStream
class。我想让 OutputStream
write()
方法打印任何 byte[]
给它的东西(编码是一个不同的问题,在这里无关紧要)。我的问题是在运行时用代理替换 OutputStream
class 会使 JVM 崩溃。使用 Javassist 仅操作字节码会失败,因为 Javassist ClassPool.get("java.io.OutputStream")
也会使 JVM 崩溃。
那个class就这么贱吗?同样的方法适用于 URI
class.
是否有另一种方法可以做我想做的事情,或者是我唯一的选择来替换 JRE 中的 rt.jar?
@Override
public byte[] transform(ClassLoader loader, String className,
Class<?> classBeingRedefined, ProtectionDomain protectionDomain,
byte[] classfileBuffer) {
ClassPool pool = ClassPool.getDefault();
try {
CtClass cc = pool.get("java.io.OutputStream");
CtMethod method =
Arrays.stream(cc.getDeclaredMethods()).filter(ctMethod -> ctMethod.getLongName().equals("java.io.OutputStream.write(byte[],int,int)"))
.findFirst().get();
method.insertBefore("System.out.println(new String(, java.nio.charset.StandardCharsets.UTF_8));");
return cc.toBytecode();
} catch (NotFoundException | CannotCompileException | IOException e) {
e.printStackTrace();
}
return null;
}
产生输出:
Exception in thread "main" java.lang.NoClassDefFoundError: org/springframework/boot/SpringApplication (wrong name: java/io/OutputStream)
因为我已经用我的“新”OutputStream
覆盖了第一个加载的 class。然而 OutputStream
class 永远不会被调用到转换方法中...
transform
方法是一个回调函数,可能会在每次加载 class 时被调用。您在 className
参数中收到 class 名称,在 classfileBuffer
.
中收到原始定义
因此,您要做的第一件事就是检查当前调用是否真的对应于您要转换的 class。如果没有,就 return null
。你不这样做,但是return一个java.io.OutputStream
的转换版本,不管你被要求转换哪个class。这就是你得到错误的原因。您被要求转换 class org/springframework/boot/SpringApplication
但 return 一个名为 java/io/OutputStream
的 class。请注意消息中的“错误名称”。当然,这种不匹配 class 会在不验证名称的情况下导致更多问题。
在检查当前调用负责 java.io.OutputStream
后,您应该使用提供的 classfileBuffer
来读取原始 class 版本,而不是使用 ClassPool.getDefault().get(…)
,因为 ClassPool
不知道您在参数中收到的 class 的版本,可能会发现它的错误版本或无法访问 class 全部。
但请注意,检测方法 OutputStream.write(byte[],int,int)
alone is unlikely to be sufficient. As its documentation 表示:
Subclasses are encouraged to override this method and provide a more efficient implementation.
这在实践中发生在所有相关实施中 classes。要记录覆盖方法的调用,您还必须对它们进行检测。还请注意,subclasses 可能会使用不委托给 write(byte[],int,int)
.
的实现覆盖 write(byte[])
对于一个监控项目,我尝试使用 Javassist 检测 OutputStream
class。我想让 OutputStream
write()
方法打印任何 byte[]
给它的东西(编码是一个不同的问题,在这里无关紧要)。我的问题是在运行时用代理替换 OutputStream
class 会使 JVM 崩溃。使用 Javassist 仅操作字节码会失败,因为 Javassist ClassPool.get("java.io.OutputStream")
也会使 JVM 崩溃。
那个class就这么贱吗?同样的方法适用于 URI
class.
是否有另一种方法可以做我想做的事情,或者是我唯一的选择来替换 JRE 中的 rt.jar?
@Override
public byte[] transform(ClassLoader loader, String className,
Class<?> classBeingRedefined, ProtectionDomain protectionDomain,
byte[] classfileBuffer) {
ClassPool pool = ClassPool.getDefault();
try {
CtClass cc = pool.get("java.io.OutputStream");
CtMethod method =
Arrays.stream(cc.getDeclaredMethods()).filter(ctMethod -> ctMethod.getLongName().equals("java.io.OutputStream.write(byte[],int,int)"))
.findFirst().get();
method.insertBefore("System.out.println(new String(, java.nio.charset.StandardCharsets.UTF_8));");
return cc.toBytecode();
} catch (NotFoundException | CannotCompileException | IOException e) {
e.printStackTrace();
}
return null;
}
产生输出:
Exception in thread "main" java.lang.NoClassDefFoundError: org/springframework/boot/SpringApplication (wrong name: java/io/OutputStream)
因为我已经用我的“新”OutputStream
覆盖了第一个加载的 class。然而 OutputStream
class 永远不会被调用到转换方法中...
transform
方法是一个回调函数,可能会在每次加载 class 时被调用。您在 className
参数中收到 class 名称,在 classfileBuffer
.
因此,您要做的第一件事就是检查当前调用是否真的对应于您要转换的 class。如果没有,就 return null
。你不这样做,但是return一个java.io.OutputStream
的转换版本,不管你被要求转换哪个class。这就是你得到错误的原因。您被要求转换 class org/springframework/boot/SpringApplication
但 return 一个名为 java/io/OutputStream
的 class。请注意消息中的“错误名称”。当然,这种不匹配 class 会在不验证名称的情况下导致更多问题。
在检查当前调用负责 java.io.OutputStream
后,您应该使用提供的 classfileBuffer
来读取原始 class 版本,而不是使用 ClassPool.getDefault().get(…)
,因为 ClassPool
不知道您在参数中收到的 class 的版本,可能会发现它的错误版本或无法访问 class 全部。
但请注意,检测方法 OutputStream.write(byte[],int,int)
alone is unlikely to be sufficient. As its documentation 表示:
Subclasses are encouraged to override this method and provide a more efficient implementation.
这在实践中发生在所有相关实施中 classes。要记录覆盖方法的调用,您还必须对它们进行检测。还请注意,subclasses 可能会使用不委托给 write(byte[],int,int)
.
write(byte[])