3

大規模なデスクトップ Java アプリケーションがあり、他の開発者がプラグインを開発できるようにしたいと考えています。プラグインは、指定されたディレクトリに配置される jar になります。これらは、起動時にクラスパスにはありません。実行時にロードしてデプロイします。

複雑なのは、一部のプラグインが相互に依存するだけでなく、コア アプリケーションにも依存することです。したがって、各プラグイン/jar を独自の URLClassLoader にロードすることはできません。したがって、すべてのプラグインを 1 つの URLClassLoader にロードしたいと考えています。さらに、一部のプラグインはさまざまな理由で初期化に失敗する場合があります。そして、一日の終わりに、正常にロードされたプラグインについて知っている ClassLoader だけが必要です。その理由は非常に奇妙で、リフレクションを使用してクラスをインスタンス化するいくつかのレガシーに関連しています。失敗したプラグイン jar 内で定義されたクラスに対してプラグインが初期化されない場合、これは失敗する必要があります。

この要件がなければ、解決策は次のようになります。

  1. jar URL を収集し、それらに基づいて ClassLoader を構築します。
  2. 各jarからプラグインクラスを初期化してみてください(マニフェストの構成で定義されています)

ここで、ClassLoader はレガシ システムに渡され、そのリフレクションに使用されます。ただし、プラグインの初期化に失敗したプラグイン jar からクラスをインスタンス化できることは理解しています (jar は引き続き ClassLoader の URL[] にあるため)。したがって、これは上記の私の要件を破ります。

これまでに思いついた唯一の解決策は、次のようにカスタム URLClassLoader を作成することです (単に findClass() へのアクセスを許可するため)。

public class CustomURLClassLoader extends URLClassLoader {

    public CustomURLClassLoader(final URL[] urls, final ClassLoader parent) {
        super(urls, parent);
    }

    @Override
    protected Class<?> findClass(final String name) throws ClassNotFoundException {
        return super.findClass(name);
    }
}

そして、基本的に複数の子 ClassLoader を認識する別のカスタム ClassLoader を作成しました。

public class MultiURLClassLoader extends ClassLoader {

    private Set<CustomURLClassLoader> loaders = new HashSet<CustomURLClassLoader>();

    public MultiURLClassLoader(final ClassLoader parent) {
        super(parent);
    }

    @Override
    protected Class<?> findClass(final String name) throws ClassNotFoundException {
        Iterator<CustomURLClassLoader> loadersIter = loaders.iterator();
        boolean first = true;
        while (first || loadersIter.hasNext()) {
            try {
                if (first) {
                    return super.findClass(name);
                } else {
                    return loadersIter.next().findClass(name);
                }
            } catch (ClassNotFoundException e) {
                first = false;
            }
        }
        throw new ClassNotFoundException(name);
    }

    public void addClassLoader(final CustomURLClassLoader classLoader) {
        loaders.add(classLoader);
    }

    public void removeClassLoader(final CustomURLClassLoader classLoader) {
        loaders.remove(classLoader);
    }
}

次に、ロードプラグインのアルゴリズムは次のようになります

MultiURLClassLoader multiURLClassLoader = new MultiURLClassLoader(ClassLoader.getSystemClassLoader());
for (File pluginJar : new File("plugindir").listFiles()) {
    CustomURLClassLoader classLoader = null;
    try {
        URL pluginURL = pluginJar.toURI().toURL();
        final URL[] pluginJarUrl = new URL[] { pluginURL };
        classLoader = new CustomURLClassLoader(pluginJarUrl, multiURLClassLoader);
        multiURLClassLoader.addClassLoader(classLoader);
        Class<?> clazz = Class.forName("some.PluginClass", false, multiURLClassLoader);
        Constructor<?> ctor = clazz.getConstructor();
        SomePluginInterface plugin = (SomePluginInterface)ctor1.newInstance();
        plugin.initialise();            
    } catch (SomePluginInitialiseException e) {
        multiURLClassLoader.removeClassLoader(classLoader);
    }
}

次に、multiURLClassLoader インスタンスをレガシー システムに渡すと、プラグインが正常にロードされたクラスのみを (リフレクション経由で) 見つけることができます。

私はいくつかの基本的なテストを行いましたが、これまでのところ希望どおりに動作しているようです。しかし、これが良い考えのように見えるかどうかについて、誰かの意見を大いに求めていますか? これまで ClassLoaders でこれほど遊んだことはありませんでした。

ありがとう!

4

2 に答える 2

4

私が見ている問題は、どのプラグインがどのプラグインに依存しているかが事前にわからない場合、合理的なことをしたり、問題をデバッグしたり、機能していないプラグインや動作の悪いプラグインを特定したりするのが非常に難しいことです。

したがって、別のオプションをお勧めします。各プラグインのマニフェストに別のフィールドを追加します。これにより、他のプラグインがどのプラグインに依存しているかが示されます。おそらく、機能するために必要な他のプラグインJARのリストにすぎません。(コアアプリケーションクラスはいつでも利用できます。)これにより、設計がはるかに堅牢になり、多くのことが簡素化されると思います。

次に、たとえば次のように、さまざまなデザインから選択できます。

  • プラグインごとに、必要なJARだけをロードする個別のClassLoaderを作成できます。おそらく最も堅牢なソリューションです。しかし、欠点があります。他の多くのプラグインの依存関係として機能するプラグインは、異なるクラスローダーに繰り返しロードされます。状況(プラグイン数、JARサイズなど)によって異なりますが、これが問題になるかどうかは、利点になることもあります。
  • あなたが提案するように、すべてのプラグインに対して1つの大きなClassLoaderを持つことができますが、依存関係の順にプラグインクラスを要求することができます。最初に何にも依存しないもの、次にそれらの最初のものに依存するものなど。一部のプラグインクラスがロード/初期化に失敗した場合、それに依存するすべてのプラグインをすぐに破棄できます。
于 2012-08-10T19:42:46.547 に答える
1

OSGiアプローチのようなものをお探しですか?

Petr Pudlák が言ったようなことを行うこともできますが、使用しているソリューションの 1 つが循環的な依存関係を作成できるという事実を考慮する必要があります...

于 2013-07-08T13:07:24.247 に答える