springboot中SPI機(jī)制

一雏蛮、從java類加載機(jī)制說起

  • java中的類加載器負(fù)載加載來自文件系統(tǒng)涎嚼、網(wǎng)絡(luò)或者其他來源的類文件。jvm的類加載器默認(rèn)使用的是雙親委派模式挑秉。三種默認(rèn)的類加載器Bootstrap ClassLoader法梯、Extension ClassLoader和System ClassLoader(Application ClassLoader)每一個中類加載器都確定了從哪一些位置加載文件。于此同時我們也可以通過繼承java.lang.classloader實現(xiàn)自己的類加載器犀概。

Bootstrap ClassLoader:負(fù)責(zé)加載JDK自帶的rt.jar包中的類文件立哑,是所有類加載的父類
Extension ClassLoader:負(fù)責(zé)加載java的擴(kuò)展類庫從jre/lib/ect目錄或者java.ext.dirs系統(tǒng)屬性指定的目錄下加載類,是System ClassLoader的父類加載器
System ClassLoader:負(fù)責(zé)從classpath環(huán)境變量中加載類文件

java類加載結(jié)構(gòu)

1阱冶、雙親委派模型

  • 原理:當(dāng)一個類加載器收到類加載任務(wù)時刁憋,會先交給自己的父加載器去完成滥嘴,因此最終加載任務(wù)都會傳遞到最頂層的BootstrapClassLoader木蹬,只有當(dāng)父加載器無法完成加載任務(wù)時,才會嘗試自己來加載若皱。

具體:根據(jù)雙親委派模式镊叁,在加載類文件的時候,子類加載器首先將加載請求委托給它的父加載器走触,父加載器會檢測自己是否已經(jīng)加載過類晦譬,如果已經(jīng)加載則加載過程結(jié)束,如果沒有加載的話則請求繼續(xù)向上傳遞直Bootstrap ClassLoader互广。如果請求向上委托過程中敛腌,如果始終沒有檢測到該類已經(jīng)加載,則Bootstrap ClassLoader開始嘗試從其對應(yīng)路勁中加載該類文件惫皱,如果失敗則由子類加載器繼續(xù)嘗試加載像樊,直至發(fā)起加載請求的子加載器為止。

  • 采用雙親委派模式可以保證類型加載的安全性旅敷,不管是哪個加載器加載這個類生棍,最終都是委托給頂層的BootstrapClassLoader來加載的,只有父類無法加載自己猜嘗試加載媳谁,這樣就可以保證任何的類加載器最終得到的都是同樣一個Object對象涂滴。
protected Class<?> loadClass(String name, boolean resolve) {
    synchronized (getClassLoadingLock(name)) {
    // 首先,檢查該類是否已經(jīng)被加載晴音,如果從JVM緩存中找到該類柔纵,則直接返回
    Class<?> c = findLoadedClass(name);
    if (c == null) {
        try {
            // 遵循雙親委派的模型,首先會通過遞歸從父加載器開始找锤躁,
            // 直到父類加載器是BootstrapClassLoader為止
            if (parent != null) {
                c = parent.loadClass(name, false);
            } else {
                c = findBootstrapClassOrNull(name);
            }
        } catch (ClassNotFoundException e) {}
        if (c == null) {
            // 如果還找不到搁料,嘗試通過findClass方法去尋找
            // findClass是留給開發(fā)者自己實現(xiàn)的,也就是說
            // 自定義類加載器時,重寫此方法即可
           c = findClass(name);
        }
    }
    if (resolve) {
        resolveClass(c);
    }
    return c;
    }
}

2.雙親委派模型缺陷

  • 在雙親委派模型中加缘,子類加載器可以使用父類加載器已經(jīng)加載的類鸭叙,而父類加載器無法使用子類加載器已經(jīng)加載的。這就導(dǎo)致了雙親委派模型并不能解決所有的類加載器問題拣宏。
  • 案例:Java 提供了很多服務(wù)提供者接口(Service Provider Interface沈贝,SPI),允許第三方為這些接口提供實現(xiàn)勋乾。常見的 SPI 有 JDBC宋下、JNDI、JAXP 等辑莫,這些SPI的接口由核心類庫提供学歧,卻由第三方實現(xiàn),這樣就存在一個問題:SPI 的接口是 Java 核心庫的一部分各吨,是由BootstrapClassLoader加載的枝笨;SPI實現(xiàn)的Java類一般是由AppClassLoader來加載的。BootstrapClassLoader是無法找到 SPI 的實現(xiàn)類的揭蜒,因為它只加載Java的核心庫横浑。它也不能代理給AppClassLoader,因為它是最頂層的類加載器屉更。也就是說徙融,雙親委派模型并不能解決這個問題

3.使用線程上下文類加載器(ContextClassLoader)加載

  • 如果不做任何的設(shè)置,Java應(yīng)用的線程的上下文類加載器默認(rèn)就是AppClassLoader瑰谜。在核心類庫使用SPI接口時欺冀,傳遞的類加載器使用線程上下文類加載器,就可以成功的加載到SPI實現(xiàn)的類萨脑。線程上下文類加載器在很多SPI的實現(xiàn)中都會用到隐轩。
  • 通常我們可以通過Thread.currentThread().getClassLoader()和Thread.currentThread().getContextClassLoader()獲取線程上下文類加載器。

4砚哗、使用類加載器加載資源文件龙助,比如jar包

類加載器除了加載class外,還有一個非常重要功能蛛芥,就是加載資源提鸟,它可以從jar包中讀取任何資源文件,比如仅淑,ClassLoader.getResources(String name)方法就是用于讀取jar包中的資源文件

//獲取資源的方法
public Enumeration<URL> getResources(String name) throws IOException {
    Enumeration<URL>[] tmp = (Enumeration<URL>[]) new Enumeration<?>[2];
    if (parent != null) {
        tmp[0] = parent.getResources(name);
    } else {
        tmp[0] = getBootstrapResources(name);
    }
    tmp[1] = findResources(name);
    return new CompoundEnumeration<>(tmp);
}

它的邏輯其實跟類加載的邏輯是一樣的称勋,首先判斷父類加載器是否為空,不為空則委托父類加載器執(zhí)行資源查找任務(wù)涯竟,直到BootstrapClassLoader赡鲜,最后才輪到自己查找空厌。而不同的類加載器負(fù)責(zé)掃描不同路徑下的jar包,就如同加載class一樣银酬,最后會掃描所有的jar包嘲更,找到符合條件的資源文件。

// 使用線程上下文類加載器加載資源
public static void main(String[] args) throws Exception{
    // Array.class的完整路徑
    String name = "java/sql/Array.class";
    Enumeration<URL> urls = Thread.currentThread().getContextClassLoader().getResources(name);
    while (urls.hasMoreElements()) {
        URL url = urls.nextElement();
        System.out.println(url.toString());
    }
}

二揩瞪、spring中SPI機(jī)制實現(xiàn)

1.SPI機(jī)制

(1)SPI思想

  • SPI的全名為Service Provider Interface.這個是針對廠商或者插件的赋朦。
  • SPI的思想:系統(tǒng)里抽象的各個模塊,往往有很多不同的實現(xiàn)方案李破,比如日志模塊的方案宠哄,xml解析模塊、jdbc模塊的方案等嗤攻。面向的對象的設(shè)計里毛嫉,我們一般推薦模塊之間基于接口編程,模塊之間不對實現(xiàn)類進(jìn)行硬編碼妇菱。一旦代碼里涉及具體的實現(xiàn)類承粤,就違反了可拔插的原則,如果需要替換一種實現(xiàn)恶耽,就需要修改代碼密任。為了實現(xiàn)在模塊裝配的時候能不在程序里動態(tài)指明,這就需要一種服務(wù)發(fā)現(xiàn)機(jī)制偷俭。java spi就是提供這樣的一個機(jī)制:為某個接口尋找服務(wù)實現(xiàn)的機(jī)制

(2)SPI約定

  • 當(dāng)服務(wù)的提供者,提供了服務(wù)接口的一種實現(xiàn)之后缰盏,在jar包的META-INF/services/目錄里同時創(chuàng)建一個以服務(wù)接口命名的文件涌萤。該文件里就是實現(xiàn)該服務(wù)接口的具體實現(xiàn)類。而當(dāng)外部程序裝配這個模塊的時候口猜,就能通過該jar包META-INF/services/里的配置文件找到具體的實現(xiàn)類名负溪,并裝載實例化,完成模塊的注入济炎。通過這個約定川抡,就不需要把服務(wù)放在代碼中了,通過模塊被裝配的時候就可以發(fā)現(xiàn)服務(wù)類了须尚。

2崖堤、SPI使用案例

  • common-logging apache最早提供的日志的門面接口。只有接口耐床,沒有實現(xiàn)密幔。具體方案由各提供商實現(xiàn), 發(fā)現(xiàn)日志提供商是通過掃描 META-INF/services/org.apache.commons.logging.LogFactory配置文件撩轰,通過讀取該文件的內(nèi)容找到日志提工商實現(xiàn)類胯甩。只要我們的日志實現(xiàn)里包含了這個文件昧廷,并在文件里制定 LogFactory工廠接口的實現(xiàn)類即可。

3偎箫、springboot中的類SPI擴(kuò)展機(jī)制

  • 在springboot的自動裝配過程中木柬,最終會加載META-INF/spring.factories文件,而加載的過程是由SpringFactoriesLoader加載的淹办。從CLASSPATH下的每個Jar包中搜尋所有META-INF/spring.factories配置文件弄诲,然后將解析properties文件,找到指定名稱的配置后返回娇唯。需要注意的是齐遵,其實這里不僅僅是會去ClassPath路徑下查找,會掃描所有路徑下的Jar包塔插,只不過這個文件只會在Classpath下的jar包中梗摇。
public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";
// spring.factories文件的格式為:key=value1,value2,value3
// 從所有的jar包中找到META-INF/spring.factories文件
// 然后從文件中解析出key=factoryClass類名稱的所有value值
public static List<String> loadFactoryNames(Class<?> factoryClass, ClassLoader classLoader) {
    String factoryClassName = factoryClass.getName();
    // 取得資源文件的URL
    Enumeration<URL> urls = (classLoader != null ? classLoader.getResources(FACTORIES_RESOURCE_LOCATION) : ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
    List<String> result = new ArrayList<String>();
    // 遍歷所有的URL
    while (urls.hasMoreElements()) {
        URL url = urls.nextElement();
        // 根據(jù)資源文件URL解析properties文件,得到對應(yīng)的一組@Configuration類
        Properties properties = PropertiesLoaderUtils.loadProperties(new UrlResource(url));
        String factoryClassNames = properties.getProperty(factoryClassName);
        // 組裝數(shù)據(jù)想许,并返回
        result.addAll(Arrays.asList(StringUtils.commaDelimitedListToStringArray(factoryClassNames)));
    }
    return result;
}

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末伶授,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子流纹,更是在濱河造成了極大的恐慌糜烹,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,482評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件漱凝,死亡現(xiàn)場離奇詭異疮蹦,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)茸炒,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,377評論 2 382
  • 文/潘曉璐 我一進(jìn)店門愕乎,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人壁公,你說我怎么就攤上這事感论。” “怎么了紊册?”我有些...
    開封第一講書人閱讀 152,762評論 0 342
  • 文/不壞的土叔 我叫張陵比肄,是天一觀的道長。 經(jīng)常有香客問我囊陡,道長芳绩,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,273評論 1 279
  • 正文 為了忘掉前任关斜,我火速辦了婚禮示括,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘痢畜。我一直安慰自己垛膝,他們只是感情好鳍侣,可當(dāng)我...
    茶點故事閱讀 64,289評論 5 373
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著吼拥,像睡著了一般倚聚。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上凿可,一...
    開封第一講書人閱讀 49,046評論 1 285
  • 那天惑折,我揣著相機(jī)與錄音,去河邊找鬼枯跑。 笑死惨驶,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的敛助。 我是一名探鬼主播粗卜,決...
    沈念sama閱讀 38,351評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼纳击!你這毒婦竟也來了续扔?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 36,988評論 0 259
  • 序言:老撾萬榮一對情侶失蹤焕数,失蹤者是張志新(化名)和其女友劉穎纱昧,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體堡赔,經(jīng)...
    沈念sama閱讀 43,476評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡识脆,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,948評論 2 324
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了加匈。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片存璃。...
    茶點故事閱讀 38,064評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖雕拼,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情粘招,我是刑警寧澤啥寇,帶...
    沈念sama閱讀 33,712評論 4 323
  • 正文 年R本政府宣布,位于F島的核電站洒扎,受9級特大地震影響辑甜,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜袍冷,卻給世界環(huán)境...
    茶點故事閱讀 39,261評論 3 307
  • 文/蒙蒙 一磷醋、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧胡诗,春花似錦邓线、人聲如沸淌友。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,264評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽震庭。三九已至,卻和暖如春你雌,著一層夾襖步出監(jiān)牢的瞬間器联,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,486評論 1 262
  • 我被黑心中介騙來泰國打工婿崭, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留拨拓,地道東北人。 一個月前我還...
    沈念sama閱讀 45,511評論 2 354
  • 正文 我出身青樓氓栈,卻偏偏與公主長得像渣磷,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子颤绕,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,802評論 2 345

推薦閱讀更多精彩內(nèi)容