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