S2JDBCでClassPath外のJDBCドライバを使う2

前のブログの続き。


後輩とちらっと前の記事の話をしてたら
「システムクラスローダー無理やり拡張できるらしいですよ」
と情報をもらったのでぐぐってみたら速攻でてきた。


d:id:torutk:20110504:p1


なんでググらなかったんだろ。一番シンプルな方法なのに。
あたしって、ほんとバカ (元ネタは知らない)


ということで記事を参考に書いてみる。


システムクラスローダーにクラスパスを追加する

package examples;

import static java.lang.String.format;

import java.io.File;
import java.lang.reflect.Method;
import java.net.*;
import java.util.List;
import org.seasar.extension.jdbc.JdbcManager;
import org.seasar.framework.container.S2Container;
import org.seasar.framework.container.factory.S2ContainerFactory;
import examples.entity.Employee;

public class SystemClassLoaderAdditionMain {

    static final File jarFile = new File("c:\\test\\hsqldb-1.8.0.1.jar");

    public static void main(String[] args) throws Exception {

        URL jarURL =
            new URL(format("jar:%s!/", jarFile
                .getCanonicalFile()
                .toURI()
                .toURL()
                .toString()));

        addClassPath(jarURL); // クラスパス追加

        S2Container container = S2ContainerFactory.create("app.dicon");
        container.init();

        JdbcManager jdbcManager =
            (JdbcManager) container.getComponent("jdbcManager");

        List<Employee> result =
            jdbcManager.from(Employee.class).getResultList();

        for (Employee each : result) {
            System.out.println(each.name);
        }
    }

    public static void addClassPath(URL url) {
        try {
            URLClassLoader systemClassLoader =
                (URLClassLoader) ClassLoader.getSystemClassLoader(); // ※1 ダウンキャストが危険!

            // ※2 リフレクションによるカプセル化のブチ壊し!
            Method addURLMethod =
                URLClassLoader.class.getDeclaredMethod("addURL", URL.class);
            addURLMethod.setAccessible(true);
            addURLMethod.invoke(systemClassLoader, url);

        } catch (Exception e) {
            throw new RuntimeException("システムクラスローダーへのクラスパスの追加に失敗しました。", e);
        }
    }
}


うん…動くね…

感想


「ところでコードを見てくれ。こいつをどう思う?」
「すごく…行儀悪いです…」

【※1】 URLClassLoaderへのダウンキャスト


参照記事の注釈にも書いてあるけど、このダウンキャストは安全じゃない。
自分で書いたコードではない(ランタイムが決める?)ので、実際に渡ってくるインスタンスがURLClassLoader(のサブクラス)とは保証されない。
動かしたJavaランタイムがたまたまURLClassLoaderインスタンスを返しただけで、Javaランタイムが違かったり、バージョンがが違かったりすればURLClassLoaderのインスタンスではなくなる可能性がある。
その場合ClassCastExceptionが発生してしまい動かなくなってしまう…


事前に'instanceof'でURLClassLoaderかどうかチェックすることは可能だが、知らないクラスが来た場合は結局どうしようもない。

【※2】URLClassLoader#addURL(URL)のカプセル化のぶち壊し


クラスパスを追加するにはURLClassLoader#addURL(URL)を呼び出さなくてはいけないのだけども、このメソッドは'protected'アクセス修飾子で宣言されてるからURLClassLoaderのサブクラスではないクラスから呼び出すことはできない。
'protected'だから継承したサブクラス作ればいいんだけど、作ったところでこの問題のClassLoaderのインスタンスがnewされる場所を書き換えられないのでダメ。


なのでリフレクションの反則技"setAccessible(true)"を使って無理やり呼び出しているみたい。
これ使うとprivateだろうと呼べちゃうので安易に使うのはためらわれる…なんのためのアクセス修飾子なのかと…

うーん

なんというかオブジェクト指向への冒涜みたいなお行儀の悪いコードなのでこの方法もイマイチかなぁ><
まだ前回の方法のほうがそこそこ安全なぶんマシかも…?