マイペースなプログラミング日記

DTMやプログラミングにお熱なd-kamiがマイペースに書くブログ

JARに含まれるクラスを実行時に読み込む

プラグイン機能を作ってみたかったので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());
    }
}