相關歷史文章(閱讀本文前,您可能需要先看下之前的系列👇)
國內最全的Spring Boot系列之四
DriverManager SPI分析和Java SPI原理 - 第344篇
Java SPI一探究竟 - 第343篇
前言
在前面的章節《DriverManager SPI分析和Java SPI原理》中,我們通過分析DriverManager的SPI和Java SPI的原理,對於SPI有了一個比較深的了解。現在我們大部分的項目都是基於Spring或者Spring Boot開發的,所以這一節我們談談SPI在Spring | Spring Boot中的應用。
一、Spring SPI
1.1執行流程
Spring中使用的類是SpringFactoriesLoader,在org.springframework.core.io.support包中,大體的執行流程:
(1)SpringFactoriesLoader 會掃描 classpath 中的 META-INF/spring.factories文件。
(2)SpringFactoriesLoader 會加載並實例化META-INF/spring.factories 中的指定類型。
(3)META-INF/spring.factories 內容必須是 properties 的Key-Value形式,多值以逗號隔開。
1.2使用
我們來看一個小小的例子來理解下上面的流程:
(1)創建META-INF/spring.factories文件
在META-INF下創建spring.factories文件,在文件中配置如下信息:
com.kfit.id.IdGenerator=\ com.kfit.id.impl.UUIDIdGenerator,\ com.kfit.id.impl.TimestampGenerato然後在測試代碼中使用SpringFactoriesLoader進行加載:
public static void main(String[] args) { List<IdGenerator> idGeneratorList = SpringFactoriesLoader.loadFactories(IdGenerator.class,null); List<String> classNameList = SpringFactoriesLoader.loadFactoryNames(IdGenerator.class,null);
System.out.println(idGeneratorList); System.out.println(classNameList);}說明:
(1)spring.factories文件的key可以是接口,也可以是抽象類、具體的類。但是有個原則:=後面必須是key的實現類(子類)。
(2)在spring.factories文件中可以指定多個key,對於java spi如果多個接口的話,需要配置多個文件。
(3)key還可以是註解,比如SpringBoot中的的key:org.springframework.boot.autoconfigure.EnableAutoConfiguration,它就是一個註解。
(4)文件的格式需要保證正確,否則會返回[](不會報錯)。
(5)=右邊不能是抽象類,必須能夠實例化。且有空的構造函數~
(6)loadFactories依賴方法loadFactoryNames。loadFactoryNames方法只拿全類名,loadFactories拿到全類名後會立馬實例化
(7)loadFactories實例化完成所有實例後,會調用AnnotationAwareOrderComparator.sort(result)排序,所以它是支持Ordered接口排序的,這個特點特別的重要。
1.3原理
核心加載類是SpringFactoriesLoader,主要暴露了兩個方法:loadFactories和loadFactoryNames:
public final class SpringFactoriesLoader {
public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";
private static final Log logger = LogFactory.getLog(SpringFactoriesLoader.class);
private static final Map<ClassLoader, MultiValueMap<String, String>> cache = new ConcurrentReferenceHashMap<>();
private SpringFactoriesLoader() { }
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()); }
private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) { MultiValueMap<String, String> result = cache.get(classLoader); if (result != null) { return result; }
try { 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; } catch (IOException ex) { throw new IllegalArgumentException("Unable to load factories from location [" + FACTORIES_RESOURCE_LOCATION + "]", ex); } }
@SuppressWarnings("unchecked") private static <T> T instantiateFactory(String factoryImplementationName, Class<T> factoryType, ClassLoader classLoader) { try { Class<?> factoryImplementationClass = ClassUtils.forName(factoryImplementationName, classLoader); if (!factoryType.isAssignableFrom(factoryImplementationClass)) { throw new IllegalArgumentException( "Class [" + factoryImplementationName + "] is not assignable to factory type [" + factoryType.getName() + "]"); } return (T) ReflectionUtils.accessibleConstructor(factoryImplementationClass).newInstance(); } catch (Throwable ex) { throw new IllegalArgumentException( "Unable to instantiate factory class [" + factoryImplementationName + "] for factory type [" + factoryType.getName() + "]", ex); } }
}我們從源碼當中可以看到,loadFactories會調用loadFactoryNames,然後在loadFactoryNames方法中調用了方法loadSpringFactories:
private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) { MultiValueMap<String, String> result = cache.get(classLoader); if (result != null) { return result; }
try { 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; } catch (IOException ex) { throw new IllegalArgumentException("Unable to load factories from location [" + FACTORIES_RESOURCE_LOCATION + "]", ex); }}具體這裡的注釋已經寫的很詳細了,就不再過多進行說明了。
二、Spring-Boot-Starter
SpringBoot的 @SpringBootApplication 註解,裡面還有一個 @EnableAutoConfiguration 註解,開啟自動配置的。
可以發現這個自動配置註解在另一個工程,而這個工程裡也有個spring.factories文件,如下圖:
我們知道在SpringBoot的目錄下有個spring.factories文件,裡面都是一些接口的具體實現類,可以由SpringFactoriesLoader加載。
那麼同樣的道理,spring-boot-autoconfigure模塊也能動態加載了。看一下其中的內容:
org.springframework.context.ApplicationContextInitializer=\org.springframework.boot.autoconfigure.SharedMetadataReaderFactoryContextInitializer,\org.springframework.boot.autoconfigure.logging.ConditionEvaluationReportLoggingListener
org.springframework.context.ApplicationListener=\org.springframework.boot.autoconfigure.BackgroundPreinitializer
org.springframework.boot.autoconfigure.AutoConfigurationImportListener=\org.springframework.boot.autoconfigure.condition.ConditionEvaluationReportAutoConfigurationImportListener
org.springframework.boot.autoconfigure.AutoConfigurationImportFilter=\org.springframework.boot.autoconfigure.condition.OnBeanCondition,\org.springframework.boot.autoconfigure.condition.OnClassCondition,\org.springframework.boot.autoconfigure.condition.OnWebApplicationCondition
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration,\org.springframework.boot.autoconfigure.aop.AopAutoConfiguration,\org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration,\org.springframework.boot.autoconfigure.batch.BatchAutoConfiguration,\org.springframework.boot.autoconfigure.cache.CacheAutoConfiguration,\org.springframework.boot.autoconfigure.cassandra.CassandraAutoConfiguration,\org.springframework.boot.autoconfigure.context.ConfigurationPropertiesAutoConfiguration,\在這裡面配置了PropertySourceLoader和ApplicationListener等接口的具體實現類,然後通過SpringFactoriesLoader這個類去加載這個文件,並獲得具體的類路徑。
我就是我,是顏色不一樣的煙火。
我就是我,是與眾不同的小蘋果。à悟空學院:https://t.cn/Rg3fKJD
學院中有Spring Boot相關的課程!點擊「閱讀原文」進行查看!
SpringBoot視頻:http://t.cn/A6ZagYTi
SpringBoot交流平臺:https://t.cn/R3QDhU0
SpringSecurity5.0視頻:http://t.cn/A6ZadMBe
ShardingJDBC分庫分表:http://t.cn/A6ZarrqS
分布式事務解決方案:http://t.cn/A6ZaBnIr
JVM內存模型調優實戰:http://t.cn/A6wWMVqG
Spring入門到精通:https://t.cn/A6bFcDh4
大話設計模式之愛你:https://dwz.cn/wqO0MAy7