真实的国产乱ⅩXXX66竹夫人,五月香六月婷婷激情综合,亚洲日本VA一区二区三区,亚洲精品一区二区三区麻豆

成都創(chuàng)新互聯(lián)網(wǎng)站制作重慶分公司

[SpringBoot]深入淺出剖析SpringBoot中SpringFactories機(jī)制

微信號(hào):GitShare
微信公眾號(hào):愛折騰的稻草
如有問題或建議,請(qǐng)?jiān)诠娞?hào)留言[1]

讓客戶滿意是我們工作的目標(biāo),不斷超越客戶的期望值來自于我們對(duì)這個(gè)行業(yè)的熱愛。我們立志把好的技術(shù)通過有效、簡(jiǎn)單的方式提供給客戶,將通過不懈努力成為客戶在信息化領(lǐng)域值得信任、有價(jià)值的長(zhǎng)期合作伙伴,公司提供的服務(wù)項(xiàng)目有:域名注冊(cè)、虛擬空間、營(yíng)銷軟件、網(wǎng)站建設(shè)、白云鄂網(wǎng)站維護(hù)、網(wǎng)站推廣。

前續(xù)

為幫助廣大SpringBoot用戶達(dá)到“知其然,更需知其所以然”的境界,作者將通過SpringBoot系列文章全方位對(duì)SpringBoot2.0.0.RELEASE版本深入分解剖析,讓您深刻的理解其內(nèi)部工作原理。 

  • 1、[SpringBoot]利用SpringBoot快速構(gòu)建并啟動(dòng)項(xiàng)目

  • 2、[SpringBoot]詳解SpringBoot應(yīng)用的啟動(dòng)過程

  • 3、[SpringBoot]深入淺出剖析SpringBoot的應(yīng)用類型識(shí)別機(jī)制

Java SPI機(jī)制
  • Java SPI是什么?
    SPI的全名為Service Provider Interface.大多數(shù)開發(fā)人員可能不熟悉,因?yàn)檫@個(gè)是針對(duì)廠商或者插件的。
    我們系統(tǒng)里抽象的各個(gè)模塊,往往有很多不同的實(shí)現(xiàn)方案,比如日志模塊的方案,xml解析模塊、jdbc模塊的方案等。
    面向的對(duì)象的設(shè)計(jì)里,我們一般推薦模塊之間基于接口編程,模塊之間不對(duì)實(shí)現(xiàn)類進(jìn)行硬編碼。一旦代碼里涉及具體的實(shí)現(xiàn)類,就違反了可拔插的原則,
    如果需要替換一種實(shí)現(xiàn),就需要修改代碼。為了實(shí)現(xiàn)在模塊裝配的時(shí)候能不在程序里動(dòng)態(tài)指明,這就需要一種服務(wù)發(fā)現(xiàn)機(jī)制。
    Java SPI就是提供這樣的一個(gè)機(jī)制:為某個(gè)接口尋×××實(shí)現(xiàn)的機(jī)制。

  • Java SPI的約定
    當(dāng)服務(wù)的提供者,提供了服務(wù)接口的一種實(shí)現(xiàn)之后,在jar包的META-INF/services/目錄里同時(shí)創(chuàng)建一個(gè)以服務(wù)接口命名的文件。
    該文件里就是實(shí)現(xiàn)該服務(wù)接口的具體實(shí)現(xiàn)類。而當(dāng)外部程序裝配這個(gè)模塊的時(shí)候,就能通過該jar包META-INF/services/里的配置文件找到具體的實(shí)現(xiàn)類名,并裝載實(shí)例化,完成模塊的注入。

    • 基于這樣一個(gè)約定就能很好的找到服務(wù)接口的實(shí)現(xiàn)類,而不需要再代碼里制定。

    • jdk提供服務(wù)實(shí)現(xiàn)查找的一個(gè)工具類:java.util.ServiceLoader 。

SpringBoot中的SPI機(jī)制

在Spring中也有一種類似與Java SPI的加載機(jī)制。它在META-INF/spring.factories文件中配置接口的實(shí)現(xiàn)類名稱,然后在程序中讀取這些配置文件并實(shí)例化。
這種自定義的SPI機(jī)制是Spring Boot Starter實(shí)現(xiàn)的基礎(chǔ)。

Spring Factories實(shí)現(xiàn)原理

spring-core包里定義了SpringFactoriesLoader類,這個(gè)類實(shí)現(xiàn)了檢索META-INF/spring.factories文件,并獲取指定接口的配置的功能。
在這個(gè)類中定義了兩個(gè)對(duì)外的方法:

1、loadFactories

根據(jù)接口類獲取其實(shí)現(xiàn)類的實(shí)例,這個(gè)方法返回的是對(duì)象列表,其源代碼:

public static  List loadFactories(Class factoryClass, @Nullable ClassLoader classLoader) {
    Assert.notNull(factoryClass, "'factoryClass' must not be null");
    ClassLoader classLoaderToUse = classLoader;
    if (classLoaderToUse == null) {
        classLoaderToUse = SpringFactoriesLoader.class.getClassLoader();
    }
    List factoryNames = loadFactoryNames(factoryClass, classLoaderToUse);
    if (logger.isTraceEnabled()) {
        logger.trace("Loaded [" + factoryClass.getName() + "] names: " + factoryNames);
    }
    List result = new ArrayList<>(factoryNames.size());
    for (String factoryName : factoryNames) {
        result.add(instantiateFactory(factoryName, factoryClass, classLoaderToUse));
    }
    AnnotationAwareOrderComparator.sort(result);
    return result;
}
  • 先獲取ClassLoader

  • 調(diào)用loadFacotoryNames()方法,獲取factoryNames,其源碼如下:

public static List loadFactoryNames(Class factoryClass, @Nullable ClassLoader classLoader) {
    //獲取到類名
    String factoryClassName = factoryClass.getName();
    //在當(dāng)前ClassLoader下的所有META-INF/spring.factories文件的配置信息的Map中獲取指定的factory的值,如果不存在,則返回空列表
    return loadSpringFactories(classLoader).getOrDefault(factoryClassName, Collections.emptyList());
}
  • 調(diào)用loadSpringFactories方法,獲取當(dāng)前ClassLoader下的所有META-INF/spring.factories文件的配置信息

private static Map> loadSpringFactories(@Nullable ClassLoader classLoader) {
    //去緩存中獲取對(duì)應(yīng)的
    MultiValueMap result = cache.get(classLoader);
    if (result != null)
        return result;
    try {
        //獲取當(dāng)前ClassLoader下的所有包含META-INF/spring.factories文件的URL路徑
        Enumeration urls = (classLoader != null ?
                classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
                ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
        //初始返回對(duì)象
        result = new LinkedMultiValueMap<>();
        //遍歷所有的包含META-INF/spring.factories文件URL集合
        while (urls.hasMoreElements()) {
            URL url = urls.nextElement();
            UrlResource resource = new UrlResource(url);
            //轉(zhuǎn)換為Properties對(duì)象
            Properties properties = PropertiesLoaderUtils.loadProperties(resource);
            //遍歷META-INF/spring.factories文件中的所有屬性
            for (Map.Entry entry : properties.entrySet()) {
                //如果一個(gè)接口希望配置多個(gè)實(shí)現(xiàn)類,可以使用','進(jìn)行分割,將當(dāng)前Key對(duì)應(yīng)的值轉(zhuǎn)換為L(zhǎng)ist
                List factoryClassNames = Arrays.asList(StringUtils.commaDelimitedListToStringArray((String) entry.getValue()));
                //添加到返回對(duì)象中
                result.addAll((String) entry.getKey(), factoryClassNames);
            }
        }
        //添加到緩存中
        cache.put(classLoader, result);
        //返回結(jié)果
        return result;
    }
    catch (IOException ex) {
        throw new IllegalArgumentException("Unable to load factories from location [" +
                FACTORIES_RESOURCE_LOCATION + "]", ex);
    }
}

我們可以在自己的jar中配置spring.factories文件,不會(huì)影響到其它地方的配置,也不會(huì)被別人的配置覆蓋。
這一點(diǎn)非常有利于后期我們進(jìn)行自定義擴(kuò)展。

  • 調(diào)用instantiateFactory,實(shí)例化獲取到的對(duì)應(yīng)的Factory對(duì)象,其源碼:

private static  T instantiateFactory(String instanceClassName, Class factoryClass, ClassLoader classLoader) {
    try {
        //根據(jù)類名獲取類的實(shí)例,這個(gè)源碼在上一篇中已經(jīng)剖析過了。
        Class instanceClass = ClassUtils.forName(instanceClassName, classLoader);
        //isAssignableFrom()是判斷instanceClassName的對(duì)象是否是FactoryClass的子類或者相同的類,如果是返回true。
        if (!factoryClass.isAssignableFrom(instanceClass)) {
            //如果實(shí)例化的目標(biāo)類不是Factory類的子類或者不是Factory類,則拋出異常
            throw new IllegalArgumentException("Class [" + instanceClassName + "] is not assignable to [" + factoryClass.getName() + "]");
        }
        //通過java反射機(jī)制,實(shí)例化目標(biāo)類
        return (T) ReflectionUtils.accessibleConstructor(instanceClass).newInstance();
    }
    catch (Throwable ex) {
        throw new IllegalArgumentException("Unable to instantiate factory class: " + factoryClass.getName(), ex);
    }
}

從這段代碼中我們可以知道,它只支持沒有參數(shù)的構(gòu)造方法。

2、loadFactoryNames

根據(jù)接口獲取其接口類的名稱,這個(gè)方法返回的是類名的列表,其源代碼已經(jīng)在上面分析過了。

SpringBoot啟動(dòng)時(shí)的應(yīng)用

在SpringBoot啟動(dòng)過程中,創(chuàng)建SpringApplication對(duì)象是,有如下兩行代碼:

setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
  • 使用SpringFactoriesLoader在應(yīng)用的classpath中查找并加載所有可用的ApplicationContextInitializer

  • 使用SpringFactoriesLoader在應(yīng)用的classpath中查找并加載所有可用的ApplicationListener

Spring Factories機(jī)制的應(yīng)用

在日常工作中,我們可能需要實(shí)現(xiàn)一些SDK或者Spring Boot Starter給被人使用,這個(gè)使用我們就可以使用Factories機(jī)制。
Factories機(jī)制可以讓SDK或者Starter的使用只需要很少或者不需要進(jìn)行配置,只需要在服務(wù)中引入我們的jar包。

知識(shí)點(diǎn)
  • Class的isAssignableFrom()解析

public native boolean isAssignableFrom(Class cls);

由方法簽名可見是一個(gè)本地方法,即C代碼編寫的。  
其作用: 
有兩個(gè)Class類型的類象,一個(gè)是調(diào)用isAssignableFrom方法的類對(duì)象(后稱對(duì)象a),以及方法中作為參數(shù)的這個(gè)類對(duì)象(稱之為對(duì)象b),這兩個(gè)對(duì)象如果滿足以下條件則返回true,否則返回false:

  • a對(duì)象所對(duì)應(yīng)類信息是b對(duì)象所對(duì)應(yīng)的類信息的父類或者是父接口,簡(jiǎn)單理解即a是b的父類或接口

  • a對(duì)象所對(duì)應(yīng)類信息與b對(duì)象所對(duì)應(yīng)的類信息相同,簡(jiǎn)單理解即a和b為同一個(gè)類或同一個(gè)接口

后記

為幫助廣大SpringBoot用戶達(dá)到“知其然,更需知其所以然”的境界,作者將通過SpringBoot系列文章全方位對(duì)SpringBoot2.0.0.RELEASE版本深入分解剖析,讓您深刻的理解其內(nèi)部工作原理。 敬請(qǐng)關(guān)注[愛折騰的稻草(GitShare)]公眾號(hào),為您提供更多更優(yōu)質(zhì)的技術(shù)教程。


[SpringBoot]深入淺出剖析SpringBoot中Spring Factories機(jī)制圖注:愛折騰的稻草

分享標(biāo)題:[SpringBoot]深入淺出剖析SpringBoot中SpringFactories機(jī)制
網(wǎng)址分享:http://weahome.cn/article/jeshjc.html

其他資訊

在線咨詢

微信咨詢

電話咨詢

028-86922220(工作日)

18980820575(7×24)

提交需求

返回頂部