5

クラスパスの動的変更を検討しています。うまく機能する解決策を 1 つ見つけましたが、それは addURL() への明示的な呼び出しを使用して行われます。(おそらく起動時)

ただし、デフォルトのクラスローダーがクラスを見つけられないように見える場合は、実行時にクラスローディングプロセスをインターセプトしてクラスを見つけたいと思います。ClassLoaderをサブクラス化してデフォルトに委譲findClass()し、これらのメソッドが呼び出されたことを示すデバッグ行を出力しようとしましたが、クラスが暗黙的なクラスローディングloadClass()を介して依存クラスを使用している場合、それらが呼び出されることはありません。

// regular object instantiation with 'new'
BrowserLauncher launcher;
launcher = new BrowserLauncher();

// static methods
Foobar.doSomethingOrOther();

// Class.forName()
Class cl = Class.forName("foo.bar.baz");

// reflection on a Class object obtained statically
Class<Foobar> cl = Foobar.class;
// do something with cl, like call static methods or newInstance()

このような状況でクラスローディングはどのように機能しますか? (対 Classloader.loadClass() が明示的に呼び出される単純なケース)

以下は、カスタムクラスローダーでの私の試みです。の引数リストを指定して DynClassLoader0.main() を使用し、{"some.package.SomeClass", "foo", "bar", "baz"}some.package.SomeClass が外部の .jar ファイルにある他のクラスを参照する場合、上記のメソッドのいずれかを使用すると、なぜ私の DynClassLoader0 の findClass() および loadClass( ) 呼ばれる?loadClass が呼び出されるのは、以下の main() 関数で loadClass を明示的に呼び出したときだけです。

package com.example.test.classloader;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

public class DynClassLoader0 extends ClassLoader {
    public DynClassLoader0()
    {
        super();
    }
    public DynClassLoader0(ClassLoader parent)
    {
        super(parent);
    }
    public void runMain(String classname, String[] args) throws ClassNotFoundException, SecurityException, NoSuchMethodException, IllegalArgumentException, IllegalAccessException, InvocationTargetException
    {
    // [***] here we explicitly use our classloader.
        Class<?> cl = loadClass(classname);
        Method main = cl.getMethod("main", String[].class);
        main.invoke(null, new Object[] {args});
    }

    @Override protected Class<?> findClass(String name) throws ClassNotFoundException
    {
        System.out.println("findClass("+name+")");
        return super.findClass(name);
    }

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

    static public void main(String[] args)
    {
        // classname, then args
        if (args.length >= 1)
        {
            String[] classArgs = new String[args.length-1];
            System.arraycopy(args, 1, classArgs, 0, args.length-1);

            ClassLoader currentThreadClassLoader
             = Thread.currentThread().getContextClassLoader();
            DynClassLoader0 classLoader = new DynClassLoader0(currentThreadClassLoader);
            // Replace the thread classloader - assumes
            // you have permissions to do so
            Thread.currentThread().setContextClassLoader(classLoader);

            try {
                classLoader.runMain(args[0], classArgs);
            }
            catch (Exception e) {
                e.printStackTrace();
            }
        }
        else
        {
            System.out.println("usage: DynClassLoader {classname} [arg0] [arg1] ...");
        }
    }
}

編集:私はすでにこれらの質問を見てきました:

編集: kdgregory が以下で言っていることは正しいと思いました。クラスローダーを明示的に使用すると ([***]コメントとしてコード内の行を参照)、そのクラスから実行されるすべてのコードは、同じクラスローダーから暗黙的なクラスローディングを引き起こします。しかし、私の DynClassLoader0.loadClass() は、最も外側の明示的な呼び出し以外では決して呼び出されません。

4

2 に答える 2

4

ClassLoader JavaDocから引用するには:

クラスローダーによって作成されたオブジェクトのメソッドとコンストラクタは、他のクラスを参照する場合があります。参照されるクラスを判別するために、Java仮想マシンは、クラスを最初に作成したクラスローダーのloadClassメソッドを呼び出します。

つまり、クラスをロードすると、そのクラスは、それをロードしたクラスローダーを介して他のクラスをロードしようとします。通常のJavaアプリケーションでは、これは、JVMに渡されるクラスパスを表すシステムクラスローダー、またはJVMランタイムのロードに使用されるブートクラスローダーです。

必要に応じて、クラスローダーを引数として取るClass.forName()のバリアントがあります。これを使用して特定のクラスをロードする場合、そのクラス内の参照は指定されたクラスローダーを使用する必要があります。


編集:私はあなたの例をたどり始めましたが、私自身のものを与える方が簡単だと判断しました。独自のクラスローダーを作成する場合は、既存のURLClassLoaderから始めることをお勧めします。これは、多くの舞台裏のものを処理するためです。

したがって、MyClassLoader単一のJARfile /ディレクトリを取得し、そのディレクトリのクラスのみをロードします。クラスをロードするために呼び出された3つのメソッドをオーバーライドし、それらの呼び出しをログに記録しました(System.outとは異なり、出力をバッファリングしないため、System.errを使用します)。

私の例では、現在作業中のライブラリを使用しています。便利でしたが、クラスパスにまだ含まれていない限り、任意のライブラリを選択できます。

main()メソッドは、MyLoaderを介してクラスをロードします。次に、ライブラリの一部でもある例外をスローすることがわかっている方法で、そのクラスのメソッドを呼び出します。リフレクションによってメソッドを呼び出すことに注意してください。ライブラリがEclipseクラスパス上にないため、明示的な参照を使用してコンパイルできませんでした。

このプログラムを(Linux用のSun JDK 1.5で)実行すると、ライブラリ内のクラスとクラスパス上のクラスの両方に対して、loadClass()への呼び出しが多数表示されます。これは予想されることです。ParseUtilクラスは他の多くのクラスを参照し、MyLoader(つまりそのクラスローダー)を使用してそれらをロードします。MyLoaderがローカルで見つけることができないクラスについては、ローダーツリーを委任します。

例外がスローされ、そのクラスローダーを印刷すると、作成したMyLoaderインスタンスと同じであることがわかります。Exception.classのローダーも出力しますが、これはnullです。JavaDocforClass.getClassLoader()によると、ブートクラスローダーを示しています。

import java.io.File;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.URL;
import java.net.URLClassLoader;


public class ClassLoaderExample
{
    private static class MyClassLoader
    extends URLClassLoader
    {
        public MyClassLoader(String path)
        throws Exception
        {
            super(new URL[] { new File(path).toURL() });
        }

        @Override
        protected Class<?> findClass(String name) throws ClassNotFoundException
        {
            System.err.println("findClass(" + name + ")");
            return super.findClass(name);
        }

        @Override
        protected synchronized Class<?> loadClass(String name, boolean resolve)
            throws ClassNotFoundException
        {
            System.err.println("loadClass(" + name + "," + resolve + ")");
            return super.loadClass(name, resolve);
        }

        @Override
        public Class<?> loadClass(String name) throws ClassNotFoundException
        {
            System.err.println("loadClass(" + name + ")");
            return super.loadClass(name);
        }
    }


    public static void main(String[] argv)
    throws Exception
    {
        ClassLoader myLoader = new MyClassLoader("/home/kgregory/Workspace/PracticalXml-1.1/target/classes/");
        System.out.println("myLoader = " + myLoader);

        Class<?> parseUtilKlass = myLoader.loadClass("net.sf.practicalxml.ParseUtil");
        Method parseMethod = parseUtilKlass.getDeclaredMethod("parse", String.class);

        try
        {
            parseMethod.invoke(null, "not at all valid XML");
        }
        catch (InvocationTargetException e)
        {
            Throwable ee = e.getCause();
            System.out.println("exception:" + ee);
            System.out.println("exception loader = " + ee.getClass().getClassLoader());

            System.out.println("Exception.class loader = " + Exception.class.getClassLoader());
        }
    }
}

今日のコメントに基づいて、#2を編集します。

クラスローダーは、リクエスト自体を実行しようとするに、リクエストを親に委任することが期待されています(これは、ClassLoader JavaDocにあります)。この方法にはいくつかの利点があります。何よりも、同じクラスの互換性のないインスタンスを意図せずにロードしないことです。

J2EEクラスローダーはこのモデルを修正します。WARのロードに使用されるクラスローダーは、含まれているEARのローダーの前にクラスを解決しようとし、EARはコンテナーのクラスローダーの前にクラスを解決しようとします。ここでの目標は分離です。WARとそのEARの両方に同じライブラリが含まれている場合、それはおそらく2つが異なるバージョンを必要としているためです(または、ビルドプロセスがずさんです)。J2EEの場合でも、コンテナクラスローダーは標準的な方法で委任すると思います。

于 2009-08-19T17:52:13.447 に答える
1

あなたのコードでは、 への呼び出しsuper.loadClass()は、クラスのロードを親クラスローダーに委譲します ( の実装を見てくださいjava.lang.ClassLoader#loadClass)。したがってDynClassLoader0、クラスをロードするのはのインスタンスではなく、コンストラクタ パラメータとして に渡したcurrentThreadClassLoader(から取得した) です。また、ロードされたクラスが他のクラスを参照する場合、クラスローダーではなく、そのクラスローダーによってもロードされます。Thread.currentThread().getContextClassLoader()DynClassLoader0DynClassLoader0

于 2009-08-19T18:04:16.573 に答える