1. 程式人生 > >Springboot 內部工具類 SpringFactoriesLoader 原始碼解析

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 屬性檔案,並分析出其中定義的工廠類。這些工廠類進而被啟動邏輯使用,應用於進一步初始化工作。

公開成員介紹

  1. 類靜態成員常量 final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories"

此常量定義了該工具類要從每個jar包中提取的工廠類定義屬性檔案的相對路徑。

  1. 類靜態方法 <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)

當然還有其他一些入口點,這裡就不一一列舉。