ClassLoader的一次小了解
-
-
- 背景
- 探索解決
-
背景
今天群裡一哥們說了一個問題,試了好幾下才解決。有點慚愧,記錄之。
需求:
業務方要求,需要通路sqlite資料庫,但是不能提前把sqlite的jar包放到工程裡。( 可能是為了實作技術上的所謂的熱拔插吧)
探索解決
需求是否合理,到了開發解決,隻有使命必達。
當然,前提是有可行性,否則你可以拿着40米的大刀,讓需求側同學先跑39米 :))
熱拔插,那就拿出反射來搞一把呗。
于是乎,有了一下過程
- 第一版代碼
String path ="file:///User/xx/tmp/sqlite-jdbc-3.8.7.jar";
URL url = new URL(path);
URLClassLoader myClassLoader = new URLClassLoader(new URL[] {url},Thread.currentThread()
.getContextClassLoader());
Class<?> driverClass = myClassLoader.loadClass("org.sqlite.JDBC");
Class.forName("org.sqlite.JDBC",true, myClassLoader);
String dbURL="jdbc:sqlite:C:/Users/Administrator/Downloads/test.db";
Class<?> dmClazz = myClassLoader.loadClass("java.sql.DriverManager");
Method method = dmClazz.getMethod("getConnection", Connection.class);
Connection conn = (Connection)method.invoke(null,dbURL);
以上代碼用了反射,類加載器,指導思想是,既然是動态加載,那就全部用新的classload 搞進來,不就可以了嗎?
結果,不行。 因為DriverManager getConnection 是個靜态方法,而且沒有給你指定loader的地方。 即使自己load近來,依然找不到驅動。
- 思考總結
上述代碼,把已經想到的構造一個classloader 反射調用方法,擷取Connection,都用了,發現還是不行。
此時,喝杯茶,靜一下。
翻看一下代碼
Class.forName();
DriverManager.getConnection
看了一下相關注釋,JDBC本身就是用反射擷取驅動類,進行管理。
是以這時,思考了一下,是否換個思路,把驅動jar包放到目前loader的classpath即可。
- 解決方案
指導思想:
1.擷取目前loader
2.擷取相關jar包
3.放進目前loader對應的classpath
代碼如下:
//1.擷取jar
String path ="file:///User/xx/tmp/sqlite-jdbc-3.8.7.jar";
URL url = new URL(path);
//2擷取目前loader
URLClassLoader classLoader = (URLClassLoader) ClassLoader.getSystemClassLoader();
//3. 把外部臨時加載的jar包放進classpath
Method method = URLClassLoader.class.getDeclaredMethod("addURL", URL.class);
boolean accessible=method.isAccessible();
method.setAccessible(true);
method.invoke(classLoader, url);
method.setAccessible(accessible);
- 小結
回顧解決問題的過程,其實剛開始受問題發起者的影響較大,繞進了技術範疇。
遇阻後,倒過來根據經驗解決問題,反倒立馬解決
很多時間,不是技術問題,是經驗問題