5

意図:

私はjava.lang.instrumentパッケージを使用して、Java プログラム用のインストルメンテーションを作成しています。各メソッドの最初と最後にメソッド呼び出しを追加するために、このシステムを介してバイトコード操作を使用するという考え方です。一般的に言えば、変更された Java メソッドは次のようになります。

public void whateverMethod(){
    MyFancyProfiler.methodEntered("whateverMethod");
    //the rest of the method as usual...
    MyFancyProfiler.methodExited("whateverMethod");
}

MyFancyProfilerpremainは、メソッド ( の一部) の実行中に初期化される、比較的複雑なシステムへのエントリ ポイントjava.lang.instrumentです。

編集-この質問の解決策でMyFancyProfiler説明されているようなメカニズムを通じて、システムの残りの部分への参照を取得する静的 API が含まれています。参照は として取得され、リフレクションを介して適切な呼び出しが行われるため、現在の ClassLoader が基礎となるクラスを認識していなくても機能します。Object

困難

単純な Java プログラムの場合、このアプローチはうまく機能します。「実際の」アプリ (ウィンドウ化されたアプリケーション、特にRCP/OSGiアプリケーションなど) の場合、ClassLoaders. ClassLoadersクラスを見つける方法がわからない人もいるMyFancyProfilerので、 で静的メソッドを呼び出そうとすると例外がスローされMyFancyProfilerます。

これに対する私の解決策(および私の実際の問題が発生している場所)は、現在、反射的に呼び出しMyFancyProfilerて遭遇したそれぞれに「注入」することです。その要点は次のとおりです。ClassLoaderdefineClass

public byte[] transform(ClassLoader loader, String className, /* etc... */) {
  if(/* this is the first time I've seen loader */){
    //try to look up `MyFancyProfiler` in `loader`.
    if(/* loader can't find my class */){
      // get the bytes for the MyFancyProfiler class
      // reflective call to 
      // loader.defineClass(
      //   "com.foo.bar.MyFancyProfiler", classBytes, 0, classBytes.length);
    }
  }
  // actually do class transformation via ASM bytecode manipulation
}

詳細情報を編集- このインジェクションの理由は、ロードされた ClassLoader に関係なく、すべてのクラスがMyFancyProfiler.methodEntered直接呼び出すことができるようにするためです。その呼び出しを行ったら、MyFancyProfilerリフレクションを使用してシステムの残りの部分とやり取りする必要があります。そうしないと、直接参照しようとすると InvocationTargetException または NoClassDef 例外が発生します。私は現在、「直接的な」依存関係MyFancyProfilerがJREシステムクラスだけであるように動作しているので、問題ないようです。

問題

これでも機能します!ほとんどの時間!しかし、Eclipse をトレースしようとしている (コマンド ラインから IDE を起動している) ときに遭遇する少なくとも 2 つの個別の ClassLoader については、メソッドNullPointerException内から次のようなメッセージが表示されます。ClassLoader.defineClass

java.lang.NullPointerException
    at java.lang.ClassLoader.checkPackageAccess(ClassLoader.java:500)
    at java.lang.ClassLoader.defineClass1(Native Method)
    at java.lang.ClassLoader.defineClass(ClassLoader.java:791)
    at java.lang.ClassLoader.defineClass(ClassLoader.java:634)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:601)
    // at my code that calls the reflection

ClassLoader.javaの 500 行目は、コンストラクター時に初期化される Set のように見える へdomains.add(pd)の呼び出しであり、(私が知る限り)「デフォルト」である必要があります。したがって、その行が . を引き起こす明白な方法はわかりません。現在、私は困惑しており、誰かがこれについて何らかの洞察を提供できることを望んでいます。domainspdProtectionDomainProtectionDomainNullPointerException

defineClassがこのように失敗する原因は何ですか? また、明らかな解決策がない場合は、全体的な問題に対する潜在的な代替アプローチを提供できますか?

4

2 に答える 2

4

ClassLoader にコードを挿入する代わりに、MyFancyProfiler を含む jar をブートストラップ クラスローダーにロードしてみてください。これを行う最も簡単な方法は、javaagent jar のマニフェストに次の行を追加することです。

Boot-Class-Path: fancy-profiler-bootstrap-stuff.jar

これにより、そのjar内のすべてが、OSGiや友人を含むすべてのクラスローダーからアクセスできるようになります.

于 2013-04-17T05:44:47.530 に答える