spring.factories配置文件的工廠模式

在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包移除便可。

相關文章
相關標籤/搜索