1. 程式人生 > >自定義spring boot starter三部曲之三:原始碼分析spring.factories載入過程

自定義spring boot starter三部曲之三:原始碼分析spring.factories載入過程

本文是《自定義spring boot starter三部曲》系列的終篇,前文中我們開發了一個starter並做了驗證,發現關鍵點在於spring.factories的自動載入能力,讓應用只要依賴starter的jar包即可,今天我們來分析Spring和Spring boot原始碼,瞭解spring.factories自動載入原理;

三部曲文章連結

  1. 《自定義spring boot starter三部曲之一:準備工作》
  2. 《自定義spring boot starter三部曲之二:實戰開發》
  3. 《自定義spring boot starter三部曲之三:原始碼分析spring.factories載入過程》

版本情況

本文中涉及到的庫的版本:

  1. Spring boot :1.5.9.RELEASE;
  2. JDK :1.8.0_144

初步分析

先回顧customizeservicestarter模組中spring.factories檔案的內容:

org.springframework.boot.autoconfigure.EnableAutoConfiguration=com.bolingcavalry.customizeservicestarter.CustomizeConfiguration

從上述內容可以確定今天原始碼學習目標:

  1. spring容器如何處理配置類;
  2. spring boot配置類的載入情況;
  3. spring.factories中的EnableAutoConfiguration配置何時被載入?
  4. spring.factories中的EnableAutoConfiguration配置被載入後做了什麼處理;

spring容器如何處理配置類

  1. ConfigurationClassPostProcessor類的職責是處理配置類;
  2. ConfigurationClassPostProcessor是BeanDefinitionRegistryPostProcessor介面的實現類,它的postProcessBeanDefinitionRegistry方法在容器初始化階段會被呼叫(BeanDefinitionRegistryPostProcessor介面的更多細節請參考
    《spring4.1.8擴充套件實戰之六:註冊bean到spring容器(BeanDefinitionRegistryPostProcessor介面)》
    );
  3. postProcessBeanDefinitionRegistry方法又呼叫processConfigBeanDefinitions方法處理具體業務;
  4. processConfigBeanDefinitions方法中通過ConfigurationClassParser類來處理Configuration註解,如下圖:
    在這裡插入圖片描述
  5. 如上圖紅框所示,所有被Configuration註解修飾過的類,都會被parser.parse(candidates)處理,即ConfigurationClassParser類的parse方法;
  6. 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容器配置類的邏輯:

  1. 找出配置類;
  2. 找出配置類中的Import註解;
  3. Import註解的值是class,如果該class實現了ImportSelector介面,就呼叫其selectImports方法,將返回的名稱例項化;

有了上面的結論就可以結合Spring boot的原始碼來分析載入了哪些資料了;

spring boot配置類的載入情況

  1. 我們的應用使用了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 {
	......
  1. EnableAutoConfiguration註解中,通過Import註解引入了EnableAutoConfigurationImportSelector.class:
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(EnableAutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {
	......
  1. 看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實現;

  1. 檢視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);
		}
	}
  1. 通過上述程式碼可以發現,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;
	}
  1. getCandidateConfigurations方法中,呼叫了靜態方法SpringFactoriesLoader.loadFactoryNames,上面提到的SpringFactoriesLoader.loadFactoryNames方法是關鍵,看看官方文件對此靜態方法的描述,如下圖紅框所示,該方法會在spring.factories檔案中尋找指定介面對應的實現類的全名(包名+實現類):
    在這裡插入圖片描述

  2. 在getCandidateConfigurations方法中,呼叫SpringFactoriesLoader.loadFactoryNames的時候傳入的指定型別是getSpringFactoriesLoaderFactoryClass方法的返回值:

protected Class<?> getSpringFactoriesLoaderFactoryClass() {
		return EnableAutoConfiguration.class;
	}

現在可以梳理一下了:

  1. spring boot應用啟動時使用了EnableAutoConfiguration註解;
  2. EnableAutoConfiguration註解通過import註解將EnableAutoConfigurationImportSelector類例項化,並且將其selectImports方法返回的類名例項化後註冊到spring容器;
  3. EnableAutoConfigurationImportSelector的selectImports方法返回的類名,來自spring.factories檔案內的配置資訊,這些配置資訊的key等於EnableAutoConfiguration;

現在真相大白了:只要我們在spring.factories檔案內配置了EnableAutoConfiguration,那麼對於的類就會被例項化後註冊到spring容器;

至此,《自定義spring boot starter三部曲》系列就完結了,希望實戰加原始碼分析的三篇文章,能幫助您理解和實現自定義starter這種簡單快捷的擴充套件方式;