SpringBoot自动装配

本文详细探讨了Spring Boot的自动装配机制,从@EnableAutoConfiguration的使用到自动装配组件的选择、排除和过滤过程。SpringFactoriesLoader加载META-INF/spring.factories资源中的自动装配类,并通过AutoConfigurationImportSelector的selectImports方法进行处理。排除机制通过exclude和excludeName属性以及外部配置spring.autoconfigure.exclude实现。自动装配组件的过滤依赖于AutoConfigurationImportFilter,如OnBeanCondition和OnClassCondition,基于条件判断类是否存在。最后,通过AutoConfigurationImportListener监听自动装配事件,提供排序机制如@AutoConfigureOrder、@AutoConfigureBefore和@AutoConfigureAfter。整个过程确保了Spring Boot应用的组件按需、有序地自动装配。
摘要由CSDN通过智能技术生成

在使用Spring Framework时,当@Component或@Configuration Class需要被配置时,应用需要借助@Import或@ComponentScan的能力,由于应用依赖JAR存在变化的可能,因此其中的@Component类所在的包路径也随之不确定,如果要实现当前应用所有组件自动装配,则@Import显然是无能为力的,开发人员自然会想到使用@ComponentScan扫描应用默认包路径,理论上默认包及其子包下的所有@Component类均会被@ComponentScan注册为Spring Bean,从而达到组件自动装配的目的,但是@ComponentScan(basePackages="")仅扫描当前应用所在的包及其子包而不是所有包。

1、理解Spring Boot自动装配

许多Spring-Boot开发者喜欢他们的应用程序使用自动配置、组件扫描,并且能够在他们的“应用程序类”上定义额外的配置。可以使用单个@SpringBootApplication注释来启用这三个功能,即:

  • @EnableAutoConfiguration:启用SpringBoot的自动配置机制
  • @ComponentScan:在应用程序所在的包上启用@ComponentScan
  • @Configuration:允许在上下文中注册额外的bean或导入其他配置类

其中@EnableAutoConfiguration用于激活Spring Boot自动装配的特性。按照命名规范和实现特点,@EnableAutoConfiguration也属于Spring Boot @Enable模块装配的实现:

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

通常Spring Boot应用引导类不会直接标注@EnableAutoConfiguration而是选择@SpringBootApplication来激活@EnableAutoConfiguration、@ComponentScan和@SpringBootConfiguration。作为Spring Boot最核心的@SpringBootApplication,其组合注解成员@SpringBootConfiguration与@Configuration无异:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Configuration
public @interface SpringBootConfiguration {
}

1.1、理解@EnableAutoConfiguration

Spring Boot自动装配尝试自动配置应用的组件,这些组件来自应用依赖的JAR,并且这样的装配是有前提条件的,可在@EnableAutoConfiguration和@SpringBootApplication中选择其一激活自动装配特性。

1.2、优雅地替换自动装配

Spring Boot自动装配并非是侵占性的,开发人员可在任意一处定义配置类,从而覆盖那些被自动装配的组件。如自定义的DataSource bean能够覆盖默认的嵌入式数据库的数据源bean,可将其解读为Spring Boot优先解析自定义配置类,并且内建的自动装配配置类实际上为默认的条件装配,即一旦应用存在自定义实现,则不再将它们装配。

1.3、失效自动装配

Spring Boot提供两种失效手段:

  • 代码配置方式
    • 配置类型安全的属性方法:@EnableAutoConfiguration.exclude()
    • 配置排除类名的属性方法:@EnableAutoConfiguration.excludeName()
  • 外部化配置方式
    • 配置属性:spring.autoconfigure.exclude

本质上Spring Boot失效自动装配是一种类似Spring Framework黑名单方式的条件装配,然而想要在Spring Framework实现该功能,要么阻断@Configuration Class BeanDefinition的注册,要么通过@Conditonal实现条件控制。前者的实现成本较高,后者对@Configuration Class存在注解侵入性。这种复杂的逻辑由官方来实现皆大欢喜,不过可以大胆猜测其中的实现,@EnableAutoConfiguration可通过ImportSelector实现,且exclude()和excludeName()属性可从其AnnotationMetadata对象中获取,那么@EnableAutoConfiguration所装配组件又从哪里获取呢?

2、Spring Boot自动装配原理

依照@Enable模块驱动设计模式,@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)方法中实现:

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

面对selectImports方法复杂的实现,按照执行的顺序结合类和方法的字面意思,此处不妨大胆地猜测其中的含义:

  1. AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader.loadMetadata(this.beanClassLoader);似乎是加载自动装配的元信息,暂且不求甚解,存疑。
  2. AnnotationAttributes attributes = getAttributes(annotationMetadata);应该是获取@EnableAutoConfiguration标注类的元信息。
  3. List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);由于configurations作为selectImports方法的返回对象,而该方法返回的是导入类名集合,所以该对象应该是自动装配的候选类名集合。
  4. configurations = removeDuplicates(configurations);方法字面意思是移除重复对象,说明configurations存在重复的可能,至于为什么会重复?这是一个好问题。
  5. Set<String> exclusions = getExclusions(annotationMetadata, attributes);由于后续configurations将移除exclusions,所以exclusions应该是自动装配组件的排除名单。
  6. configurations = filter(configurations, autoConfigurationMetadata);经过去重和排除后的configurations再执行过滤操作,该步骤依赖于步骤1获取的AutoConfigurationMetadata对象,换言之,AutoConfigurationMetadata对象充当过滤条件。
  7. fireAutoConfigurationImportEvents(configurations, exclusions);在configurations对象返回前,貌似触发了一个自动装配的导入事件,事件可能包括候选的装配组件类名单和排除名单。

尽管目前无法证明以上猜测是否属实,不过可以基本定位@EnableAutoConfiguration中的疑惑:

  1. @EnableAutoConfiguration如何装配组件,以及装配哪些组件?可由getCandidateConfigurations方法解答。
  2. @EnableAutoConfiguration如何排除某些组件的自动装配,以及与其配置手段是如何交互的?getExclusions方法可以解释。

不过还有一个更奇怪的问题,为什么应用自定义配置Class能够覆盖其自动装配Class?这个问题留到最后说明。

2.1、@EnableAutoConfiguration读取候选装配组件

首先分析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;
	}

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

该方法实际执行的是SpringFactoriesLoader.loadFactoryNames方法:

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()) {
					String factoryClassName = ((String) entry.getKey()).trim();
					for (String factoryName : StringUtils.commaDelimitedListToStringArray((String) entry.getValue())) {
						result.add(factoryClassName, factoryName.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 Framework工厂机制的加载器,loadFactoryNames(Class,ClassLoader)方法加载原理如下:

  1. 搜索指定ClassLoader下所有的META-INF/spring.factories资源内容(可存在多个)
  2. 将一个或多个META-INF/spring.factories资源内容作为Properties文件读取,合并为一个Key为接口的全类名,Value是实现类全类名列表的Map,作为loadSpringFactories方法的返回值。
  3. 再从上一步返回的Map中查找并返回方法指定类名所映射的实现类全类名列表。

在Spring Boot2.1.9.RELEASE为例,框架内部JAR文件默认引入多个META-INF/spring.factories资源文件,其中包含@EnableAutoConfiguration配置信息的JAR文件有:

  • spring-boot-autoconfigure
  • spring-boot-actuator-autoconfigure
  • spring-boot-devtools

由于@EnableAutoConfiguration配置可能存在自动装配组件类名重复定义的情况,当getCandidateConfigurations方法获取所有的候选类集合名后立即执行removeDuplicates方法,利用Set内元素不可重复达到去重的目的:

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

当自动装配组件类被SpringFactoriesLoader加载并去重后接下来执行排除操作。

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

当getExclusions方法执行后,程序将获得一个自动装配Class的排除列表:

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配置类的注解属性excluded和excludeName,以及将spring.autoconfigure.exclude配置值累加至排除集合excluded。随后检查排除类名集合是否合法:

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并非最终自动装配Class名单,还需再次过滤。

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

移除排除类名单后的configurations配合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);
	}

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

其中AutoConfigurationImportFilter对象集合同样被SpringFactoriesLoader加载,故查找AutoConfigurationImportFilter在所有META-INF/spring.factories资源中的配置。Spring Boot框架默认仅有一处声明,即在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

OnBeanCondition、OnClassCondition、OnWebApplicationCondition都是AutoConfigurationImportFilter的实现类,AutoConfigurationImportSelector#filter方法的实际作用是过滤META-INF/spring.factories资源中那些不符合这三个条件的Class。
不过方法参数所依赖的AutoConfigurationMetadata对象又是如何而来的呢?需要再次分析AutoConfigurationMetadataLoader#loadMetadata(ClassLoader)方法:

protected static final String PATH = "META-INF/" + "spring-autoconfigure-metadata.properties";

	private AutoConfigurationMetadataLoader() {
	}

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

	static AutoConfigurationMetadata loadMetadata(Properties properties) {
		return new PropertiesAutoConfigurationMetadata(properties);
	}

AutoConfigurationMetadataLoader是AutoConfigurationMetadata的加载器,AutoConfigurationMetadata是Spring Boot1.5开始引入的自动装配元信息接口,这些信息配置于Properties格式的资源META-INF/spring-autoconfigure-metadata.properties中,框架内部仅存在基于Properties文件格式的实现PropertiesAutoConfigurationMetadata被AutoConfigurationMetadataLoader初始化。AutoConfigurationMetadata接口支持多种数据类型的方法,OnClassCondition作为AutoConfigurationImportFilter的实现类,它依赖于AutoConfigurationMetadata#get方法获取自动装配Class的“ConditionalOnClass”元信息:

@Override
	protected final ConditionOutcome[] getOutcomes(String[] autoConfigurationClasses,
			AutoConfigurationMetadata autoConfigurationMetadata) {
		// Split the work and perform half in a background thread. Using a single
		// additional thread seems to offer the best performance. More threads make
		// things worse
		int split = autoConfigurationClasses.length / 2;
		OutcomesResolver firstHalfResolver = createOutcomesResolver(autoConfigurationClasses, 0, split,
				autoConfigurationMetadata);
		OutcomesResolver secondHalfResolver = new StandardOutcomesResolver(autoConfigurationClasses, split,
				autoConfigurationClasses.length, autoConfigurationMetadata, getBeanClassLoader());
		ConditionOutcome[] secondHalf = secondHalfResolver.resolveOutcomes();
		ConditionOutcome[] firstHalf = firstHalfResolver.resolveOutcomes();
		ConditionOutcome[] outcomes = new ConditionOutcome[autoConfigurationClasses.length];
		System.arraycopy(firstHalf, 0, outcomes, 0, firstHalf.length);
		System.arraycopy(secondHalf, 0, outcomes, split, secondHalf.length);
		return outcomes;
	}

	private final class StandardOutcomesResolver implements OutcomesResolver {

		private final String[] autoConfigurationClasses;

		private final int start;

		private final int end;

		private final AutoConfigurationMetadata autoConfigurationMetadata;

		private final ClassLoader beanClassLoader;

		private StandardOutcomesResolver(String[] autoConfigurationClasses, int start, int end,
				AutoConfigurationMetadata autoConfigurationMetadata, ClassLoader beanClassLoader) {
			this.autoConfigurationClasses = autoConfigurationClasses;
			this.start = start;
			this.end = end;
			this.autoConfigurationMetadata = autoConfigurationMetadata;
			this.beanClassLoader = beanClassLoader;
		}

		@Override
		public ConditionOutcome[] resolveOutcomes() {
			return getOutcomes(this.autoConfigurationClasses, this.start, this.end, this.autoConfigurationMetadata);
		}

		private ConditionOutcome[] getOutcomes(String[] autoConfigurationClasses, int start, int end,
				AutoConfigurationMetadata autoConfigurationMetadata) {
			ConditionOutcome[] outcomes = new ConditionOutcome[end - start];
			for (int i = start; i < end; i++) {
				String autoConfigurationClass = autoConfigurationClasses[i];
				if (autoConfigurationClass != null) {
					String candidates = autoConfigurationMetadata.get(autoConfigurationClass, "ConditionalOnClass");
					if (candidates != null) {
						outcomes[i - start] = getOutcome(candidates);
					}
				}
			}
			return outcomes;
		}

		private ConditionOutcome getOutcome(String candidates) {
			try {
				if (!candidates.contains(",")) {
					return getOutcome(candidates, this.beanClassLoader);
				}
				for (String candidate : StringUtils.commaDelimitedListToStringArray(candidates)) {
					ConditionOutcome outcome = getOutcome(candidate, this.beanClassLoader);
					if (outcome != null) {
						return outcome;
					}
				}
			}
			catch (Exception ex) {
				// We'll get another chance later
			}
			return null;
		}

		private ConditionOutcome getOutcome(String className, ClassLoader classLoader) {
			if (ClassNameFilter.MISSING.matches(className, classLoader)) {
				return ConditionOutcome.noMatch(ConditionMessage.forCondition(ConditionalOnClass.class)
						.didNotFind("required class").items(Style.QUOTE, className));
			}
			return null;
		}

	}

根据以上方法的逻辑,自动装配Class的集合autoConfigurationClasses迭代地调用AutoConfigurationMetadata#get方法获取他们的“ConditionalOnClass”元信息,以JmxAutoConfiguration为例,其“ConditionalOnClass”配置如下:

org.springframework.boot.autoconfigure.jmx.JmxAutoConfiguration.ConditionalOnClass=org.springframework.jmx.export.MBeanExporter

当org.springframework.jmx.export.MBeanExporter作为AutoConfigurationMetadata#get方法返回值时,再直接使用getOutcome方法计算匹配结果,最终判断的标准由MatchType.MISSING#matches方法决定:

protected enum ClassNameFilter {

		PRESENT {

			@Override
			public boolean matches(String className, ClassLoader classLoader) {
				return isPresent(className, classLoader);
			}

		},

		MISSING {

			@Override
			public boolean matches(String className, ClassLoader classLoader) {
				return !isPresent(className, classLoader);
			}

		};

		public abstract boolean matches(String className, ClassLoader classLoader);

		public static boolean isPresent(String className, ClassLoader classLoader) {
			if (classLoader == null) {
				classLoader = ClassUtils.getDefaultClassLoader();
			}
			try {
				forName(className, classLoader);
				return true;
			}
			catch (Throwable ex) {
				return false;
			}
		}

		private static Class<?> forName(String className, ClassLoader classLoader) throws ClassNotFoundException {
			if (classLoader != null) {
				return classLoader.loadClass(className);
			}
			return Class.forName(className);
		}

	}

总之JmxAutoConfiguration是否能够自动装配取决于其“ConditionalOnClass”关联的org.springframework.jmx.export.MBeanExporter类是否存在,从而帮助AutoConfigurationImportSelector#filter方法过滤那些类依赖不满足的自动装配Class。如此设计相对复杂,当然也有好处,常规的@ConditionalOnClass判断需要依赖自动装配Class必须被ClassLoader提前装载,然后解析其注解元信息,从而根据依赖类是否存在来判断装配与否,同时Spring应用上下文处理@Conditional的时机较晚。然而通过读取META-INF/spring-autoconfigure-metadata.properties资源的“ConditionalOnClass”配置信息,并判断其依赖类的存在性,不但实现的逻辑直接,而且减少了自动装配的计算时间。

总而言之,AutoConfigurationImportSelector读取自动装配Class的流程为:

  1. 通过SpringFactoriesLoader#loadFactoryNames方法读取所有META-INF/spring.factories资源中@EnableAutoConfiguration所关联的自动装配Class集合。
  2. 读取当前配置类所标注的@EnableAutoConfiguration属性exclude和excludeName,并与spring.autoconfigure.exclude配置属性合并为自动装配Class排除集合。
  3. 检查自动装配Class排除集合是否合法。
  4. 排除候选自动装配Class集合中的排除名单。
  5. 再次过滤候选自动装配Class集合中Class不存在的成员。

当自动装配Class读取完毕后,fireAutoConfigurationImportEvents方法被执行,可能触发了一个自动装配的导入事件,具体情况究竟如何?

2.4、@EnableAutoConfiguration自动装配事件

继续探讨fireAutoConfigurationImportEvents方法实现:

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

Spring Boot1.5开始引入AutoConfigurationImportListener接口,它有别于传统的Spring ApplicationListener的实现。ApplicationListener与Spring应用上下文ConfigurableApplicationListener紧密联系,监听Spring ApplicationEvent。而AutoConfigurationImportListener则是自定义Java EventListener实现,仅监听AutoConfigurationImportEvent,然而其实力同样被SpringFactoriesLoader加载,因此Spring Boot框架层面为开发人员提供了扩展途径。其中ConditionEvaluationReportAutoConfigurationImportListener就是内建实现,用于记录自动装配的条件评估详情,配置在META-INF/spring.factories资源中:

# Auto Configuration Import Listeners
org.springframework.boot.autoconfigure.AutoConfigurationImportListener=\
org.springframework.boot.autoconfigure.condition.ConditionEvaluationReportAutoConfigurationImportListener

2.5、@EnableAutoConfiguration自动装配生命周期

在前面章节中,讨论的议题有选择性地忽略了AutoConfigurationImportSelector的接口层次性,而是直奔@EnableAutoConfiguration自动装配的实现逻辑。前文提到:

AutoConfigurationImportSelector就是@EnableAutoConfiguration所“@Import”的DeferredImportSelector实现类,由于DeferredImportSelector作为ImportSelector的子接口,所以组件自动封装逻辑均在selectImports方法中实现。

这样的说法尽管结论没有问题,然而逻辑却不够严谨。Spring Framework4.0开始引入DeferredImportSelector接口,从字面意义上分析,DeferredImportSelector可理解为延迟的ImportSelector。

DeferredImportSelector作为ImportSelector的变种,他在@Configuration Bean处理完毕后才运作。它在@Conditional场景中尤其有用,同时该实现类可通过Ordered接口或标注@Order的方式调整其优先执行顺序,以AutoConfigurationImportSelector为例,其优先级接近最低:

	@Override
	public int getOrder() {
		return Ordered.LOWEST_PRECEDENCE - 1;
	}

ImportSelector的处理实际上在ConfigurationClassParser#processImports方法中执行,具体实现逻辑参考ConfigurationClassPostProcessor
回到Spring Boot自动装配场景,在Spring Boot2.X中AutoConfigurationImportSelector并没有继承DeferredImportSelector#getImportGroup()方法的默认实现:

@Override
		public void process(AnnotationMetadata annotationMetadata, DeferredImportSelector deferredImportSelector) {
			Assert.state(deferredImportSelector instanceof AutoConfigurationImportSelector,
					() -> String.format("Only %s implementations are supported, got %s",
							AutoConfigurationImportSelector.class.getSimpleName(),
							deferredImportSelector.getClass().getName()));
			AutoConfigurationEntry autoConfigurationEntry = ((AutoConfigurationImportSelector) deferredImportSelector)
					.getAutoConfigurationEntry(getAutoConfigurationMetadata(), annotationMetadata);
			this.autoConfigurationEntries.add(autoConfigurationEntry);
			for (String importClassName : autoConfigurationEntry.getConfigurations()) {
				this.entries.putIfAbsent(importClassName, annotationMetadata);
			}
		}

		@Override
		public Iterable<Entry> selectImports() {
			if (this.autoConfigurationEntries.isEmpty()) {
				return Collections.emptyList();
			}
			Set<String> allExclusions = this.autoConfigurationEntries.stream()
					.map(AutoConfigurationEntry::getExclusions).flatMap(Collection::stream).collect(Collectors.toSet());
			Set<String> processedConfigurations = this.autoConfigurationEntries.stream()
					.map(AutoConfigurationEntry::getConfigurations).flatMap(Collection::stream)
					.collect(Collectors.toCollection(LinkedHashSet::new));
			processedConfigurations.removeAll(allExclusions);

			return sortAutoConfigurations(processedConfigurations, getAutoConfigurationMetadata()).stream()
					.map((importClassName) -> new Entry(this.entries.get(importClassName), importClassName))
					.collect(Collectors.toList());
		}

AutoConfigurationImportSelector.AutoConfigurationGroup作为DeferredImportSelector.Group的实现,其process方法负责缓存导入类名和AnnotationMetadata的键值对象,而selectImports方法则用于将自动装配Class排序,然后被ConfigurationClassParser.processDeferredImportSelectors方法(在Spring5.1改为DeferredImportSelectorGroupingHandler#processGroupImports)用于最终自动装配Class的导入。

至此关于@EnableAutoConfiguration生命周期的讨论接近尾声,然而在AutoConfigurationGroup.selectImports()方法返回值上卖一个关子,该方法返回的是排序后自动装配Class的结果,而已知SpringFactoriesLoader#loadFactoryNames() API仅读取自动装配Class名单,并没有管理他们的顺序,自动装配Class排序的议题将在下一节讨论。

2.6、@EnableAutoConfiguration排序自动装配组件

Spring Boot提供了两种自动装配组件的拍苏手段:

  • 绝对自动装配顺序——@AutoConfigureOrder
  • 相对自动装配书序——@AutoConfigureBefore和@AutoConfigureAfter

其中@AutoConfigureOrder与Spring Framework @Order的语义相同。@AutoConfigureBefore和@AutoConfigureAfter提供相对于其他自动装配组件的顺序控制。以上三个注解的排序处理均在上一节讨论的AutoConfigurationGroup.selectImports()方法实现中:

		@Override
		public Iterable<Entry> selectImports() {
			if (this.autoConfigurationEntries.isEmpty()) {
				return Collections.emptyList();
			}
			Set<String> allExclusions = this.autoConfigurationEntries.stream()
					.map(AutoConfigurationEntry::getExclusions).flatMap(Collection::stream).collect(Collectors.toSet());
			Set<String> processedConfigurations = this.autoConfigurationEntries.stream()
					.map(AutoConfigurationEntry::getConfigurations).flatMap(Collection::stream)
					.collect(Collectors.toCollection(LinkedHashSet::new));
			processedConfigurations.removeAll(allExclusions);

			return sortAutoConfigurations(processedConfigurations, getAutoConfigurationMetadata()).stream()
					.map((importClassName) -> new Entry(this.entries.get(importClassName), importClassName))
					.collect(Collectors.toList());
		}

		private List<String> sortAutoConfigurations(Set<String> configurations,
				AutoConfigurationMetadata autoConfigurationMetadata) {
			return new AutoConfigurationSorter(getMetadataReaderFactory(), autoConfigurationMetadata)
					.getInPriorityOrder(configurations);
		}

其中核心排序处理落在sortAutoConfigurations()方法中,包括AutoConfigurationMetadata和AutoConfigurationSorter的交互。在前文"@EnableAutoConfiguration过滤自动装配组件"一节中,曾讨论AutoConfigurationMetadata是Spring Boot1.5开始引入的自动装配元信息接口,这些信息配置于Properties格式的资源META-INF/spring-autoconfigure-metadata.properties中,并且读取该资源的"ConditionalOnClass"配置元信息,判断依赖类存在性的方式,不但实现逻辑直接,而且减少了自动装配的计算时间。当然该资源文件还包括自动装配Class的AutoConfigureOrder、AutoConfigureBefore和AutoConfigureAfter的配置信息,如

org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration.AutoConfigureOrder=-2147483638
org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration.AutoConfigureAfter=org.springframework.boot.autoconfigure.web.servlet.DispatcherServletAutoConfiguration,org.springframework.boot.autoconfigure.task.TaskExecutionAutoConfiguration,org.springframework.boot.autoconfigure.validation.ValidationAutoConfiguration

因此META-INF/spring-autoconfigure-metadata.properties可认为是自动装配Class预处理元信息配置的资源。当该资源文件存在自动装配Class的注解元信息配置时,自动装配Class无须ClassLoader加载,即可得到所需的元信息,减少了运行时的计算消耗。所以在自动装配Class速度方面,Spring Boot1.5相较于之前版本是有提升的。

AutoConfigurationMetadata作为META-INF/spring-autoconfigure-metadata.properties资源的封装对象,再次在sortAutoConfigurations()方法中加载,它与MetadataReaderFactory对象同时作为AutoConfigurationSorter构造参数,辅助AutoConfigurationSorter#getInPriorityOrder()方法对自动装配Class集合进行排序:

	public List<String> getInPriorityOrder(Collection<String> classNames) {
		AutoConfigurationClasses classes = new AutoConfigurationClasses(this.metadataReaderFactory,
				this.autoConfigurationMetadata, classNames);
		List<String> orderedClassNames = new ArrayList<>(classNames);
		// Initially sort alphabetically
		Collections.sort(orderedClassNames);
		// Then sort by order
		orderedClassNames.sort((o1, o2) -> {
			int i1 = classes.get(o1).getOrder();
			int i2 = classes.get(o2).getOrder();
			return Integer.compare(i1, i2);
		});
		// Then respect @AutoConfigureBefore @AutoConfigureAfter
		orderedClassNames = sortByAnnotation(classes, orderedClassNames);
		return orderedClassNames;
	}

该方法排序自动装配Class的方式是按照字典顺序依次加载的,换言之,如果自动装配Class集合中未包含@AutoConfigureOrder等顺序注解,则他们是按照字母顺序依次加载的。随后进行的是@AutoConfigureOrder排序。当AutoConfigurationClasses构造时,将指定的自动装配Class集合逐一转换为AutoConfigurationClass对象,并形成与AutoConfigurationClasses多对一的关联关系。

	private static class AutoConfigurationClasses {

		private final Map<String, AutoConfigurationClass> classes = new HashMap<>();

		AutoConfigurationClasses(MetadataReaderFactory metadataReaderFactory,
				AutoConfigurationMetadata autoConfigurationMetadata, Collection<String> classNames) {
			addToClasses(metadataReaderFactory, autoConfigurationMetadata, classNames, true);
		}

		public Set<String> getAllNames() {
			return this.classes.keySet();
		}

		private void addToClasses(MetadataReaderFactory metadataReaderFactory,
				AutoConfigurationMetadata autoConfigurationMetadata, Collection<String> classNames, boolean required) {
			for (String className : classNames) {
				if (!this.classes.containsKey(className)) {
					AutoConfigurationClass autoConfigurationClass = new AutoConfigurationClass(className,
							metadataReaderFactory, autoConfigurationMetadata);
					boolean available = autoConfigurationClass.isAvailable();
					if (required || available) {
						this.classes.put(className, autoConfigurationClass);
					}
					if (available) {
						addToClasses(metadataReaderFactory, autoConfigurationMetadata,
								autoConfigurationClass.getBefore(), false);
						addToClasses(metadataReaderFactory, autoConfigurationMetadata,
								autoConfigurationClass.getAfter(), false);
					}
				}
			}
		}

		public AutoConfigurationClass get(String className) {
			return this.classes.get(className);
		}

		public Set<String> getClassesRequestedAfter(String className) {
			Set<String> classesRequestedAfter = new LinkedHashSet<>();
			classesRequestedAfter.addAll(get(className).getAfter());
			this.classes.forEach((name, autoConfigurationClass) -> {
				if (autoConfigurationClass.getBefore().contains(className)) {
					classesRequestedAfter.add(name);
				}
			});
			return classesRequestedAfter;
		}

	}

当AutoConfigurationClass与autoConfigurationClasses建立映射关系后,具体的@AutoConfigureOrder排序规则由AutoConfigurationClass#getOrder()方法决定:

		private int getOrder() {
			if (wasProcessed()) {
				return this.autoConfigurationMetadata.getInteger(this.className, "AutoConfigureOrder",
						AutoConfigureOrder.DEFAULT_ORDER);
			}
			Map<String, Object> attributes = getAnnotationMetadata()
					.getAnnotationAttributes(AutoConfigureOrder.class.getName());
			return (attributes != null) ? (Integer) attributes.get("value") : AutoConfigureOrder.DEFAULT_ORDER;
		}

		private boolean wasProcessed() {
			return (this.autoConfigurationMetadata != null
					&& this.autoConfigurationMetadata.wasProcessed(this.className));
		}

当getOrder()方法执行时,首先判断该wasProcessed()的结果,而该方法依赖于AutoConfigurationMetadata#wasProcessed()的结果,已知该方法取决于指定的自动装配Class是否在META-INF/spring-autoconfigure-metadata.properties资源中配置为属性名。假设当前自动装配类为WebMvcAutoConfiguration,那么wasProcessed()方法返回true,因为该类的配置存在:

org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration=

随后,getOrder()方法将读取自动装配类的AutoConfigureOrder的配置值,如果不存在,则使用默认值AutoConfigureOrder.DEFAULT_ORDER,此处仍以WebMvcAutoConfiguration为例,它的配置值为:

org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration.AutoConfigureOrder=-2147483638

否则,当自动装配类出现在META-INF/spring-autoconfigure-metadata.properties资源中时,仍旧走ASM读取元信息的老路。

在@AutoConfigureOrder绝对顺序排序之后,再进入对@AutoConfigureBefore和@AutoConfigureAfter的排序过程,即AutoConfigurationSorter#sortByAnnotation方法:

	private List<String> sortByAnnotation(AutoConfigurationClasses classes, List<String> classNames) {
		List<String> toSort = new ArrayList<>(classNames);
		toSort.addAll(classes.getAllNames());
		Set<String> sorted = new LinkedHashSet<>();
		Set<String> processing = new LinkedHashSet<>();
		while (!toSort.isEmpty()) {
			doSortByAfterAnnotation(classes, toSort, sorted, processing, null);
		}
		sorted.retainAll(classNames);
		return new ArrayList<>(sorted);
	}

	private void doSortByAfterAnnotation(AutoConfigurationClasses classes, List<String> toSort, Set<String> sorted,
			Set<String> processing, String current) {
		if (current == null) {
			current = toSort.remove(0);
		}
		processing.add(current);
		for (String after : classes.getClassesRequestedAfter(current)) {
			Assert.state(!processing.contains(after),
					"AutoConfigure cycle detected between " + current + " and " + after);
			if (!sorted.contains(after) && toSort.contains(after)) {
				doSortByAfterAnnotation(classes, toSort, sorted, processing, after);
			}
		}
		processing.remove(current);
		sorted.add(current);
	}

至此之前的疑惑被一一解答,不过@EnableAutoConfiguration除了利用@AutoConfigurationImportSelector自动装配Class,它还将标注类所在的package添加至BasePackages中,为后续扫描组件提供BasePackages数据来源,如JPA Entity扫描,这将是下一节所讨论的范围。

2.7、@EnableAutoConfiguration自动装配BasePackages

从Spring Boot1.4开始,@EnableAutoConfiguration元标注新注解@AutoConfigurationPackage:

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

}

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

}

元标注@AutoConfigurationPackage “@Import” AutoConfigurationPackages.Registrar,ConfigurationClassPostProcessor提供递归处理配置Class和元注解的能力,所以嵌套类AutoConfigurationPackages.Registrar是自动装配BasePackages的核心实现:

	static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports {

		@Override
		public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
			register(registry, new PackageImport(metadata).getPackageName());
		}
	}

	public static void register(BeanDefinitionRegistry registry, String... packageNames) {
		if (registry.containsBeanDefinition(BEAN)) {
			BeanDefinition beanDefinition = registry.getBeanDefinition(BEAN);
			ConstructorArgumentValues constructorArguments = beanDefinition.getConstructorArgumentValues();
			constructorArguments.addIndexedArgumentValue(0, addBasePackages(constructorArguments, packageNames));
		}
		else {
			GenericBeanDefinition beanDefinition = new GenericBeanDefinition();
			beanDefinition.setBeanClass(BasePackages.class);
			beanDefinition.getConstructorArgumentValues().addIndexedArgumentValue(0, packageNames);
			beanDefinition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
			registry.registerBeanDefinition(BEAN, beanDefinition);
		}
	}

AutoConfigurationPackages.Registrar作为ImportBeanDefinitionRegistrar的实现,通过AutoConfigurationPackages#register方法注册当前标注类所在package。尽管@AutoConfigurationPackage鲜有直接标注在配置Class标注的场景,不过不排除@EnableAutoConfiguration被不同的配置Class标注的场景,所以AutoConfigurationPackages.Registrar可能存在多次被“@Import"的可能,当第一个被”@Import"时,BasePackages BeanDefinition注册到Spring应用上下文中,后续的@Import操作将调整该BeanDefinition的构造器参数元信息ConstructorArgumentValues,即添加当前标注类所在的package到已有集合中。

	static final class BasePackages {

		private final List<String> packages;

		private boolean loggedBasePackageInfo;

		BasePackages(String... names) {
			List<String> packages = new ArrayList<>();
			for (String name : names) {
				if (StringUtils.hasText(name)) {
					packages.add(name);
				}
			}
			this.packages = packages;
		}

		public List<String> get() {
			return this.packages;
		}

	}

当第一个ConfigurationClass被AutoConfigurationPackages.Registrar首次执行时,BasePackages被注册为Spring Bean,其名称为“org.springframework.boot.autoconfigure.AutoConfigurationPackages”。
随后他将当前标注类所在的package作为BasePackages构造器的首参。由于AutoConfigurationPackages.Registrar#registerBeanDefinitions方法的执行处在Bean注册阶段,其BeanDefinition拥有调整的机会。当方法再度执行时,先读取BeanDefinition,然后获取其构造器参数元信息ConstructorArgumentValues,将本次的标注类所在的pakcage追加至其中。当BasePackages Bean初始化后,关联的packages可由其get()方法获得,为工具方法AutoConfigurationPackages#get()提供数据来源:

	public static List<String> get(BeanFactory beanFactory) {
		try {
			return beanFactory.getBean(BEAN, BasePackages.class).get();
		}
		catch (NoSuchBeanDefinitionException ex) {
			throw new IllegalStateException("Unable to retrieve @EnableAutoConfiguration base packages");
		}
	}

该方法被多处使用,如JPA中:
在这里插入图片描述
按照@EnableAutoConfiguration规约的特性,他将自动装配META-INF/spring.factories资源中所声明的配置类,这也意味着开发人员能够自定义自动装配实现。那么如何专业化地实现自动装配不失为一个大学问。同时不经意地发现Spring Boot内建的自动装配组件大量地使用Spring Boot @Conditional扩展注解,那么开发人员应该如何合理地复用已有实现则又是一大学问。

由于@EnableAutoConfiguration基于SpringFactoriesLoader#loadFactoryNames API实现自动装配Class名的导入,未对其配置Class做优先级排序,如果加入配置Class之间存在初始化前后依赖关系,那么又该如何实现呢? 带着这些疑问进入“自定义Spring Boot自动装配”的讨论。

参考《Spring Boot编程思想》
ConfigurationClassPostProcessor源码分析

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值