自定義spring boot starter三部曲之三:原始碼分析spring.factories載入過程
本文是《自定義spring boot starter三部曲》系列的終篇,前文中我們開發了一個starter並做了驗證,發現關鍵點在於spring.factories的自動載入能力,讓應用只要依賴starter的jar包即可,今天我們來分析Spring和Spring boot原始碼,瞭解spring.factories自動載入原理;
三部曲文章連結
- 《自定義spring boot starter三部曲之一:準備工作》;
- 《自定義spring boot starter三部曲之二:實戰開發》;
- 《自定義spring boot starter三部曲之三:原始碼分析spring.factories載入過程》
版本情況
本文中涉及到的庫的版本:
- Spring boot :1.5.9.RELEASE;
- JDK :1.8.0_144
初步分析
先回顧customizeservicestarter模組中spring.factories檔案的內容:
org.springframework.boot.autoconfigure.EnableAutoConfiguration=com.bolingcavalry.customizeservicestarter.CustomizeConfiguration
從上述內容可以確定今天原始碼學習目標:
- spring容器如何處理配置類;
- spring boot配置類的載入情況;
- spring.factories中的EnableAutoConfiguration配置何時被載入?
- spring.factories中的EnableAutoConfiguration配置被載入後做了什麼處理;
spring容器如何處理配置類
- ConfigurationClassPostProcessor類的職責是處理配置類;
- ConfigurationClassPostProcessor是BeanDefinitionRegistryPostProcessor介面的實現類,它的postProcessBeanDefinitionRegistry方法在容器初始化階段會被呼叫(BeanDefinitionRegistryPostProcessor介面的更多細節請參考
- postProcessBeanDefinitionRegistry方法又呼叫processConfigBeanDefinitions方法處理具體業務;
- processConfigBeanDefinitions方法中通過ConfigurationClassParser類來處理Configuration註解,如下圖:
- 如上圖紅框所示,所有被Configuration註解修飾過的類,都會被parser.parse(candidates)處理,即ConfigurationClassParser類的parse方法;
- parse方法中呼叫processDeferredImportSelectors方法做處理:找到Configuration類中的Import註解,對於Import註解的值,如果實現了ImportSelector介面,就呼叫其selectImports方法,將返回的名稱例項化:
private void processDeferredImportSelectors() {
//這裡就是Configuration註解中的Import註解的值,
//例如EnableAutoConfiguration註解的原始碼中,Import註解的值是EnableAutoConfigurationImportSelector.class
List<DeferredImportSelectorHolder> deferredImports = this.deferredImportSelectors;
this.deferredImportSelectors = null;
Collections.sort(deferredImports, DEFERRED_IMPORT_COMPARATOR);
for (DeferredImportSelectorHolder deferredImport : deferredImports) {
ConfigurationClass configClass = deferredImport.getConfigurationClass();
try {
//以EnableAutoConfiguration註解為例,其Import註解的值為EnableAutoConfigurationImportSelector.class,
//那麼此處就是在呼叫EnableAutoConfigurationImportSelector的selectImports方法,返回了一個字串陣列
String[] imports = deferredImport.getImportSelector().selectImports(configClass.getMetadata());
//字串陣列中的每個字串都代表一個類,此處做例項化
processImports(configClass, asSourceClass(configClass), asSourceClasses(imports), false);
}
catch (BeanDefinitionStoreException ex) {
throw ex;
}
catch (Throwable ex) {
throw new BeanDefinitionStoreException(
"Failed to process import candidates for configuration class [" +
configClass.getMetadata().getClassName() + "]", ex);
}
}
}
小結一下spring容器配置類的邏輯:
- 找出配置類;
- 找出配置類中的Import註解;
- Import註解的值是class,如果該class實現了ImportSelector介面,就呼叫其selectImports方法,將返回的名稱例項化;
有了上面的結論就可以結合Spring boot的原始碼來分析載入了哪些資料了;
spring boot配置類的載入情況
- 我們的應用使用了SpringBootApplication註解,看此註解的原始碼,使用了EnableAutoConfiguration註解:
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = {
@Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {
......
- EnableAutoConfiguration註解中,通過Import註解引入了EnableAutoConfigurationImportSelector.class:
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(EnableAutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {
......
- 看EnableAutoConfigurationImportSelector的原始碼:
/**
* {@link DeferredImportSelector} to handle {@link EnableAutoConfiguration
* auto-configuration}. This class can also be subclassed if a custom variant of
* {@link EnableAutoConfiguration @EnableAutoConfiguration}. is needed.
*
* @deprecated as of 1.5 in favor of {@link AutoConfigurationImportSelector}
* @author Phillip Webb
* @author Andy Wilkinson
* @author Stephane Nicoll
* @author Madhura Bhave
* @since 1.3.0
* @see EnableAutoConfiguration
*/
@Deprecated
public class EnableAutoConfigurationImportSelector
extends AutoConfigurationImportSelector {
@Override
protected boolean isEnabled(AnnotationMetadata metadata) {
if (getClass().equals(EnableAutoConfigurationImportSelector.class)) {
return getEnvironment().getProperty(
EnableAutoConfiguration.ENABLED_OVERRIDE_PROPERTY, Boolean.class,
true);
}
return true;
}
}
上述原始碼有三處重點需要關注:
第一,EnableAutoConfigurationImportSelector是AutoConfigurationImportSelector的子類;
第二,EnableAutoConfigurationImportSelector已經被廢棄了,不推薦使用;
第三,文件中已經寫明廢棄原因:從1.5版本開始,其特性由父類AutoConfigurationImportSelector實現;
- 檢視AutoConfigurationImportSelector的原始碼,重點關注selectImports方法,該方法的返回值表明了哪些類會被例項化:
@Override
public String[] selectImports(AnnotationMetadata annotationMetadata) {
if (!isEnabled(annotationMetadata)) {
return NO_IMPORTS;
}
try {
//將所有spring-autoconfigure-metadata.properties檔案中的鍵值對儲存在autoConfigurationMetadata中
AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader
.loadMetadata(this.beanClassLoader);
AnnotationAttributes attributes = getAttributes(annotationMetadata);
//取得所有配置類的名稱
List<String> configurations = getCandidateConfigurations(annotationMetadata,
attributes);
configurations = removeDuplicates(configurations);
configurations = sort(configurations, autoConfigurationMetadata);
Set<String> exclusions = getExclusions(annotationMetadata, attributes);
checkExcludedClasses(configurations, exclusions);
configurations.removeAll(exclusions);
configurations = filter(configurations, autoConfigurationMetadata);
fireAutoConfigurationImportEvents(configurations, exclusions);
return configurations.toArray(new String[configurations.size()]);
}
catch (IOException ex) {
throw new IllegalStateException(ex);
}
}
- 通過上述程式碼可以發現,getCandidateConfigurations方法的呼叫是個關鍵,它返回的字串都是即將被例項化的類名,來看此方法原始碼:
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata,
AnnotationAttributes attributes) {
List<String> configurations = SpringFactoriesLoader.loadFactoryNames(
getSpringFactoriesLoaderFactoryClass(), getBeanClassLoader());
Assert.notEmpty(configurations,
"No auto configuration classes found in META-INF/spring.factories. If you "
+ "are using a custom packaging, make sure that file is correct.");
return configurations;
}
-
getCandidateConfigurations方法中,呼叫了靜態方法SpringFactoriesLoader.loadFactoryNames,上面提到的SpringFactoriesLoader.loadFactoryNames方法是關鍵,看看官方文件對此靜態方法的描述,如下圖紅框所示,該方法會在spring.factories檔案中尋找指定介面對應的實現類的全名(包名+實現類):
-
在getCandidateConfigurations方法中,呼叫SpringFactoriesLoader.loadFactoryNames的時候傳入的指定型別是getSpringFactoriesLoaderFactoryClass方法的返回值:
protected Class<?> getSpringFactoriesLoaderFactoryClass() {
return EnableAutoConfiguration.class;
}
現在可以梳理一下了:
- spring boot應用啟動時使用了EnableAutoConfiguration註解;
- EnableAutoConfiguration註解通過import註解將EnableAutoConfigurationImportSelector類例項化,並且將其selectImports方法返回的類名例項化後註冊到spring容器;
- EnableAutoConfigurationImportSelector的selectImports方法返回的類名,來自spring.factories檔案內的配置資訊,這些配置資訊的key等於EnableAutoConfiguration;
現在真相大白了:只要我們在spring.factories檔案內配置了EnableAutoConfiguration,那麼對於的類就會被例項化後註冊到spring容器;
至此,《自定義spring boot starter三部曲》系列就完結了,希望實戰加原始碼分析的三篇文章,能幫助您理解和實現自定義starter這種簡單快捷的擴充套件方式;