【问题标题】:Is there a way in Java to intercept the creation of a object by using Instrumentation?Java中有没有办法通过使用Instrumentation来拦截对象的创建?
【发布时间】:2020-02-04 10:21:48
【问题描述】:

我需要拦截所有 ClassNotFoundException 或 NoClassDefError 的创建:问题是其中一些异常被某些库捕获并以其他异常类型重新抛出,因此我无法检索类名。 有没有办法在 Java 中使用 Instrumentation 来做到这一点?

【问题讨论】:

  • 这可能通过 ASM 实现。我以前做过一些事情来改变内置 java 类的行为 - 请参阅 stackoverflow.com/a/48706364/898289
  • 您可以转换明确创建这些类实例的类。但我怀疑你是否可以拦截 JVM 本身创建的NoClassDefError 实例。
  • 我尝试使用 java.lang.instrument.Instrumentation.addTransformer(ClassFileTransformer) 编写 Java 代理,并使用 System.out.println() 记录所有类。不知何故,您 ClassNotFoundException 没有仪器“看到”类。我认为这门课一定有什么“特别”之处。
  • 是的,但是我对JVM在内部构造错误时调用构造函数有疑问。

标签: java exception instrumentation


【解决方案1】:

您可以编写自己的ClassLoader 实现,并在loadClass() 或其他可用方法中应用您的逻辑。 ClassLoader 是应用程序中ClassNotFoundException 的常用来源。除非第 3 方库更改默认的类加载过程(例如 OSGI),否则它们仍会调用您的 ClassLoader

【讨论】:

  • 我需要为所有运行时类加载器拦截这个异常
  • 类加载器使用委托模式,子类加载器在加载不了类时调用父类加载器。您应该能够用try/catch 包装对super 的调用,并在重新抛出异常之前发现异常。
  • 如果类路径中有一个库直接使用我的类加载器的父类加载器,它将不起作用
  • @KarolDowbecki 这仅在您的自定义类加载器上调用了loadClass 时才有效。一旦你返回一个通过super 调用解析的类,它的所有依赖项都将直接通过父加载器解析。
【解决方案2】:

以下示例将检测添加到ClassNotFoundException 的构造函数,并在调用它时执行System.err.println

我不确定如何从我认为您需要的工具调用回调。

import java.lang.instrument.ClassFileTransformer;
import java.lang.instrument.IllegalClassFormatException;
import java.lang.instrument.Instrumentation;
import java.lang.instrument.UnmodifiableClassException;
import java.security.ProtectionDomain;

import net.bytebuddy.jar.asm.ClassReader;
import net.bytebuddy.jar.asm.ClassVisitor;
import net.bytebuddy.jar.asm.ClassWriter;
import net.bytebuddy.jar.asm.MethodVisitor;
import net.bytebuddy.jar.asm.Opcodes;

public class ClassNotFoundExceptionIntercept {

    public static void premain(String agentArgs, Instrumentation inst) throws UnmodifiableClassException {
        inst.addTransformer(new ClassFileTransformer() {
            @Override
            public byte[] transform(ClassLoader l, String name, Class<?> c, ProtectionDomain d, byte[] b)
                    throws IllegalClassFormatException {
                if ("java/lang/ClassNotFoundException".equals(name)) {
                    return instrument(b);
                }
                return b;
            }
        }, true);
        inst.retransformClasses(java.lang.ClassNotFoundException.class);
    }

    private static byte[] instrument(byte[] originalBytes) {
        ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES);
        ClassAdapter adapter = new ClassAdapter(cw);
        ClassReader cr = new ClassReader(originalBytes);
        cr.accept(adapter, ClassReader.SKIP_FRAMES);
        return cw.toByteArray();
    }

    public static class ClassAdapter extends ClassVisitor implements Opcodes {
        public ClassAdapter(ClassVisitor cv) {
            super(ASM4, cv);
        }
        @Override
        public MethodVisitor visitMethod(int access, String name, String descriptor, String signature,
                String[] exceptions) {
            if ("<init>".equals(name)) {
                MethodVisitor mv = cv.visitMethod(access, name, descriptor, signature, exceptions);
                return new Wrapper(mv);
            } else {
                return super.visitMethod(access, name, descriptor, signature, exceptions);
            }
        }
    }

    private static class Wrapper extends MethodVisitor {
        public Wrapper(MethodVisitor mv) {
            super(Opcodes.ASM4, mv);
        }
        @Override
        public void visitMethodInsn(int opcode, String owner, String name, String desc, boolean itf) {
            mv.visitMethodInsn(opcode, owner, name, desc, itf);

            mv.visitFieldInsn(Opcodes.GETSTATIC, "java/lang/System", "err", "Ljava/io/PrintStream;");
            mv.visitLdcInsn("Constructor invoked");
            mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
        }
    }

}

这需要像这样使用 META-INF/MANIFEST.MF 编译成 JAR 文件

Manifest-Version: 1.0
Premain-Class: ClassNotFoundExceptionIntercept
Agent-Class: ClassNotFoundExceptionIntercept
Can-Retransform-Classes: true
Can-Redefine-Classes: true

并使用程序参数-javaagent:/home/adam/agent-example.jar调用

这可以证明可以与吞下异常的测试类一起使用

public class Test {

    public static void main(String[] args) {
        try {
            Class.forName("brexit");
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }

}

输出

Constructor invoked
Constructor invoked
Constructor invoked
java.lang.ClassNotFoundException: brexit
    at java.net.URLClassLoader.findClass(URLClassLoader.java:382)
    at java.lang.ClassLoader.loadClass(ClassLoader.java:424)
    at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:349)
    at java.lang.ClassLoader.loadClass(ClassLoader.java:357)
    at java.lang.Class.forName0(Native Method)
    at java.lang.Class.forName(Class.java:264)
    at Test.main(Test.java:9)

【讨论】:

  • 非常感谢,我试试看
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-06-18
  • 1970-01-01
相关资源
最近更新 更多