再说Spring Boot的自动装配原理

导语

由于疫情原因,基本上大家过了半个暑假了,过年在家的时候,感觉貌似很少有人去写技术文章,于是我也随了大流,其实更多的原因是家里比较忙,最近忙完手头的一些事情,想着要继续回来写点东西,考虑了一下还是继续接着之前的那篇文章来写吧!

前面的一篇说说Spring Boot(Spring)的自动装配机制文章中,简单大概笼统的说了一下Spring Boot的自动装配机制。其间应该也有一些错讹之处,不过那篇文章只是作为一个比较概括性的讲述。

那么本篇文章将会,来继续说说Spring Boot的自动装配这一块的原理。其实也就是对上一篇文章的有些地方做一个比较详细的讲述。那么就开始吧!

一、理解Spring Boot自动装配

在前一篇文章里,我们说到了Spring Boot中自动装配是从注解@SpringBootApplication开始的,但是这个注解是一个组合注解。它里面包含了几个注解,如:@SpringBootConfiguration、 @EnableAutoConfiguration 、@ComponentScan等,但是如果细心的朋友,如果尝试过使用@SpringBootApplication @EnableAutoConfiguration 去注解启动类,然后你会发现其实@EnableAutoConfiguration 也是可以激活Spring Boot的自动装配特性的,这就意味着@SpringBootApplication 它并不是Spring Boot自动装配的必须注解,它所起到的作用就是见多注解所带来的的配置成

二、简述Spring Boot的自动装配入口

前面我们说了@SpringBootApplication 注解不是Spring Boot激活自动装配特性的必需注解,因此这核心的任务就落到@EnableAutoConfiguration 的肩上了。了解Spring历史的同学,知道“@Enable”是Spring 3.x版本就开始具有的注解了,它的核心其实就是Spring为了简化装配的步骤。这里先不说它,后面有机会再写篇文章来说说@Enable。

既然提到了@Enable,那么这里就必然摆脱不了@EnableAutoConfiguration 注解了,因为它是自动装配的核心。而@EnableAutoConfiguration 是“@Import”: ImportSelector或ImportBeanDefinitionRegistrar的实现类,我们类结合代码看看:

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

	......
}

上面的AutoConfigurationImportSelector就是@EnableAutoConfiguration 的“@Import”的DeferredImportSelector的实现类,而DeferredImportSelector又是ImportSelector类的子接口,因此组件的自动装配逻辑基本上都在selectImports(AnnotationMetadata annotationMetadata)方法里来进行实现的:

	@Override
	public String[] selectImports(AnnotationMetadata annotationMetadata) {
		if (!isEnabled(annotationMetadata)) {
			return NO_IMPORTS;
		}
		AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader
				.loadMetadata(this.beanClassLoader);
		AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(autoConfigurationMetadata,
				annotationMetadata);
		return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
	}

	protected AutoConfigurationEntry getAutoConfigurationEntry(AutoConfigurationMetadata autoConfigurationMetadata,
			AnnotationMetadata annotationMetadata) {
		if (!isEnabled(annotationMetadata)) {
			return EMPTY_ENTRY;
		}
		AnnotationAttributes attributes = getAttributes(annotationMetadata);
		List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
		configurations = removeDuplicates(configurations);
		Set<String> exclusions = getExclusions(annotationMetadata, attributes);
		checkExcludedClasses(configurations, exclusions);
		configurations.removeAll(exclusions);
		configurations = filter(configurations, autoConfigurationMetadata);
		fireAutoConfigurationImportEvents(configurations, exclusions);
		return new AutoConfigurationEntry(configurations, exclusions);
	}

以上的代码大概的逻辑如下:

1.loadMetadata(this.beanClassLoader):加载自动装配的元信息。

2.getAttributes(annotationMetadata):获取@EnableAutoConfiguration 标注类的元信息。

3.List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes):这里configurations是返回对象,里面的信息是自动装配的候选类名集合。

4.configurations = removeDuplicates(configurations):移除重复对象,说明configurations中可能存在重复。

5.Set<String> exclusions = getExclusions(annotationMetadata, attributes):从configurations移除exclusions,因此exclusions应该是自动装配所需要排除的名单。

6.filter(configurations, autoConfigurationMetadata):经过去重和排除之后的configurations对象还要进行过滤操作,而这里的过滤条件是AutoConfigurationMetadata对象。

7.fireAutoConfigurationImportEvents(configurations, exclusions):在configurations对象返回之前,需要触发自动装配的导入事件。

在这些步骤里面是有一些疑问的,譬如@EnableAutoConfiguration 是如何装配组件的?然后它装配了哪些组件?再然后就是如何排除某些组件的自动装配等。带着这些疑问来继续看代码。

 

三、@EnableAutoConfiguration 读取候选的装配组件

刚才前面说到的@EnableAutoConfiguration 是如何装配组件的,这里需要从getCandidateConfigurations(annotationMetadata, attributes)方法来入手:

protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
	List<String> configurations = SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(),
			getBeanClassLoader());
	......        
	return configurations;
}

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

上面代码的核心逻辑是:loadFactoryNames这个方法:

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);
	}
}

从上面的代码中在loadSpringFactories方法中,首先判断缓存中是否存在result,如果存在则直接返回,不存在才会进行下面一些类加载的操作,在这里我们看到一个常量:FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories",看到spring.factories是不是感觉很面熟。

那么就简单说下Spring Framework工厂机制加载器loadFactoryNames方法的基本加载原理:

1.搜索指定classLoader路径下的的所有的META-INF/spring.factories资源内容(存在多个)。

2.将一个或者多个META-INF/spring.factories资源的内容作为Properties文件来读取,合并成一个Key为接口的全名类,Value为实现类全类名的一个集合,来作为loadSpringFactories方法的返回值。

刚说到spring.factories这个大家应该很面熟,这里我们以spring-boot-autoconfigure-2.2.4.RELEASE.jar包下面的META-INF文件夹下的spring.factories文件,在这个文件内有配置项的信息,这里给大家截个图看看:

四、@EnableAutoConfiguration 排除自动装配组件

在执行getExclusions(annotationMetadata, attributes)方法后,将会得到一个需要进行排除的Class名单:

private static final String PROPERTY_NAME_AUTOCONFIGURE_EXCLUDE = "spring.autoconfigure.exclude";

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;
}

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的配置值累加到集合exclude。然后,再检查配出类名的集合是否是合法的:

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);
	}
}

protected void handleInvalidExcludes(List<String> invalidExcludes) {
	StringBuilder message = new StringBuilder();
	for (String exclude : invalidExcludes) {
		message.append("\t- ").append(exclude).append(String.format("%n"));
	}
	throw new IllegalStateException(String.format(
			"The following classes could not be excluded because they are not auto-configuration classes:%n%s",
			message));
}

上面的代码中,当在排除存在于当前ClassLoader且不在自动装配候选名单里的时候,会执行handleInvalidExcludes,触发排除类的非法异常,然后接着这个排除集合exclusions从候选自动装配的Class名单configurations中移除:configurations.removeAll(exclusions),再然后就是计算后的configurations还不是最终所需要的,那么就还要进行再次过滤。

 

五、@EnableAutoConfiguration 过滤自动装配组件

前面刚说到,在排除后所获取到的类名单,若还不符合最后自动化装配的需求,就还需要再进行一次过滤,这是需要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]);
		}
	}
	if (logger.isTraceEnabled()) {
		int numberFiltered = configurations.size() - result.size();
		logger.trace("Filtered " + numberFiltered + " auto configuration class in "
				+ TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startTime) + " ms");
	}
	return new ArrayList<>(result);
}

在上面的代码中,AutoConfigurationImportFilter还是会被SpringFactoriesLoader记载,因此查找AutoConfigurationImportFilter在所有的"META-INF/spring.factories"资源中的配置。但这在Spring  Boot中仅有一处申明,那就是我们上面图片中可看到的:

# 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

这里的loadFactories和前面的loadFactoryNames方法有着一些区别,因为是在逻辑上是loadFactories调用loadFactoryNames,因此在loadFactoryNames这部分的逻辑是一致的。他们主要的区别是在于loadFactories获取工厂类的名单后,会逐一的进行类加载。这些还必须是参数factoryClass的子类,并被实例化以排序。也就是说"META-INF/spring.factories"中所申明的OnClassCondition对也是AutoConfigurationImportFilter的实现类,这就说明了filter(configurations, autoConfigurationMetadata)方法的实际作用是要过滤META-INF/spring.factories资源中那些当前ClassLoader中不存在的Class。

 

总结

本篇文章中主要说了如下几方面内容:简单的概括了下Spring Boot自动装配、简述Spring Boot的自动装配入口、@EnableAutoConfiguration 读取候选的装配组件、@EnableAutoConfiguration 排除自动装配组件、@EnableAutoConfiguration 过滤自动装配组件。

Guess you like

Origin blog.csdn.net/zfy163520/article/details/104345414