Springboot自動配置源碼閱讀

1. MAVEN引用

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-parent</artifactId>
		<version>2.0.1.RELEASE</version>
		<relativePath />
	</parent>
	<groupId>com.example</groupId>
	<artifactId>easy-demo</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<name>easy-demo</name>
	<description>Demo project for Spring Boot</description>

	<properties>
		<java.version>1.8</java.version>
	</properties>

	<dependencies>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-web</artifactId>
		</dependency>
	</dependencies>

</project>

2. 閱讀源碼

@SpringBootApplication 註解部分源碼如下:

@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 {

}

可以看到@SpringBootApplication註解本身是個組合註解,與自動配置相關的是@EnableAutoConfiguration註解,部分源碼:

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

}

需要關注@EnableAutoConfiguration註解的@Import註解,用來將導入並注入Spring容器成爲Bean對象。源碼如下:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Import {

	/**
	 * {@link Configuration}, {@link ImportSelector}, {@link ImportBeanDefinitionRegistrar}
	 * or regular component classes to import.
	 */
	Class<?>[] value();

}

@Import註解支持導入3中類型的類:@Configuration註解聲明的配置類,ImportSelector接口實現類,ImportBeanDefinitionRegistrar接口的子類,具體用法可以參考這篇博客:spring註解之@Import註解的三種使用方式

回到正題,@EnableAutoConfiguration註解上聲明的@Import採用的是ImportSelector接口實現類的方式,下面是AutoConfigurationImportSelector類的源碼中的繼承關係:

public class AutoConfigurationImportSelector
		implements DeferredImportSelector, BeanClassLoaderAware, ResourceLoaderAware,
		BeanFactoryAware, EnvironmentAware, Ordered {

}

我們需要關注ImportSelector接口相關的代碼實現,所以先看ImportSelector接口的部分源碼:

public interface DeferredImportSelector extends ImportSelector {

	/**
	 * Return a specific import group or {@code null} if no grouping is required.
	 * @return the import group class or {@code null}
	 */
	@Nullable
	default Class<? extends Group> getImportGroup() {
		return null;
	}
}

AutoConfigurationImportSelector類中DeferredImportSelector接口相關的代碼實現:

	@Override
	public Class<? extends Group> getImportGroup() {
		return AutoConfigurationGroup.class;
	}

再來看看DeferredImportSelector接口的父接口ImportSelector接口源碼:

public interface ImportSelector {

	/**
	 * Select and return the names of which class(es) should be imported based on
	 * the {@link AnnotationMetadata} of the importing @{@link Configuration} class.
	 */
	String[] selectImports(AnnotationMetadata importingClassMetadata);

}

AutoConfigurationImportSelector類中ImportSelector接口相關的代碼實現:

	@Override
	public String[] selectImports(AnnotationMetadata annotationMetadata) {
		if (!isEnabled(annotationMetadata)) {  //判斷是否啓用自動配置
			return NO_IMPORTS;
		}
		AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader
				.loadMetadata(this.beanClassLoader); //加載META-INF/spring-autoconfigure-metadata.properties文件
		AnnotationAttributes attributes = getAttributes(annotationMetadata);
		List<String> configurations = getCandidateConfigurations(annotationMetadata,
				attributes);//加載META-INF/spring.factories文件
		configurations = removeDuplicates(configurations);//需要自動配置的項目去重
		Set<String> exclusions = getExclusions(annotationMetadata, attributes); //計算不需要自動配置的項目列表
		checkExcludedClasses(configurations, exclusions); //檢查需要排除自動配置的項目
		configurations.removeAll(exclusions);//移除不需要自動配置的項目
		configurations = filter(configurations, autoConfigurationMetadata);
		fireAutoConfigurationImportEvents(configurations, exclusions); //執行自動配置
		return StringUtils.toStringArray(configurations);
	}

需要重點關注getCandidateConfigurations(annotationMetadata,attributes);這行代碼,它負責加載META-INF/spring.factories文件。
源碼如下:

	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;
	}
	public static List<String> loadFactoryNames(Class<?> factoryClass, @Nullable ClassLoader classLoader) {
		String factoryClassName = factoryClass.getName();
		return loadSpringFactories(classLoader).getOrDefault(factoryClassName, 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()) {
					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);
		}
	}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章