17

クラスをインターセプトして、どのクラスがアプリケーションにロードされているかを出力するカスタム クラスローダーをセットアップしようとしています。クラスローダーはこんな感じ

public class MyClassLoader extends ClassLoader {
    @Override
    public Class<?> loadClass(String name) throws ClassNotFoundException {
        System.out.println("Loading: " + name);
        return super.loadClass(name);
    }
}     

ロードするすべてのクラスの名前を吐き出すだけです。ただし、コードを実行しようとすると、

import org.python.util.PythonInterpreter;
public class Scripts {
    public String main(){

        PythonInterpreter p = new PythonInterpreter();
        p.exec("print 'Python ' + open('.gitignore').read()");

        return "Success! Nothing broke";
    }
}

経由

MyClassLoader bcl = new MyClassLoader();
Class c = bcl.loadClass("Scripts");

Method m = c.getMethod("main");
String result = (String) m.invoke(c.getConstructor().newInstance());

それは印刷されます

Loading: Scripts
Loading: java.lang.Object
Loading: java.lang.String
Loading: org.python.util.PythonInterpreter
Python build/
.idea/*
*.iml
RESULT: Success! Nothing broke

これはかなり奇妙に思えます。は単純なクラスではなく、パッケージorg.python.util.PythonInterpreter内の他の多くのクラスに依存しています。org.python.utilこれらのクラスは明らかにロードされています。これは、exec'd python コードが何かを実行してファイルを読み取ることができるためです。ただし、何らかの理由で、これらのクラスは、ロードされたクラスローダーによってロードされていませんPythonInterpreter

何故ですか?Cクラスをロードするために使用されるクラスローダーは、 が必要とする他のすべてのクラスをロードするために使用されるという印象を受けましたCが、ここでは明らかにそうではありません。その仮定は間違っていますか?Cもしそうなら、すべての推移的な依存関係がクラスローダーによってロードされるように設定するにはどうすればよいですか?

編集:

URLClassLoader提案されたを使用したいくつかの実験。の委任を変更しましたloadClass():

try{
    byte[] output = IOUtils.toByteArray(this.getResourceAsStream(name));
    return instrument(defineClass(name, output, 0, output.length));
}catch(Exception e){
    return instrument(super.loadClass(name));
}

また、プレーンな ClassLoader ではなく、MyClassLoader サブクラス URLClassLoader を作成し、次の方法で URL を取得します。

super(((URLClassLoader)ClassLoader.getSystemClassLoader()).getURLs());

しかし、それは正しいことではないようです。特に、getResourceAsStream()Jython lib のような非システム クラスであっても、要求しているすべてのクラスに対して null が返されます。

4

5 に答える 5

20

クラスローディングの基礎

クラスローダーを拡張してクラスのロード方法を変更するには、主に次の 2 つの場所があります。

  • findClass(String name) - 通常の親の最初の委譲を持つクラスを検索する場合は、このメソッドをオーバーライドします。
  • loadClass(String name, boolean resolve) - クラスのロード委任が行われる方法を変更する場合は、このメソッドをオーバーライドします。

ただし、クラスは、java.lang.ClassLoader によって提供される最終の defineClass(...) メソッドからのみ取得できます。ロードされているすべてのクラスを取得したいので、loadClass( String, boolean ) をオーバーライドし、その中で defineClass(...) への呼び出しを使用する必要があります。

: defineClass(...) メソッドの内部には、JVM のネイティブ側への JNI バインディングがあります。そのコード内で、java.* パッケージ内のクラスのチェックがあります。これらのクラスは、システム クラス ローダーによってロードされるだけです。これにより、Java 自体の内部をいじるのを防ぐことができます。

子ファースト ClassLoader の例

これは、作成しようとしている ClassLoader の非常に単純な実装です。必要なすべてのクラスが親クラス ローダーで利用できると想定しているため、クラス バイトのソースとして親を使用するだけです。この実装では簡潔にするために Apache Commons IO を使用していますが、簡単に削除できます。

import java.io.IOException;
import java.io.InputStream;

import static org.apache.commons.io.IOUtils.toByteArray;
import static org.apache.commons.io.IOUtils.closeQuietly;
...
public class MyClassLoader
  extends ClassLoader {
  MyClassLoaderListener listener;

  MyClassLoader(ClassLoader parent, MyClassLoaderListener listener) {
    super(parent);
    this.listener = listener;
  }

  @Override
  protected Class<?> loadClass(String name, boolean resolve)
    throws ClassNotFoundException {
    // respect the java.* packages.
    if( name.startsWith("java.")) {
      return super.loadClass(name, resolve);
    }
    else {
      // see if we have already loaded the class.
      Class<?> c = findLoadedClass(name);
      if( c != null ) return c;

      // the class is not loaded yet.  Since the parent class loader has all of the
      // definitions that we need, we can use it as our source for classes.
      InputStream in = null;
      try {
        // get the input stream, throwing ClassNotFound if there is no resource.
        in = getParent().getResourceAsStream(name.replaceAll("\\.", "/")+".class");
        if( in == null ) throw new ClassNotFoundException("Could not find "+name);

        // read all of the bytes and define the class.
        byte[] cBytes = toByteArray(in);
        c = defineClass(name, cBytes, 0, cBytes.length);
        if( resolve ) resolveClass(c);
        if( listener != null ) listener.classLoaded(c);
        return c;
      } catch (IOException e) {
        throw new ClassNotFoundException("Could not load "+name, e);
      }
      finally {
        closeQuietly(in);
      }
    }
  }
}

これは、クラスのロードを監視するための単純なリスナー インターフェイスです。

public interface MyClassLoaderListener {
  public void classLoaded( Class<?> c );
}

次に、現在のクラス ローダーを親として MyClassLoader の新しいインスタンスを作成し、クラスがロードされるときにクラスを監視できます。

MyClassLoader classLoader = new MyClassLoader(this.getClass().getClassLoader(), new MyClassLoaderListener() {
  public void classLoaded(Class<?> c) {
    System.out.println(c.getName());
  }
});
classLoader.loadClass(...);

これは最も一般的なケースで機能し、クラスがロードされたときに通知を受け取ることができます。ただし、これらのクラスのいずれかが独自の子ファースト クラス ローダーを作成する場合、ここで追加された通知コードをバイパスできます。

より高度なクラスの読み込み

ロードされているクラスを実際にトラップするには、子クラス ローダーが loadClass(String, boolean) をオーバーライドする場合でも、ロードするクラスと ClassLoader.defineClass(...) に対して行う可能性のある呼び出しの間にコードを挿入する必要があります。 . これを行うには、 ASMなどのツールを使用してバイト コードの書き換えを開始する必要があります。このメソッドを使用して java.net.URL コンストラクター呼び出しを書き換える GitHubのChlorineというプロジェクトがあります。ロード時にクラスをいじることに興味がある場合は、そのプロジェクトをチェックしてください。

于 2012-11-16T18:51:59.377 に答える
2

ロードされたクラスを出力したい場合は、JVM で verbose:class オプションをオンにしてみてはどうでしょうか?

java -verbose:class your.class.name.here

直接の質問に答えるには:

何故ですか?クラスCをロードするために使用されるクラスローダーは、Cが必要とする他のすべてのクラスをロードするために使用されるという印象を受けましたが、ここでは明らかにそうではありません。その仮定は間違っていますか?そうである場合、C のすべての推移的な依存関係がクラスローダーによってロードされるように設定するにはどうすればよいですか?

ClassLoader の検索中、検索はリーフ ClassLoader からルートに実行されます。Java が新しいクラスをロードする必要があることが判明すると、ClassLoader ツリーのルートからクラス解決を開始したリーフに戻って実行されます。

なんで?カスタム クラスが Java 標準ライブラリから何かをロードする必要があるかどうかを検討してください。正解は、クラスを最大限に共有できるように、これを System ClassLoader によってロードする必要があるということです。特に、ロードされているクラスがさらに多くのクラスをロードする可能性があると考える場合。

これにより、複数のシステム Classes インスタンスが異なる ClassLoader にロードされる可能性があるという問題も解決されます (それぞれが同じ完全修飾名を持つ)。EDITクラスは ClassLoader で正しく解決されます。ただし、2 つの問題があります。

  1. 2 つの String インスタンスab. a.getClass().isInstance(b)とが異なる ClassLoader でインスタンス化されている場合、 とはa.getClass() == b.getClass()true ではありません。これは恐ろしい問題を引き起こします。ab
  2. シングルトン: シングルトンではありません。ClassLoader ごとに 1 つ持つことができます。

編集終了

もう 1 つの観察: ClassLoader を特別にクラスをロードするようにセットアップしたように、インタープリターは多くの場合、インタープリター環境とスクリプトをロードする ClassLoader インスタンスを作成します。そうすれば、スクリプトが変更された場合、ClassLoader を削除して (スクリプトと共に)、新しい ClassLoader に再ロードできます。EJB とサーブレットもこのトリックを使用します。

于 2012-11-15T07:31:39.900 に答える
2

もしあなたがそうするなら

    System.out.println( p.getClass().getClassLoader() );

pのクラスローダーがあなたのものではないことがわかりますMyClassLoader bcl。実際にはbcl、 の親であるシステム クラス ローダーによってロードされました。

依存クラスをロードするときPythonInterpreter、あなたの ではなく、実際のクラスローダーであるシステムクラスローダーを使用するbclため、インターセプトには到達しません。

この問題を解決するには、クラスローダーがその親に委任できず、実際にクラスを自分でロードする必要があります。

そのために、サブクラス化できますURLClassLoader(システム クラスローダーから URL を盗みます)。

于 2012-11-10T00:22:02.963 に答える
0

他のloadClass()メソッドをオーバーライドするとどうなりますか?

protected Class<?> loadClass(String name, boolean resolve)
于 2012-11-09T20:30:02.700 に答える
0

PySystemStatePythonInterpreter をインスタンス化する前に、オブジェクトを使用してカスタム クラス ローダーを指定できます。

PySystemState state = new PySystemState();
state.setClassLoader(classLoader);
PythonInterpreter interp = new PythonInterpreter(table, state);

http://wiki.python.org/jython/LearningJython

于 2012-11-09T20:41:16.980 に答える