在使用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方法复杂的实现,按照执行的顺序结合类和方法的字面意思,此处不妨大胆地猜测其中的含义:
- AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader.loadMetadata(this.beanClassLoader);似乎是加载自动装配的元信息,暂且不求甚解,存疑。
- AnnotationAttributes attributes = getAttributes(annotationMetadata);应该是获取@EnableAutoConfiguration标注类的元信息。
- List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);由于configurations作为selectImports方法的返回对象,而该方法返回的是导入类名集合,所以该对象应该是自动装配的候选类名集合。
- configurations = removeDuplicates(configurations);方法字面意思是移除重复对象,说明configurations存在重复的可能,至于为什么会重复?这是一个好问题。
- Set<String> exclusions = getExclusions(annotationMetadata, attributes);由于后续configurations将移除exclusions,所以exclusions应该是自动装配组件的排除名单。
- configurations = filter(configurations, autoConfigurationMetadata);经过去重和排除后的configurations再执行过滤操作,该步骤依赖于步骤1获取的AutoConfigurationMetadata对象,换言之,AutoConfigurationMetadata对象充当过滤条件。
- fireAutoConfigurationImportEvents(configurations, exclusions);在configurations对象返回前,貌似触发了一个自动装配的导入事件,事件可能包括候选的装配组件类名单和排除名单。
尽管目前无法证明以上猜测是否属实,不过可以基本定位@EnableAutoConfiguration中的疑惑:
- @EnableAutoConfiguration如何装配组件,以及装配哪些组件?可由getCandidateConfigurations方法解答。
- @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)方法加载原理如下:
- 搜索指定ClassLoader下所有的META-INF/spring.factories资源内容(可存在多个)
- 将一个或多个META-INF/spring.factories资源内容作为Properties文件读取,合并为一个Key为接口的全类名,Value是实现类全类名列表的Map,作为loadSpringFactories方法的返回值。
- 再从上一步返回的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的流程为:
- 通过SpringFactoriesLoader#loadFactoryNames方法读取所有META-INF/spring.factories资源中@EnableAutoConfiguration所关联的自动装配Class集合。
- 读取当前配置类所标注的@EnableAutoConfiguration属性exclude和excludeName,并与spring.autoconfigure.exclude配置属性合并为自动装配Class排除集合。
- 检查自动装配Class排除集合是否合法。
- 排除候选自动装配Class集合中的排除名单。
- 再次过滤候选自动装配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源码分析
本文详细探讨了Spring Boot的自动装配机制,从@EnableAutoConfiguration的使用到自动装配组件的选择、排除和过滤过程。SpringFactoriesLoader加载META-INF/spring.factories资源中的自动装配类,并通过AutoConfigurationImportSelector的selectImports方法进行处理。排除机制通过exclude和excludeName属性以及外部配置spring.autoconfigure.exclude实现。自动装配组件的过滤依赖于AutoConfigurationImportFilter,如OnBeanCondition和OnClassCondition,基于条件判断类是否存在。最后,通过AutoConfigurationImportListener监听自动装配事件,提供排序机制如@AutoConfigureOrder、@AutoConfigureBefore和@AutoConfigureAfter。整个过程确保了Spring Boot应用的组件按需、有序地自动装配。
1054

被折叠的 条评论
为什么被折叠?



