springboot加載spring.factories的價值


在springboot的各個依賴包下,我們經常看到META-INF/spring.factories這個文件。spring.factories文件的內容基本上都是這樣的格式

1 # Initializers
2 org.springframework.context.ApplicationContextInitializer=
\org.springframework.boot.autoconfigure.SharedMetadataReaderFactoryContextInitializer,
\org.springframework.boot.autoconfigure.logging.ConditionEvaluationReportLoggingListener

通過spring.factories內容我們知道,這個里面其實就是應用上下文初始化。那么這個配置文件有什么作用呢?

通過源碼我們可以看出來。

org.springframework.context.ApplicationContextInitializer是一個接口。而,
org.springframework.boot.autoconfigure.SharedMetadataReaderFactoryContextInitializer
org.springframework.boot.autoconfigure.logging.ConditionEvaluationReportLoggingListener
這兩個就是接口的實現類。那么從這個邏輯看我們知道。spring.factories這個配置件,就是加載
ApplicationContextInitializer接口的實現類。實現初始化的作用、那么我們知道大部分封裝的功能獲取對象都是通過。
反射獲取對應的實例對象。就像是簡單工廠模式一樣,也因此spring將這個文件定義為spring.factories這個名字


下面以ApplicationContextInitializer接口為示例,我們看看springboot是怎么使用spring.factories的。

首先會用classLoader加載類路徑下的所有spring.factories的配置內容,loadSpringFactories方法將返回一個key=接口名,value=實現類集合的Map結構

private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
    // 先試着取緩存
    MultiValueMap<String, String> result = cache.get(classLoader); if (result != null) { return result; } try { // 獲取所有spring.factories的URL Enumeration<URL> urls = (classLoader != null ? classLoader.getResources(FACTORIES_RESOURCE_LOCATION) : ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION)); result = new LinkedMultiValueMap<>(); // 遍歷URL while (urls.hasMoreElements()) { URL url = urls.nextElement(); UrlResource resource = new UrlResource(url); // 加載每個URL中的properties配置 Properties properties = PropertiesLoaderUtils.loadProperties(resource); // 遍歷每個配置 for (Map.Entry<?, ?> entry : properties.entrySet()) { String factoryClassName = ((String) entry.getKey()).trim(); // 將實現類的配置按照","符號分割開 for (String factoryName : StringUtils.commaDelimitedListToStringArray((String) entry.getValue())) { // 逐個添加到接口對應的集合當中  result.add(factoryClassName, factoryName.trim()); } } } // 加入緩存  cache.put(classLoader, result); return result; } catch (IOException ex) { // ...  } }

有了以上這個Map結構,就可以拿到對應接口的實現類集合

public static List<String> loadFactoryNames(Class<?> factoryClass, @Nullable ClassLoader classLoader) {
    String factoryClassName = factoryClass.getName(); return loadSpringFactories(classLoader).getOrDefault(factoryClassName, Collections.emptyList()); }

這里的factoryClass是接口,通過getName()方法獲取全限定名,然后根據該全限定名從Map結構中get出對應的實現類全限定名的集合。

到這里我們得到了一個實現類的集合,要獲取實現類具體的實例對象只需要通過反射得到實例對象即可

private <T> List<T> createSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes,
            ClassLoader classLoader, Object[] args, Set<String> names) { List<T> instances = new ArrayList<>(names.size()); // 遍歷實例對象的全限定名 for (String name : names) { try { // 加載該類 Class<?> instanceClass = ClassUtils.forName(name, classLoader); // 斷言是否為該接口的實現類  Assert.isAssignable(type, instanceClass); // 獲取構造方法 Constructor<?> constructor = instanceClass.getDeclaredConstructor(parameterTypes); // 實例化該類 T instance = (T) BeanUtils.instantiateClass(constructor, args); // 添加到結果集當中  instances.add(instance); } catch (Throwable ex) { throw new IllegalArgumentException("Cannot instantiate " + type + " : " + name, ex); } } return instances; }

 

因此spring.factories就像是工廠一樣配置了大量的接口對應的實現類,我們通過這些配置 + 反射處理就可以拿到相應的實現類。這種類似於插件式的設計方式,只要引入對應的jar包,那么對應的spring.factories就會被掃描到,對應的實現類也就會被實例化,如果不需要的時候,直接把jar包移除即可。

這也是我們說為什么要加上spring.factories配置文件的作用。實現的熱插法




















免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM