6

解決できない問題があります。次の 2 つのクラスと継承関係があるとします。

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メソッド ( loadClasscalls )を呼び出してみませんか。defineClassその結果、instrument メソッドが呼び出されることを期待していましたが、残念ながらそうではありませんでした。代わりに、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 で A の前に B を変換しても問題は見られませんでした (私はASMを使用しました)。変換コードを外部で実行し、結果のクラスを (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 に答える