大規模なデスクトップ Java アプリケーションがあり、他の開発者がプラグインを開発できるようにしたいと考えています。プラグインは、指定されたディレクトリに配置される jar になります。これらは、起動時にクラスパスにはありません。実行時にロードしてデプロイします。
複雑なのは、一部のプラグインが相互に依存するだけでなく、コア アプリケーションにも依存することです。したがって、各プラグイン/jar を独自の URLClassLoader にロードすることはできません。したがって、すべてのプラグインを 1 つの URLClassLoader にロードしたいと考えています。さらに、一部のプラグインはさまざまな理由で初期化に失敗する場合があります。そして、一日の終わりに、正常にロードされたプラグインについて知っている ClassLoader だけが必要です。その理由は非常に奇妙で、リフレクションを使用してクラスをインスタンス化するいくつかのレガシーに関連しています。失敗したプラグイン jar 内で定義されたクラスに対してプラグインが初期化されない場合、これは失敗する必要があります。
この要件がなければ、解決策は次のようになります。
- jar URL を収集し、それらに基づいて ClassLoader を構築します。
- 各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 でこれほど遊んだことはありませんでした。
ありがとう!