Springboot擴展:SPI機制之spring.factories


1.序言

SPI ,全稱為 Service Provider Interface,是一種服務提供發現機制,為框架提供良好擴展性的機制。

例如:我們系統中抽象模塊,往往有多種實現,在面向對象的編程中,如果需要換另一種實現,就需要修改代碼,為了不修改代碼就需要一種發現機制。

再例如:加載第三方的bean交由Spring容器管理時。

SPI就是提供這樣一種機制,為接口尋找實現。將裝配的控制權移到程序之外。簡單說就是可插拔設計。

2.約定

當服務的提供者,提供了服務接口的一種實現之后,需在項目的classpath下的META-INF/spring.factories文件中配置該接口的實現類名稱,以便程序讀取配置文件來實例化。

org.springframework.context.ApplicationContextInitializer=com.yue.test.CustomApplicationContextInitializer

3.原理

在Spring-Core包的core/io/support下定義了SpringFactoriesLoader類,這個類用來加載META-INF/spring.factories文件,並獲取指定接口的實現類。

loadFactories:使用給定的類加載器從文件中加載並實例化給定類型的工廠實現。(返回的是實例對象列表

loadFactoryNames:使用給定的類加載器從文件中加載給定類型的工廠實現的標准類名。(返回的是類名列表)

//加載並實例化給定類型的工廠實現
public static <T> List<T> loadFactories(Class<T> factoryType, @Nullable ClassLoader classLoader) {
    Assert.notNull(factoryType, "'factoryType' must not be null");
    ClassLoader classLoaderToUse = classLoader;
    if (classLoaderToUse == null) {
        classLoaderToUse = SpringFactoriesLoader.class.getClassLoader();
    }
    // 返回的類名列表
    List<String> factoryImplementationNames = loadFactoryNames(factoryType, classLoaderToUse);
    if (logger.isTraceEnabled()) {
        logger.trace("Loaded [" + factoryType.getName() + "] names: " + factoryImplementationNames);
    }
    List<T> result = new ArrayList<>(factoryImplementationNames.size());
    for (String factoryImplementationName : factoryImplementationNames) {
        // 實例化
        result.add(instantiateFactory(factoryImplementationName, factoryType, classLoaderToUse));
    }
    AnnotationAwareOrderComparator.sort(result);
    return result;
}
//加載指定類型的工廠實現的標准類名
public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {
    String factoryTypeName = factoryType.getName();
    return loadSpringFactories(classLoader).getOrDefault(factoryTypeName, Collections.emptyList());
}
// 加載spring.factoies文件
private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
    MultiValueMap<String, String> result = cache.get(classLoader);
    if (result != null) {
        return result;
    }
    try {
        // 獲取文件的地址,將當前項目內及其引入的jar包下的META-INF/spring.factories文件全部讀取出來
        Enumeration<URL> urls = (classLoader != null ?
                classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
                ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
        result = new LinkedMultiValueMap<>();
        while (urls.hasMoreElements()) {
            URL url = urls.nextElement();
            UrlResource resource = new UrlResource(url);
            // 讀取加載
            Properties properties = PropertiesLoaderUtils.loadProperties(resource);
            for (Map.Entry<?, ?> entry : properties.entrySet()) {
                String factoryTypeName = ((String) entry.getKey()).trim();
                for (String factoryImplementationName : StringUtils.commaDelimitedListToStringArray((String) entry.getValue())) {
                    result.add(factoryTypeName, factoryImplementationName.trim());
                }
            }
        }
        cache.put(classLoader, result);
        return result;
    }
    ...
}

4.說明

加載第三方Bean,方法一:使用@Import注解;方法二:在META-INF/spring.factories文件中配置

META-INF/spring.factories中常用的幾種接口:

#hh
org.springframework.context.ApplicationContextInitializer
#ss
org.springframework.context.ApplicationListener
#ss
org.springframework.boot.autoconfigure.EnableAutoConfiguration
#ss
org.springframework.boot.diagnostics.FailureAnalyzer

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM