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