starter源碼解析

上一篇介紹了如何自定義starterhtml

這一篇來揭開starter的神祕面紗。其實starter的原理就是Spring Boot自動裝配的原理。 Auto-configurationjava

在SpringBoot官方文檔的第四章「6. Using the @SpringBootApplication Annotation」一節中,對@SpringBootApplication@EnableAutoConfiguration作出瞭解釋:git

A single @SpringBootApplication annotation can be used to enable those three features, that is:
@EnableAutoConfiguration: enable Spring Boot’s auto-configuration mechanism(啓動SpringBoot的自動配置)
@ComponentScan: enable @Component scan on the package where the application is located (see the best practices)
@ConfigurationPropertiesScan: enable @ConfigurationProperties scan on the package where the application is located (see the best practices)
@Configuration: allow to register extra beans in the context or import additional configuration classes
複製代碼

理解 @EnableAutoConfiguration

從官網能夠知道,@EnableAutoConfiguration是啓動自動配置的關鍵。 直接查看源碼,這裏個人SpringBoot版本是2.2.6github

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {

	String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";

	/** * Exclude specific auto-configuration classes such that they will never be applied. * @return the classes to exclude */
	Class<?>[] exclude() default {};

	/** * Exclude specific auto-configuration class names such that they will never be * applied. * @return the class names to exclude * @since 1.3.0 */
	String[] excludeName() default {};

}
複製代碼

這裏能夠看到該註解有兩個屬性,分別是exclude()與excludeName(),根據註解的意思能夠知道,這裏是提供兩種方式來排除自動加載的類。除此以外SpringBoot還提供了一種外部化配置spring.autoconfigure.exclude來排除不想加載的類。spring

該註解如何排除排除組件下面會繼續分析。編程

這裏看到了@Import註解,該註解的意思是導入一個配置類。在org.springframework.context.annotation.ConfigurationClassParser#processImports方法中實現,之後會專門寫篇文章來說解。其中AutoConfigurationImportSelector實現了DeferredImportSelector接口,而DeferredImportSelector又繼承ImportSelector。因此關鍵邏輯在selectImports(AnnotationMetadata)方法中實現:bash

public class AutoConfigurationImportSelector implements DeferredImportSelector, BeanClassLoaderAware,
		ResourceLoaderAware, BeanFactoryAware, EnvironmentAware, Ordered {
			@Override
	···
	public String[] selectImports(AnnotationMetadata annotationMetadata) {
		if (!isEnabled(annotationMetadata)) {
			return NO_IMPORTS;
		}
		// 1 加載元信息
		AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader
				.loadMetadata(this.beanClassLoader);
		// 2 核心代碼,下面重點分析
		AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(autoConfigurationMetadata,
				annotationMetadata);
		return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
	}
	
	protected AutoConfigurationEntry getAutoConfigurationEntry(AutoConfigurationMetadata autoConfigurationMetadata,
		AnnotationMetadata annotationMetadata) {
	if (!isEnabled(annotationMetadata)) {
		return EMPTY_ENTRY;
	}
	// 2.1 獲取標註@EnableAutoConfiguration類的元信息
	AnnotationAttributes attributes = getAttributes(annotationMetadata);
	// 2.2 
	List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
	// 2.3 放入LinkedHashSet去重
	configurations = removeDuplicates(configurations);
	// 2.4 排除自動裝配類
	Set<String> exclusions = getExclusions(annotationMetadata, attributes);
	// 2.5 
	checkExcludedClasses(configurations, exclusions);
	// 2.6
	configurations.removeAll(exclusions);
	// 2.7
	configurations = filter(configurations, autoConfigurationMetadata);
	// 2.8
	fireAutoConfigurationImportEvents(configurations, exclusions);
	return new AutoConfigurationEntry(configurations, exclusions);
}
	···
}
複製代碼

1.加載元信息

final class AutoConfigurationMetadataLoader {
    protected static final String PATH = "META-INF/spring-autoconfigure-metadata.properties";
	static AutoConfigurationMetadata loadMetadata(ClassLoader classLoader) {
		return loadMetadata(classLoader, PATH);
	}

	static AutoConfigurationMetadata loadMetadata(ClassLoader classLoader, String path) {
		try {
			Enumeration<URL> urls = (classLoader != null) ? classLoader.getResources(path)
					: ClassLoader.getSystemResources(path);
			Properties properties = new Properties();
			while (urls.hasMoreElements()) {
				properties.putAll(PropertiesLoaderUtils.loadProperties(new UrlResource(urls.nextElement())));
			}
			return loadMetadata(properties);
		}
		catch (IOException ex) {
			throw new IllegalArgumentException("Unable to load @ConditionalOnClass location [" + path + "]", ex);
		}
	}
	···
}
複製代碼

AutoConfigurationMetadata是自動裝配元信息接口,信息配置在META-INF/spring-autoconfigure-metadata.properties中。app

2.1 獲取標註@EnableAutoConfiguration類的元信息

protected AnnotationAttributes getAttributes(AnnotationMetadata metadata) {
		String name = getAnnotationClass().getName();
		AnnotationAttributes attributes = AnnotationAttributes.fromMap(metadata.getAnnotationAttributes(name, true));
		Assert.notNull(attributes, () -> "No auto-configuration attributes found. Is " + metadata.getClassName()
				+ " annotated with " + ClassUtils.getShortName(name) + "?");
		return attributes;
	}
	
	protected Class<?> getAnnotationClass() {
	return EnableAutoConfiguration.class;
}
複製代碼

2.2 讀取META-INF/spring.factories下的資源

protected Class<?> getSpringFactoriesLoaderFactoryClass() {
	return EnableAutoConfiguration.class;
}
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;
}
複製代碼

該方法實際執行的是SpringFactoriesLoader#loadFactoryNames(Class,ClassLoader)方法:ide

public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";
	
public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {
	String factoryTypeName = factoryType.getName();
	return loadSpringFactories(classLoader).getOrDefault(factoryTypeName, Collections.emptyList());
}

private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
	MultiValueMap<String, String> result = cache.get(classLoader);
	if (result != null) {
		return result;
	}

	try {
		Enumeration<URL> urls = (classLoader != null ?
				classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
				ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
		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()) {
				String factoryTypeName = ((String) entry.getKey()).trim();
				for (String factoryImplementationName : StringUtils.commaDelimitedListToStringArray((String) entry.getValue())) {
					result.add(factoryTypeName, factoryImplementationName.trim());
				}
			}
		}
		cache.put(classLoader, result);
		return result;
	}
	catch (IOException ex) {
		throw new IllegalArgumentException("Unable to load factories from location [" +
				FACTORIES_RESOURCE_LOCATION + "]", ex);
	}
}
複製代碼

SpringFactoriesLoader是Spring的工廠機制的加載器,loadFactoryNames(Class,ClassLoader)加載原理以下:SpringFactoriesLoader是Spring的工廠機制的加載器,loadFactoryNames(Class,ClassLoader)加載原理以下:spring-boot

  1. 搜索ClassLoader下面全部的META-INF/spring.factories資源。
  2. 將一個或者多個META-INF/spring.factories資源內容做爲Properties文件讀取,合併爲一個key爲接口的全類名,Value是實現類全類名列表的Map,做爲loadSpringFactories(classLoader)方法的返回值。 參考META-INF/spring.factories
  3. 再從上一步返回的Map中查找並返回方法指定類名所映射的實現類全類名列表。

從圖片中能夠看出,上一章中自定義的starter的配置路徑也加載出來了。

2.3 放入LinkedHashSet去重

protected final <T> List<T> removeDuplicates(List<T> list) {
	return new ArrayList<>(new LinkedHashSet<>(list));
}
複製代碼

2.4 排除自動裝配組件

protected Set<String> getExclusions(AnnotationMetadata metadata, AnnotationAttributes attributes) {
	Set<String> excluded = new LinkedHashSet<>();
	excluded.addAll(asList(attributes, "exclude"));
	excluded.addAll(Arrays.asList(attributes.getStringArray("excludeName")));
	excluded.addAll(getExcludeAutoConfigurationsProperty());
	return excluded;
}

// 從外部化配置中找到spring.autoconfigure.exclude配置值
private List<String> getExcludeAutoConfigurationsProperty() {
	if (getEnvironment() instanceof ConfigurableEnvironment) {
		Binder binder = Binder.get(getEnvironment());
		return binder.bind(PROPERTY_NAME_AUTOCONFIGURE_EXCLUDE, String[].class).map(Arrays::asList)
				.orElse(Collections.emptyList());
	}
	String[] excludes = getEnvironment().getProperty(PROPERTY_NAME_AUTOCONFIGURE_EXCLUDE, String[].class);
	return (excludes != null) ? Arrays.asList(excludes) : Collections.emptyList();
}
複製代碼

將標註@EnableAutoConfiguration配置類註解屬性exclude和excludeName,以及spring.autoconfigure.exclude配置值累加到排除集合excluded。

2.5 檢查排除類名集合是否合法

private void checkExcludedClasses(List<String> configurations, Set<String> exclusions) {
		List<String> invalidExcludes = new ArrayList<>(exclusions.size());
		for (String exclusion : exclusions) {
			if (ClassUtils.isPresent(exclusion, getClass().getClassLoader()) && !configurations.contains(exclusion)) {
				invalidExcludes.add(exclusion);
			}
		}
		if (!invalidExcludes.isEmpty()) {
		    // 觸發排除類非法異常
			handleInvalidExcludes(invalidExcludes);
		}
	}
複製代碼

當排除類存在於當前的classLoader且不在自動候選名單configurations中時,handleInvalidExcludes(List)被執行,觸發排除類非法異常

2.6 將排除類集exclusions合從候選名單configurations中移除

configurations.removeAll(exclusions);
複製代碼

2.7 配合autoConfigurationMetadata對象執行過濾操做

private List<String> filter(List<String> configurations, AutoConfigurationMetadata autoConfigurationMetadata) {
		long startTime = System.nanoTime();
		String[] candidates = StringUtils.toStringArray(configurations);
		boolean[] skip = new boolean[candidates.length];
		boolean skipped = false;
		for (AutoConfigurationImportFilter filter : getAutoConfigurationImportFilters()) {
			invokeAwareMethods(filter);
			boolean[] match = filter.match(candidates, autoConfigurationMetadata);
			for (int i = 0; i < match.length; i++) {
				if (!match[i]) {
					skip[i] = true;
					candidates[i] = null;
					skipped = true;
				}
			}
		}
		if (!skipped) {
			return configurations;
		}
		List<String> result = new ArrayList<>(candidates.length);
		for (int i = 0; i < candidates.length; i++) {
			if (!skip[i]) {
				result.add(candidates[i]);
			}
		}
        ···
		return new ArrayList<>(result);
	}

	protected List<AutoConfigurationImportFilter> getAutoConfigurationImportFilters() {
		return SpringFactoriesLoader.loadFactories(AutoConfigurationImportFilter.class, this.beanClassLoader);
	}
複製代碼

其中AutoConfigurationImportFilter對象集合一樣被SpringFactoriesLoader加載,因此在META-INF/spring.factories中查找AutoConfigurationImportFilter,發現有三處聲明,即在org.springframework.boot:spring-boot-autoconfigure中:

# Auto Configuration Import Filters
org.springframework.boot.autoconfigure.AutoConfigurationImportFilter=\
org.springframework.boot.autoconfigure.condition.OnBeanCondition,\
org.springframework.boot.autoconfigure.condition.OnClassCondition,\
org.springframework.boot.autoconfigure.condition.OnWebApplicationCondition
複製代碼

不過此處的SpringFactoriesLoader#loadFactories(Class,ClassLoader)方法與以前的loadFactoryNames(Class,ClassLoader)不一樣,前者調用了後者

public static <T> List<T> loadFactories(Class<T> factoryType, @Nullable ClassLoader classLoader) {
		Assert.notNull(factoryType, "'factoryType' must not be null");
		ClassLoader classLoaderToUse = classLoader;
		if (classLoaderToUse == null) {
			classLoaderToUse = SpringFactoriesLoader.class.getClassLoader();
		}
		List<String> factoryImplementationNames = loadFactoryNames(factoryType, classLoaderToUse);
		if (logger.isTraceEnabled()) {
			logger.trace("Loaded [" + factoryType.getName() + "] names: " + factoryImplementationNames);
		}
		List<T> result = new ArrayList<>(factoryImplementationNames.size());
		for (String factoryImplementationName : factoryImplementationNames) {
		                // instantiateFactory()實例化方法
			result.add(instantiateFactory(factoryImplementationName, factoryType, classLoaderToUse));
		}
		AnnotationAwareOrderComparator.sort(result);
		return result;
	}
複製代碼

區別是前者獲取工廠類名單factoriesName後,逐一進行實例化。換言之,AutoConfigurationImportSelector#filter(List<String>,AutoConfigurationMetadata)方法其實是過濾META-INF/spring.factories資源中那些當前ClassLoader不存在的Class。

2.8 自動裝配事件

private void fireAutoConfigurationImportEvents(List<String> configurations, Set<String> exclusions) {
		List<AutoConfigurationImportListener> listeners = getAutoConfigurationImportListeners();
		if (!listeners.isEmpty()) {
			AutoConfigurationImportEvent event = new AutoConfigurationImportEvent(this, configurations, exclusions);
			for (AutoConfigurationImportListener listener : listeners) {
				invokeAwareMethods(listener);
				listener.onAutoConfigurationImportEvent(event);
			}
		}
	}

	protected List<AutoConfigurationImportListener> getAutoConfigurationImportListeners() {
		return SpringFactoriesLoader.loadFactories(AutoConfigurationImportListener.class, this.beanClassLoader);
	}
複製代碼

AutoConfigurationImportListener接口與傳統的Spring ApplicationListener不一樣,ApplicationListener與Spring應用上下文緊密相關,監聽ApplicationEvent。Spring事件下次單獨寫一篇文章。 而AutoConfigurationImportListener屬於自定義監聽器,僅監聽AutoConfigurationImportEvent事件,其實例被SpringFactoriesLoader加載,其中ConditionEvaluationReportAutoConfigurationImportListener就是內建實現,用於記錄自動裝配的條件評估詳情, 配置在META-INF/spring.factories資源中:

# Auto Configuration Import Listeners
org.springframework.boot.autoconfigure.AutoConfigurationImportListener=\
org.springframework.boot.autoconfigure.condition.ConditionEvaluationReportAutoConfigurationImportListener
複製代碼

參考書籍:《Spring Boot編程思想》

相關文章
相關標籤/搜索