java實(shí)現(xiàn)插拔式加載用戶自定義插件
前言
從23年底開(kāi)始維護(hù)自己的開(kāi)源項(xiàng)目,異構(gòu)數(shù)據(jù)源流轉(zhuǎn)系統(tǒng)datalinkx,系統(tǒng)支持不同數(shù)據(jù)源之間的數(shù)據(jù)同步,包括mysql、oracle、es、redis等等。
有一天一個(gè)同學(xué)問(wèn)我說(shuō)datalinkx系統(tǒng)支不支持用戶上傳自己的自定義數(shù)據(jù)源插件,不用動(dòng)datalinkx的源碼即可讓系統(tǒng)支持用戶自定義數(shù)據(jù)源的讀寫(xiě)同步呢。
我一聽(tīng)有點(diǎn)意思,一般我都是版本迭代后支持某個(gè)數(shù)據(jù)源的讀寫(xiě),能不能把這個(gè)能力開(kāi)放出去呢,這樣大家使用的時(shí)候就不需要等系統(tǒng)更新,上傳符合規(guī)范的插件包就可以。
也就是說(shuō)我們需要實(shí)現(xiàn),用戶只需要將其他數(shù)據(jù)源的同步j(luò)ar包,放到某個(gè)文件夾下,系統(tǒng)就會(huì)自動(dòng)掃描到這個(gè)jar包從而獲得對(duì)應(yīng)數(shù)據(jù)源的讀寫(xiě)能力。

SPI
通常聽(tīng)到插件化就會(huì)下意識(shí)的想起SPI,SPI是JDK內(nèi)置的一種類似服務(wù)提供發(fā)現(xiàn)的機(jī)制,比如java.sql.Driver接口,其他不同的數(shù)據(jù)源廠商可以針對(duì)這一接口做出不同的實(shí)現(xiàn), MySQL和PostgreSQL分別有不同的實(shí)現(xiàn)提供給用戶,利用SPI機(jī)制可以為某個(gè)接口尋找服務(wù)實(shí)現(xiàn)。
主要思想是將裝配的控制權(quán)移到程序之外,在模塊化設(shè)計(jì)中這個(gè)機(jī)制尤其重要,核心思想就解耦,具體實(shí)現(xiàn)可以參考jdbc的實(shí)現(xiàn)。

思考下利用SPI可以實(shí)現(xiàn)插拔式插件開(kāi)發(fā)嗎,似乎不可以,使用SPI必須在當(dāng)前classpath中,也就是說(shuō)雖然解耦了,但必須存在依賴,比如你想要讓系統(tǒng)支持PG,必須要在項(xiàng)目中引入PG驅(qū)動(dòng)依賴才可以加載到PG驅(qū)動(dòng)內(nèi)的相關(guān)類。
這樣還是要修改系統(tǒng)源代碼才能得到支持,這明顯是不符合我們的預(yù)期的。
自定義加載器
我們想要的效果是用戶可以開(kāi)發(fā)一個(gè)獨(dú)立的插件jar包,放到datalinkx的driver-dist目錄下,datalinkx即可獲得獨(dú)立插件jar包中的能力。
看到這里有準(zhǔn)備面試的jym心里默背一遍java類加載過(guò)程和雙親委派機(jī)制,背不下來(lái)的看完趕緊補(bǔ)上。我們知道一個(gè)JVM進(jìn)程正常情況下只能加載當(dāng)前classpath下的所有java類。
有沒(méi)有通過(guò)掃描固定路徑下的jar包,將外部java類加載當(dāng)當(dāng)前classpath的方式呢。有的兄弟,有的。
URLClassLoader
// 從driver-dist文件夾下讀取各個(gè)數(shù)據(jù)源插件
List<URL> dataSourcePlugins = JarLoaderUtil.loadJarsFromDirectory(DRIVER_DIST);
String targetJarName = String.format("datalinkx-driver-%s-1.0.0.jar", dsType.toLowerCase());
String targetClassName = getDriverClass(dsType);
URL targetJarUrl = dataSourcePlugins.stream()
.filter(plugin -> plugin.getPath().endsWith(targetJarName))
.findFirst()
.orElseThrow(() -> new DatalinkXServerException(StatusCode.DRIVER_LOAD_FAIL, "系統(tǒng)未支持該數(shù)據(jù)源類型"));
// 使用自定義的 URLClassLoader 加載目標(biāo) JAR 包中的類
URLClassLoader urlClassLoader = new URLClassLoader(new URL[]{targetJarUrl}, Thread.currentThread().getContextClassLoader());
Class<?> clazz = Class.forName(targetClassName, true, urlClassLoader);
我們總結(jié)一下具體做了啥:
- 各個(gè)數(shù)據(jù)源模塊打包后都會(huì)自動(dòng)復(fù)制到datalinkx/driver-dist中
- 通過(guò)使用自定義 URLClassLoader加載器加載driver-dist文件夾內(nèi)的jar包
- 通過(guò)class.forName反射的方式生成對(duì)用對(duì)象
這樣我們就可以在當(dāng)前進(jìn)程中加載到外部jar包中的類,也就實(shí)現(xiàn)了Java插拔式加載用戶自定義插件功能。
到此這篇關(guān)于java實(shí)現(xiàn)插拔式加載用戶自定義插件的文章就介紹到這了,更多相關(guān)java 插拔式加載自定義插件內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Java lambda list轉(zhuǎn)換map時(shí),把多個(gè)參數(shù)拼接作為key操作
這篇文章主要介紹了Java lambda list轉(zhuǎn)換map時(shí),把多個(gè)參數(shù)拼接作為key操作,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2020-08-08
java實(shí)現(xiàn)Redisson的基本使用
Redisson是一個(gè)在Redis的基礎(chǔ)上實(shí)現(xiàn)的Java駐內(nèi)存數(shù)據(jù)網(wǎng)格客戶端,本文主要介紹了java實(shí)現(xiàn)Redisson的基本使用,具有一定的參考價(jià)值,感興趣的可以了解一下2023-12-12
java實(shí)現(xiàn)Excel的導(dǎo)入導(dǎo)出
這篇文章主要為大家詳細(xì)介紹了java實(shí)現(xiàn)Excel的導(dǎo)入導(dǎo)出,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2020-06-06
springboot+springmvc+mybatis項(xiàng)目整合
這篇文章主要為大家詳細(xì)介紹了springboot+springmvc+mybatis項(xiàng)目的整合,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2018-04-04
Java聊天室之使用Socket實(shí)現(xiàn)傳遞對(duì)象
這篇文章主要為大家詳細(xì)介紹了Java簡(jiǎn)易聊天室之使用Socket實(shí)現(xiàn)傳遞對(duì)象功能,文中的示例代碼講解詳細(xì),具有一定的借鑒價(jià)值,需要的可以了解一下2022-10-10
SpringBoot啟動(dòng)失敗Application?run?failed的問(wèn)題分析及解決辦法
文章主要介紹了SpringBoot啟動(dòng)失敗的問(wèn)題分析和解決辦法,包括XML文件格式錯(cuò)誤、數(shù)據(jù)庫(kù)名稱錯(cuò)誤以及MyBatis掃描路徑問(wèn)題的排查和修復(fù)步驟,感興趣的朋友跟隨小編一起看看吧2026-01-01

