S2JDBCでClassPath外のJDBCドライバを使う2
前のブログの続き。
後輩とちらっと前の記事の話をしてたら
「システムクラスローダー無理やり拡張できるらしいですよ」
と情報をもらったのでぐぐってみたら速攻でてきた。
なんでググらなかったんだろ。一番シンプルな方法なのに。
あたしって、ほんとバカ (元ネタは知らない)
ということで記事を参考に書いてみる。
システムクラスローダーにクラスパスを追加する
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だろうと呼べちゃうので安易に使うのはためらわれる…なんのためのアクセス修飾子なのかと…
うーん
なんというかオブジェクト指向への冒涜みたいなお行儀の悪いコードなのでこの方法もイマイチかなぁ><
まだ前回の方法のほうがそこそこ安全なぶんマシかも…?