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