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[])