在springboot的各個依賴包下,咱們常常看到META-INF/spring.factories這個文件。spring.factories文件的內容基本上都是這樣的格式:spring
1 # Initializers 2 org.springframework.context.ApplicationContextInitializer=\ 3 org.springframework.boot.autoconfigure.SharedMetadataReaderFactoryContextInitializer,\ 4 org.springframework.boot.autoconfigure.logging.ConditionEvaluationReportLoggingListener
咱們看到,這個文件配置了一個key:value格式的數據緩存
1)key是:org.springframework.context.ApplicationContextInitializerspringboot
2)value是:org.springframework.context.boot.autoconfigure.SharedMetadataReaderFactoryContextInitializer,org.springframework.context.boot.autoconfigure.logging.ConditionEvaluationReportLoggingListenerlua
key聲明的是一個接口,value則是這個接口對應的實現類,若是有多個則以","符號分割。url
簡單來講,spring.factories文件包含了一些接口相對應的實現類的配置,咱們經過這些配置就能夠知道接口有哪些可選的實現類,並經過反射獲取對應的實例對象。就像是簡單工廠模式同樣,也所以spring將這個文件定義爲spring.factories這個名字。spa
下面以ApplicationContextInitializer接口爲示例,咱們看看springboot是怎麼使用spring.factories的。插件
首先會用classLoader加載類路徑下的全部spring.factories的配置內容,loadSpringFactories方法將返回一個key=接口名,value=實現類集合的Map結構設計
1 private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) { 2 // 先試着取緩存 3 MultiValueMap<String, String> result = cache.get(classLoader); 4 if (result != null) { 5 return result; 6 } 7 8 try { 9 // 獲取全部spring.factories的URL 10 Enumeration<URL> urls = (classLoader != null ? classLoader.getResources(FACTORIES_RESOURCE_LOCATION) : ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION)); 11 result = new LinkedMultiValueMap<>(); 12 // 遍歷URL 13 while (urls.hasMoreElements()) { 14 URL url = urls.nextElement(); 15 UrlResource resource = new UrlResource(url); 16 // 加載每一個URL中的properties配置 17 Properties properties = PropertiesLoaderUtils.loadProperties(resource); 18 // 遍歷每一個配置 19 for (Map.Entry<?, ?> entry : properties.entrySet()) { 20 String factoryClassName = ((String) entry.getKey()).trim(); 21 // 將實現類的配置按照","符號分割開 22 for (String factoryName : StringUtils.commaDelimitedListToStringArray((String) entry.getValue())) { 23 // 逐個添加到接口對應的集合當中 24 result.add(factoryClassName, factoryName.trim()); 25 } 26 } 27 } 28 // 加入緩存 29 cache.put(classLoader, result); 30 return result; 31 } catch (IOException ex) { 32 // ... 33 } 34 }
有了以上這個Map結構,就能夠輕鬆拿到對應接口的實現類集合了,如:code
1 public static List<String> loadFactoryNames(Class<?> factoryClass, @Nullable ClassLoader classLoader) { 2 String factoryClassName = factoryClass.getName(); 3 return loadSpringFactories(classLoader).getOrDefault(factoryClassName, Collections.emptyList()); 4 }
這裏的factoryClass是接口,經過getName()方法獲取全限定名,而後根據該全限定名從Map結構中get出對應的實現類全限定名的集合。對象
到這裏咱們獲得了一個實現類的集合,要獲取實現類具體的實例對象只須要經過反射獲得實例對象便可,如:
1 private <T> List<T> createSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes, 2 ClassLoader classLoader, Object[] args, Set<String> names) { 3 List<T> instances = new ArrayList<>(names.size()); 4 // 遍歷實例對象的全限定名 5 for (String name : names) { 6 try { 7 // 加載該類 8 Class<?> instanceClass = ClassUtils.forName(name, classLoader); 9 // 斷言是否爲該接口的實現類 10 Assert.isAssignable(type, instanceClass); 11 // 獲取構造方法 12 Constructor<?> constructor = instanceClass.getDeclaredConstructor(parameterTypes); 13 // 實例化該類 14 T instance = (T) BeanUtils.instantiateClass(constructor, args); 15 // 添加到結果集當中 16 instances.add(instance); 17 } 18 catch (Throwable ex) { 19 throw new IllegalArgumentException("Cannot instantiate " + type + " : " + name, ex); 20 } 21 } 22 return instances; 23 }
spring.factories就像是工廠同樣配置了大量的接口對應的實現類,咱們經過這些配置 + 反射處理就能夠拿到相應的實現類。這種相似於插件式的設計方式,只要引入對應的jar包,那麼對應的spring.factories就會被掃描到,對應的實現類也就會被實例化,若是不須要的時候,直接把jar包移除便可。