2009-08-06 9 views
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の計測をトリガできるかどうか、どのようにトリガできるかということが発生します。クラスファイルトランスフォーマのtransform()メソッドは、クラスAを明示的に呼び出す必要があります。だから私は読み始め、これにぶつかった:

java.lang.instrument.ClassFileTransformer.transform()のjavadocは言う:

変圧器は、すべての新しいクラス定義と、すべての クラス再定義 のために呼び出されます。新しいクラス定義 の要求は、 ClassLoader.defineClassで行われます。クラスの再定義のためのリクエスト は、 Instrumentation.redefineClassesまたはその ネイティブ相当品で行われます。

変換メソッドは、クラスローダのインスタンスと一緒に来て、そのBの計測が開始されたとき、クラスAとloadClass方法(loadClass呼び出しdefineClassに)自分自身を呼び出していない、なぜ私は、思いました。 私は、その結果、機器メソッドが呼び出されることを期待しましたが、悲しいことに、そうではありませんでした。代わりに、計装なしでクラスAがロードされました。 (エージェントは想定されているにもかかわらず負荷プロセスを横取りしません)

この問題を解決する方法はありますか?一部のバイトコードを操作するエージェントが別のクラスを手動でロードできないことが考えられる理由を見ていますか?

Bが操作される前に、Aがロードされ計測されているので、次のコードが正しく機能することに注意してください。

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

ありがとうございます!

答えて

8

私は太陽の上でBをAに変換すると何の問題も見られませんでした。1.6.0 _ 15と1.5.0 _ 17 JRE(私はASMを使用しました)。私は変換コードを外部で実行し、結果のクラスを検査することによって(javapなどで)変換コードを再確認します。私はあなたのエージェントの前にAが何らかの理由でロードされていないことを確認するために、あなたのクラスパス設定をチェックしたいと考えています(おそらく、premainをgetAllLoadedClassesでチェックインしてください)。


EDIT:

あなたはこのようなあなたのエージェントにクラスAをロードする場合:...

Class.forName("A"); 

その後、例外がスローされます。

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

これは理にかなっています - Aはエージェントの依存関係になり、エージェントがインスツルメントすることは意味がありませんそれ自身のコード。スタックオーバーフローの原因となる無限ループが発生します。そのため、AClassFileTransformerで処理されません。


完全性のために、ここに問題なく動作するテストコードがあります。前述のように、それはASMライブラリに依存します。

剤:Aため

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()); 
    } 

} 

方法インジェクター:

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()を追加しました。このコードで実行し

:あなたの答えのための

transform: MainInstrumented 
transform: B 
transform: A 
X 
+0

Thxを:

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

は...それは、この出力を生成します。 問題を別の観点から見てみましょう。 AとBの両方のクラスが空であるとします。トランスフォームメソッドの最初と最後にログを追加すると、どのクラスがエージェントによってロードされているのか、どの時点にあるのかがわかります。 は、実行:新しいBを() を結果は次のようになります。そのクラスBは、エージェントによってロードされ、その後クラスA され、手動でウルエージェントにClassloader.loadClass()メソッドを使用して負荷クラスAに今すぐ試すことができますBがそれを渡すとき? 結果は次のとおりです。Bはエージェント経由でロードされましたが、Aは実行されませんでした。 ? 乾杯 christoph –

+0

まずは、お返事と努力に感謝します。私はそれを感謝します。 Uが正しいです!私はJavassistをあらゆる変換に使用しました。 Javassistは変更を再コンパイルします。この結果、上記のコンパイルエラーが発生します。 ASMはバイトコードで直接動作し、再コンパイルなどの必要はありません。 エージェント内のクラスの読み込みについて:私はあなたの答えを理解していません。私はEclipseを使用し、エージェントに追加する場合: > //クラス名がBの場合 > Class.forName( "A"); と私はデバッガの実行に従います。例外はスローされず、エージェントは入力されません。 –

+0

もう一度、あなたは正しいです。あなたが言及した例外は正しいです。物事をより簡単にしましょう:両方のクラスにはメソッドがなく、まったく計装されることを意図していません。エージェントが行う唯一の考えは、クラスBがエージェントを渡す場合、Class.forName( "A");呼び出されるものとする。これは、クラスローディングの適切な順序を引き起こすはずです(最初にA、次にB)。この例を試してみてください。あなたはBだけがエージェントを渡すことがわかります!だから、エージェントの一員として呼び出されたときにAがエージェントを渡さない理由が生じる。 –