Javaで作るシステムで、プラグインを作れるようなAPIを提供したくなることあるじゃないですか。
そういうときに使える、「jarファイルを指定すると、その中から指定interfaceの実装クラスを探してロードし、Classオブジェクトを返してくれる」メソッドloadClassesInJar
を作りました。
指定interfaceを実装しているだけでなく、引数なしコンストラクタを持っていることもチェックしています。
ここは拡張して、指定シグネチャのコンストラクタを持っていること、というチェックにしても良いでしょう。
実際にソフトウェアに組み込む際は、起動時にプラグインディレクトリ内をなめて.jarファイルを探してloadClassesInJar
でもいいですし、ファイルシステム監視を入れてプラグインディレクトリ内に新規ファイルが登場するたびloadClassesInJar
という使い方もできます。
JDK5以降対応。
/**
* 指定したjarファイルから指定interfaceの実装クラス(引数なしコンストラクタを持つもの)をすべてロードして返します。
* @param jarPath jarファイル
* @param i 実装しているべきinterfaceまたは親クラス
* @param <Interface> 実装しているべきinterfaceまたは親クラス
* @return jarファイル内に含まれる、条件に合うクラスすべてを含むリスト
* @throws IOException jarファイルの読み込みができませんでした。
*/
public static <Interface> List<Class<Interface>> loadClassesInJar(String jarPath, Class<Interface> i) throws IOException
{
final URL jarUrl = new File(jarPath).toURI().toURL();
final URLClassLoader urlClassLoader = URLClassLoader.newInstance(new URL[]{jarUrl});
final List<Class<Interface>> result = new ArrayList<Class<Interface>>();
final JarFile jarFile = new JarFile(jarPath);
for (final Enumeration<JarEntry> entries = jarFile.entries(); entries.hasMoreElements(); )
{
// ファイル要素に限って(=ディレクトリをはじいて)スキャン
final JarEntry jarEntry = entries.nextElement();
if (jarEntry.isDirectory()) continue;
// classファイルに限定
final String fileName = jarEntry.getName();
if (!fileName.endsWith(".class")) continue;
// classファイルをクラスとしてロード。
final Class<?> clazz;
try {
clazz = urlClassLoader.loadClass(fileName.substring(0, fileName.length() - 6).replace('/', '.'));
} catch (ClassNotFoundException e) {
continue;
}
// iの派生型であることを確認
if (!i.isAssignableFrom(clazz)) continue;
// 引数なしコンストラクタを持つことを確認
try {
clazz.getConstructor();
} catch (NoSuchMethodException e) {
continue;
}
result.add((Class<Interface>) clazz);
}
return result;
}