6

我有一个我无法解决的问题。假设我们有以下两个类和一个继承关系:

public class A {
}

public class B extends A {
    public void foo() {}
}

我想检测其他代码,使其如下所示:

public class A {
    public void print() { }
}

public class B extends A {
     public void foo() { print(); }
}

为了实现这个目标,我将我的实现基于java.lang.instrument包,使用带有我自己的类文件转换器的代理。该机制也称为动态字节码检测。

到目前为止小菜一碟。现在,我的测试方法执行以下操作:

代码:

B b = new B();
b.foo();

由于检测包中的以下限制,这不起作用:调用时new B(),检测从类 B 开始,并在加载被操作类时以编译错误结束,因为超类 A 还没有 print() 方法!问题出现了,如果以及如何在 B 类之前触发 A 类的检测。我的 classfiletransformer 的 transform() 方法应该用 A 类显式调用!所以我开始阅读并遇到了这个:

java.lang.instrument.ClassFileTransformer.transform()javadoc 说:

每个新的类定义和每个类重新定义都会调用转换器。使用 ClassLoader.defineClass 请求新的类定义。使用 Instrumentation.redefineClasses 或其本机等效项提出类重定义请求。

transform 方法带有一个类加载器实例,所以我想,当 B 的检测开始时,为什么不自己用类 A调用该loadClass方法(loadClass调用)。defineClass我预计仪器方法会因此被调用,但遗憾的是事实并非如此。相反,该类A是在没有检测的情况下加载的。(尽管应该拦截加载过程,但代理不会拦截)

任何想法,如何解决这个问题?您是否看到一个原因,为什么操作某些字节码的代理无法手动加载另一个类,然后希望也可以通过该/任何代理发送?

请注意,由于在操作 B 之前已加载和检测 A,因此以下代码可以正常工作。

A a =  new A();
B b = new B();
b.foo();

非常感谢!

4

1 回答 1

8

在 Sun 1.6.0_15 和 1.5.0_17 JRE(我使用ASM)上,当我在 A 之前转换 B 时,我没有看到任何问题。我会通过在外部运行转换代码并检查生成的类(例如使用 javap)来仔细检查转换代码。我还会检查您的类路径配置,以确保由于某种原因没有在您的代理之前加载 A(也许使用 getAllLoadedClasses 检查您的premain)。


编辑:

如果您A像这样在代理中加载类:

Class.forName("A");

...然后抛出异常:

Exception in thread "main" java.lang.NoSuchMethodError: B.print()V

这是有道理的 -A成为代理的依赖项,代理检测自己的代码是没有意义的。你会得到一个导致堆栈溢出的无限循环。因此,A不被ClassFileTransformer.


为了完整起见,这是我的测试代码,可以正常工作。如前所述,它依赖于 ASM 库。

中介:

public class ClassModifierAgent implements ClassFileTransformer {

  public byte[] transform(ClassLoader loader, String className,
      Class<?> classBeingRedefined, ProtectionDomain protectionDomain,
      byte[] classfileBuffer) throws IllegalClassFormatException {
    System.out.println("transform: " + className);
    if ("A".equals(className)) {
      return new AModifier().modify(classfileBuffer);
    }
    if ("B".equals(className)) {
      return new BModifier().modify(classfileBuffer);
    }
    return classfileBuffer;
  }

  /** Agent "main" equivalent */
  public static void premain(String agentArguments,
      Instrumentation instrumentation) {
    instrumentation.addTransformer(new ClassModifierAgent());
  }

}

方法注入器A

public class AModifier extends Modifier {

  @Override
  protected ClassVisitor createVisitor(ClassVisitor cv) {
    return new AVisitor(cv);
  }

  private static class AVisitor extends ClassAdapter {

    public AVisitor(ClassVisitor cv) { super(cv); }

    @Override
    public void visitEnd() {
      MethodVisitor mv = cv.visitMethod(Opcodes.ACC_PUBLIC, "print", "()V",
          null, null);
      mv.visitCode();
      mv.visitFieldInsn(Opcodes.GETSTATIC, "java/lang/System", "out",
          "Ljava/io/PrintStream;");
      mv.visitLdcInsn("X");
      mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/io/PrintStream",
          "println", "(Ljava/lang/String;)V");
      mv.visitInsn(Opcodes.RETURN);
      mv.visitMaxs(2, 1);
      mv.visitEnd();

      super.visitEnd();
    }

  }

}

方法替换B

public class BModifier extends Modifier {

  @Override
  protected ClassVisitor createVisitor(ClassVisitor cv) {
    return new BVisitor(cv);
  }

  class BVisitor extends ClassAdapter {

    public BVisitor(ClassVisitor cv) { super(cv); }

    @Override
    public MethodVisitor visitMethod(int access, String name, String desc,
        String signature, String[] exceptions) {
      if ("foo".equals(name)) {
        MethodVisitor mv = cv.visitMethod(Opcodes.ACC_PUBLIC, "foo", "()V",
            null, null);
        mv.visitCode();
        mv.visitVarInsn(Opcodes.ALOAD, 0);
        mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "B", "print", "()V");
        mv.visitInsn(Opcodes.RETURN);
        mv.visitMaxs(1, 1);
        mv.visitEnd();
        return new EmptyVisitor();
      } else {
        return super.visitMethod(access, name, desc, signature, exceptions);
      }
    }
  }
}

通用基础代码:

public abstract class Modifier {

  protected abstract ClassVisitor createVisitor(ClassVisitor cv);

  public byte[] modify(byte[] data) {
    ClassReader reader = new ClassReader(data);
    ClassWriter writer = new ClassWriter(reader, ClassWriter.COMPUTE_FRAMES);
    ClassVisitor visitor = writer;
    visitor = new CheckClassAdapter(visitor);
    visitor = createVisitor(visitor);
    reader.accept(visitor, 0);
    return writer.toByteArray();
  }

}

对于一些可见的结果,我添加了一个System.out.println('X');A.print().

在此代码上运行时:

public class MainInstrumented {
  public static void main(String[] args) {
    new B().foo();
  }
}

...它产生这个输出:

transform: MainInstrumented
transform: B
transform: A
X
于 2009-08-06T16:23:08.467 回答