プラグイン機能を作ってみたかったのでClassLoaderを自作してみた。とりあえずJARに含まれるCLASSの列挙ができるようになった。
最初はClassLoaderを継承させてたけど、JARにスーパークラスとサブクラスが含まれている場合に、先にサブクラスを読み込むとスーパークラスが見つからないと怒られて悩んだ。APIリファレンスのURLClassLoaderに検索パスなんて単語を見つけたのでURLClassLoaderに切り替えて、検索パスや二重読み込みしないようにしたらうまくいった。
import java.util.List; import java.util.ArrayList; import java.util.Enumeration; import java.util.jar.JarFile; import java.util.jar.JarEntry; import java.io.File; import java.io.BufferedInputStream; import java.io.IOException; import java.net.URL; import java.net.URLClassLoader; public class PluginLoader extends URLClassLoader{ public PluginLoader(){ super(new URL[0], Thread.currentThread().getContextClassLoader()); } public List<Class<?>> loadClasses(File file) throws IOException{ //引数で渡されたファイルに含まれるクラスを格納するリスト List<Class<?>> classList = new ArrayList<Class<?>>(); //引数で渡されたファイルを検索対象に追加 super.addURL(file.toURI().toURL()); //引数で渡されたファイルをJarFileとして読み込む JarFile jarFile = new JarFile(file); Enumeration<JarEntry> enumeration = jarFile.entries(); while(enumeration.hasMoreElements()){ JarEntry entry = enumeration.nextElement(); //名前の最後が".class"でなければ無視 if(!entry.getName().endsWith(".class")) continue; //ファイル名から".class"を取り除き、'/'を'.'に変換する String className = entry.getName().replace(".class", "").replaceAll("/", "\\."); //すでに読み込まれてるなら無視 if(super.findLoadedClass(className) != null) continue; //JARに含まれているクラスファイルをバイナリとして読み込む BufferedInputStream bis = new BufferedInputStream(jarFile.getInputStream(entry)); byte[] classBinary = new byte[(int)entry.getSize()]; int n = bis.read(classBinary); bis.close(); //読み込んだバイナリをクラスとして定義 Class<?> definedClass = super.defineClass(className, classBinary, 0, n); classList.add(definedClass); } return classList; } public static void main(String[] args) throws Exception{ PluginLoader loader = new PluginLoader(); //plugin.jarに含まれるクラスの名前を列挙する List<Class<?>> classList = loader.loadClasses(new File("plugin.jar")); for(Class loadedClass : classList) System.out.println(loadedClass.getName()); } }