Springboot 內部工具類 SpringFactoriesLoader 原始碼解析
類名 : SpringFactoriesLoader
所在包 : org.springframework.core.io.support
官方文件
本文原始碼基於 springboot 2.1.0,跟之前 springboot 1.5.x 版本相比,
SpringFactoriesLoader
實現已經有了變化,
比如在新的實現中已經加入了快取機制。如果相應的檔案需要被讀取多次的話,第一次讀取後會快取起來,之後
的讀取會使用快取資料。
概述
該類並不對外暴露給應用開發者使用,而是spring
框架自己使用的內部工具類,本身被宣告為 abstract
,不可以被例項化。
在 Springboot
應用啟動的過程中,這個類的工作很重要, 啟動邏輯使用該類從classpath
上所有jar
包中找出各自的 META-INF/spring.factories
屬性檔案,並分析出其中定義的工廠類。這些工廠類進而被啟動邏輯使用,應用於進一步初始化工作。
公開成員介紹
- 類靜態成員常量
final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories"
此常量定義了該工具類要從每個jar包中提取的工廠類定義屬性檔案的相對路徑。
- 類靜態方法
<T> List<T> loadFactories(Class<T> factoryClass, ClassLoader classLoader)
此方法會讀取classpath
上所有的jar
包中的所有 META-INF/spring.factories
屬性檔案,找出其中定義的匹配型別 factoryClass
的工廠類,然後建立每個工廠類的物件/例項,並返回這些工廠類物件/例項的列表。
3. 類靜態方法 List<String> loadFactoryNames(Class<?> factoryClass, ClassLoader classLoader)
此方法會讀取classpath
上所有的jar包中的所有META-INF/spring.factories
屬性檔案,找出其中定義的匹配型別 factoryClass
原始碼解析
package org.springframework.core.io.support;
import java.io.IOException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Enumeration;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.core.annotation.AnnotationAwareOrderComparator;
import org.springframework.core.io.UrlResource;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.util.ConcurrentReferenceHashMap;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.util.ReflectionUtils;
import org.springframework.util.StringUtils;
/**
* General purpose factory loading mechanism for internal use within the framework.
* 僅限框架內部使用的工具類,通用目的的工廠載入機制。
*
* SpringFactoriesLoader#loadFactories loads and instantiates
* factories of a given type from #FACTORIES_RESOURCE_LOCATION files which
* may be present in multiple JAR files in the classpath. The spring.factories
* file must be in Properties format, where the key is the fully qualified
* name of the interface or abstract class, and the value is a comma-separated list of
* implementation class names.
*
* SpringFactoriesLoader#loadFactories設計用於載入和例項化指定型別的工廠,這些工廠型別的定義
* 來自classpath中多個JAR包內常量FACTORIES_RESOURCE_LOCATION所指定的那些spring.factories檔案。
* spring.factories檔案的格式必須是屬性檔案格式,每條屬性的key必須是介面或者抽象類的全限定名,
* 而屬性值value是一個逗號分割的實現類的名稱。
*
* 舉例來講,一條屬性定義如下:
*
* example.MyService=example.MyServiceImpl1,example.MyServiceImpl2
*
* 這裡 example.MyService 是介面或者抽象類的全限定名, MyServiceImpl1和MyServiceImpl2是相應的
* 兩個實現類。
*
*/
public final class SpringFactoriesLoader {
/**
* The location to look for factories.
* Can be present in multiple JAR files.
* 在classpath中的多個JAR中要掃描的工廠配置檔案的在本JAR包中的路徑。
* 實際上,Springboot的每個 autoconfigure包都包含一個這樣的檔案。
*/
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() {
}
/**
* Load and instantiate the factory implementations of the given type from
* #FACTORIES_RESOURCE_LOCATION, using the given class loader.
* The returned factories are sorted through AnnotationAwareOrderComparator.
* If a custom instantiation strategy is required, use #loadFactoryNames
* to obtain all registered factory names.
* @param factoryClass the interface or abstract class representing the factory
* @param classLoader the ClassLoader to use for loading (can be null to use the default)
* @throws IllegalArgumentException if any factory implementation class cannot
* be loaded or if an error occurs while instantiating any factory
* @see #loadFactoryNames
*/
public static <T> List<T> loadFactories(Class<T> factoryClass, @Nullable ClassLoader classLoader) {
Assert.notNull(factoryClass, "'factoryClass' must not be null");
ClassLoader classLoaderToUse = classLoader;
if (classLoaderToUse == null) {
classLoaderToUse = SpringFactoriesLoader.class.getClassLoader();
}
// 載入型別為factoryClass的工廠的名稱,其實是一個個的全限定類名,使用指定的classloader:
// classLoaderToUse
List<String> factoryNames = loadFactoryNames(factoryClass, classLoaderToUse);
if (logger.isTraceEnabled()) {
logger.trace("Loaded [" + factoryClass.getName() + "] names: " + factoryNames);
}
List<T> result = new ArrayList<>(factoryNames.size());
// 例項化所載入的每個工廠類
for (String factoryName : factoryNames) {
result.add(instantiateFactory(factoryName, factoryClass, classLoaderToUse));
}
// 排序
AnnotationAwareOrderComparator.sort(result);
return result;
}
/**
* Load the fully qualified class names of factory implementations of the
* given type from #FACTORIES_RESOURCE_LOCATION, using the given
* class loader.
* @param factoryClass the interface or abstract class representing the factory
* @param classLoader the ClassLoader to use for loading resources; can be
* null to use the default
* @throws IllegalArgumentException if an error occurs while loading factory names
* @see #loadFactories
*/
public static List<String> loadFactoryNames(Class<?> factoryClass, @Nullable ClassLoader classLoader) {
String factoryClassName = factoryClass.getName();
// 1. 使用指定的classloader掃描classpath上所有的JAR包中的檔案META-INF/spring.factories,載入其中的多值
// 工廠屬性定義,使用多值Map的形式返回,
// 2. 返回多值Map中key為factoryClassName的工廠名稱列表,如果沒有相應的entry,返回空列表而不是返回null
return loadSpringFactories(classLoader).getOrDefault(factoryClassName, Collections.emptyList());
}
/**
* 使用指定的classloader掃描classpath上所有的JAR包中的檔案META-INF/spring.factories,載入其中的多值
* 工廠屬性定義,使用多值Map的形式返回
**/
private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
MultiValueMap<String, String> result = cache.get(classLoader);
if (result != null) {
return result;
}
try {
// 掃描classpath上所有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()) {
// 找到的每個META-INF/spring.factories檔案都是一個Properties檔案,將其內容
// 載入到一個 Properties 物件然後處理其中的每個屬性
URL url = urls.nextElement();
UrlResource resource = new UrlResource(url);
Properties properties = PropertiesLoaderUtils.loadProperties(resource);
for (Map.Entry<?, ?> entry : properties.entrySet()) {
// 獲取工廠類名稱(介面或者抽象類的全限定名)
String factoryClassName = ((String) entry.getKey()).trim();
// 將逗號分割的屬性值逐個取出,然後放到多值Map結果result中去。
for (String factoryName : StringUtils.commaDelimitedListToStringArray(
(String) entry.getValue())) {
result.add(factoryClassName, factoryName.trim());
}
}
}
cache.put(classLoader, result);
return result;
}
catch (IOException ex) {
throw new IllegalArgumentException("Unable to load factories from location [" +
FACTORIES_RESOURCE_LOCATION + "]", ex);
}
}
/**
* @param instanceClassName 工廠實現類全限定名稱
* @param factoryClass 工廠所屬介面/抽象類全限定名稱
* @param classLoader 所要使用的類載入器
**/
@SuppressWarnings("unchecked")
private static <T> T instantiateFactory(String instanceClassName, Class<T> factoryClass,
ClassLoader classLoader) {
try {
Class<?> instanceClass = ClassUtils.forName(instanceClassName, classLoader);
if (!factoryClass.isAssignableFrom(instanceClass)) {
throw new IllegalArgumentException(
"Class [" + instanceClassName + "] is not assignable to ["
+ factoryClass.getName() + "]");
}
return (T) ReflectionUtils.accessibleConstructor(instanceClass).newInstance();
}
catch (Throwable ex) {
throw new IllegalArgumentException("Unable to instantiate factory class: "
+ factoryClass.getName(), ex);
}
}
}
主要用途
Spring提供的一些JAR包,主要是springboot提供的一些JAR包裡面會帶有檔案META-INF/spring.factories,然後在Springboot啟動的時候,根據啟動階段不同的需求,框架就會多次呼叫SpringFactoriesLoader
載入相應的工廠配置資訊。
比如Springboot應用使用了註解@EnableAutoConfiguration
時,就會觸發對SpringFactoriesLoader.loadFactoryNames()
的呼叫。具體請參考 :
Spring EnableAutoConfigurationImportSelector 是如何工作的 ?
還有其他一些應用點,比如 :
// SpringApplication.initialize
// => SpringApplication.getSpringFactoriesInstances()
SpringFactoriesLoader.loadFactoryNames(org.springframework.context.ApplicationContextInitializer)
// SpringApplication.initialize
// => SpringApplication.getSpringFactoriesInstances()
SpringFactoriesLoader.loadFactoryNames(org.springframework.context.ApplicationListenr)
// SpringApplication.run
// => getRunListeners
// => SpringApplication.getSpringFactoriesInstances()
SpringFactoriesLoader.loadFactoryNames(org.springframework.boot.SpringApplicationRunListener)
// SpringApplication.run
// => prepareEnvironment
// => SpringApplication.getSpringFactoriesInstances()
// => ConfigFileApplicationListener.onApplicationEnvironmentPreparedEvent() //事件處理
// => loadPostProcessors()
SpringFactoriesLoader.loadFactoryNames(org.springframework.boot.env.EnvironmentPostProcessor)
當然還有其他一些入口點,這裡就不一一列舉。