Spring Boot 擴展點應用之工廠加載機制
Spring
工廠加載機制,即Spring Factories Loader
,核心邏輯是使用SpringFactoriesLoader
加載由用戶實現的類,並配置在約定好的META-INF/spring.factories
路徑下,該機制可以為框架上下文動態的增加擴展。
該機制類似於Java SPI
,給用戶提供可擴展的鉤子,從而達到對框架的自定義擴展功能。
核心實現類 SpringFactoriesLoader
SpringFactoriesLoader
是Spring
工廠加載機制的核心底層實現類。它的主要作用是 從META-INF/spring.factories
路徑下加載指定接口的實現類。該文件可能存在於工程類路徑下或者 jar 包之內,所以會存在多個該文件。
public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";
SpringFactoriesLoader
loadFactories
load 並從 FACTORIES_RESOURCE_LOCATION
文件中實例化給定類型的工廠實現類。 spring.factories
文件必須采用 Properties
格式,其中鍵是接口或抽象類的完全限定*名稱,值是逗號分隔的實現類名稱列表。例如:
該文件的格式,Key
必須為接口或抽象類的全限定名,value
為 具體的實現類,多個以 逗號分隔。類似如下配置:
# Application Context Initializers org.springframework.context.ApplicationContextInitializer=org.springframework.boot.context.ConfigurationWarningsApplicationContextInitializer,org.springframework.boot.context.ContextIdApplicationContextInitializer,org.springframework.boot.context.config.DelegatingApplicationContextInitializer,org.springframework.boot.web.context.ServerPortInfoApplicationContextInitializer
從該文件中我們可以看到,其中 ApplicationContextInitializer
為父類,value為實現類,以逗號分隔。
SpringFactoriesLoader 源碼分析
Spring Boot
完成自動裝配的核心之一就是工廠加載機制。我們以Spring Boot
的自動裝配為例來分析。如果要開啟Spring
的自動裝配功能,會使用@EnableAutoConfiguration
這個註解,這個註解會Import
AutoConfigurationImportSelector
這個類。
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {
AutoConfigurationImportSelector
中有一個方法就是加載 EnableAutoConfiguration
為 key
的實現配置類。
protected List<AutoConfigurationImportFilter> getAutoConfigurationImportFilters() {
return SpringFactoriesLoader.loadFactories(AutoConfigurationImportFilter.class,
this.beanClassLoader)
}
SpringFactoriesLoader
loadFactories
加載 所有以 factoryClass
為 Key
的實現類
public static <T> List<T> loadFactories(Class<T> factoryClass, @Nullable ClassLoader classLoader) {
// 省略部分前置判斷和 logger 代碼
ClassLoader classLoaderToUse = classLoader;
if (classLoaderToUse == null) {
classLoaderToUse = SpringFactoriesLoader.class.getClassLoader();
}
//根據當前接口類的全限定名作為key,從loadFactoryNames從文件中獲取到所有實現類的全限定名
List<String> factoryNames = loadFactoryNames(factoryClass, classLoaderToUse);
List<T> result = new ArrayList<>(factoryNames.size());
//實例化所有實現類,並保存到 result 中返回。
for (String factoryName : factoryNames) {
result.add(instantiateFactory(factoryName, factoryClass, classLoaderToUse));
}
AnnotationAwareOrderComparator.sort(result);
return result;
}
調用 loadSpringFactories
從META-INF/spring.factories
文件中進行加載
從文件中讀取接口和實現類的邏輯,返回
Map<String, List<String>>
private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
MultiValueMap<String, String> result = cache.get(classLoader);
if (result != null) {
return result;
}
try {
//FACTORIES_RESOURCE_LOCATION --> META-INF/spring.factories
Enumeration<URL> urls = (classLoader != null ?
classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
//一Key多值 Map,適合上文提到的一個接口多個實現類的情形。
result = new LinkedMultiValueMap<>();
while (urls.hasMoreElements()) {
URL url = urls.nextElement();
UrlResource resource = new UrlResource(url);
Properties properties = PropertiesLoaderUtils.loadProperties(resource);
for (Map.Entry<?, ?> entry : properties.entrySet()) {
//以逗號進行分割,得到List的實現類全限定名集合
List<String> factoryClassNames = Arrays.asList(
StringUtils.commaDelimitedListToStringArray((String) entry.getValue()));
result.addAll((String) entry.getKey(), factoryClassNames);
}
}
cache.put(classLoader, result);
//返回
return result;
}
catch (IOException ex) {
throw new IllegalArgumentException("Unable to load factories from location [" +
FACTORIES_RESOURCE_LOCATION + "]", ex);
}
}
總結
上面通過以 Spring Boot
的自動裝配為例,我們分析了 Spring
工廠加載機制的整個過程,重點分析了SpringFactoriesLoader
類。通過這樣的機制,我們可以十分的方便的為框架提供各式各樣的擴展插件,我們可以自己定義自己的組件的自動裝配配置類,然後通過工廠加載機制讓 Spring
進行加載並得到自動裝配。
工廠加載機制的應用 ApplicationContextInitializer
ApplicationContextInitializer
是在Spring Boot
或者Spring Mvc
啟動過程中調用的。具體時機為Spring 應用上下文refresh
之前(調用refresh
方法)。
ApplicationContextInitializer 主要提供應用上下文未refresh之前的擴展,這時候可以對 ConfigurableApplicationContext
進行一些擴展處理等。
自定義一個類,實現 ApplicationContextInitializer
,並重寫 initialize
方法:
@Order(Ordered.HIGHEST_PRECEDENCE)
public class HelloWorldApplicationContextInitializer implements ApplicationContextInitializer {
@Override
public void initialize(ConfigurableApplicationContext applicationContext) {
System.out.println("=================> applicationContext: " + applicationContext.getId());
}
}
啟動 Spring Boot
程序,我們可以看到在 refresh
之前,會在控制臺打印上面這句話。
- 另外的實現方式:
- application.properties添加配置方式:
context.initializer.classes=com.maple.spring.initializer.AfterHelloWorldApplicationContextInitializer
對於這種方式是通過 DelegatingApplicationContextInitializer
這個初始化類中的 initialize
方法獲取到 application.properties
中 context.initializer.classes
對應的實現類,並對該實現類進行加載。
3.在 SpringApplication
中直接添加
public static void main(String[] args) {
new SpringApplicationBuilder(SpringBootDemo3Application.class)
.initializers(new AfterHelloWorldApplicationContextInitializer())
.run(args);
}
}
Spring Boot 使用 工廠機制加載 ApplicationListener 實現類
# Application Listeners
org.springframework.context.ApplicationListener=org.springframework.boot.ClearCachesApplicationListener,org.springframework.boot.builder.ParentContextCloserApplicationListener,org.springframework.boot.context.FileEncodingApplicationListener,org.springframework.boot.context.config.AnsiOutputApplicationListener,org.springframework.boot.context.config.ConfigFileApplicationListener,org.springframework.boot.context.config.DelegatingApplicationListener,org.springframework.boot.context.logging.ClasspathLoggingApplicationListener,org.springframework.boot.context.logging.LoggingApplicationListener,org.springframework.boot.liquibase.LiquibaseServiceLocatorApplicationListener
總結
工廠加載機制是 Spring
動態加載實現類的一種方式,提前在擴展類中寫好對應自動配置類,我們可以完成自動裝配的效果。Spring Boot
自動裝配模塊其中的loader 自動配置實現類就是基於此實現的。
Spring Boot
的一些新特性幾乎用到的都是 Spring Framework
的核心特性。因此學習 Spring Boot
,歸根結底就是學習 Spring Framework
核心。它是所有 Spring
應用的基石,所以我們應該從上至下,由淺入深來進行學習和分析。
Spring Boot 擴展點應用之工廠加載機制