Spring Boot Starter的自动配置(Auto-configuration)是个很强大的东东,也是重要且巧妙的设计。不过不把它的原理搞清楚,在判断哪些自动配置会被启用时,会摸不着头脑。下面就从源码的角度来一探究竟。
Auto-configuration和Spring的Java Configuration不是一回事哦。具体可看Spring Boot文档中的定义。
16. Auto-configuration
49. Creating Your Own Auto-configuration
@SpringBootApplication
注解的定义如下:
// 前面略 @EnableAutoConfiguration @ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class), @Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) }) public @interface SpringBootApplication { 复制代码
首先看 @ComponentScan
部分,使用了一个 AutoConfigurationExcludeFilter
作为exclude filter,从字面上讲,就是包扫描排除掉自动配置的包。So,如果Spring判断某个类是自动配置类,则不会对其进行包扫描(以创建声明的Bean)。具体逻辑不再深究。
这里重点要讲的是 @EnableAutoConfiguration
这个注解:
// 省略不重要的内容 @Import(AutoConfigurationImportSelector.class) public @interface EnableAutoConfiguration { 复制代码
关注 @Import(AutoConfigurationImportSelector.class)
这句话。 @Import
我们都很熟悉了,它是XML配置中的 <import>
的替代。然而 AutoConfigurationImportSelector
这个类并没有被 @Configuration
修饰,不是个Java配置类。所以就很奇怪了。
看源码应该优先看注释。
我们来看看 @Import
的注释透露了什么:
Allows for importing @Configuration classes, ImportSelector and ImportBeanDefinitionRegistrar implementations, as well as regular component classes 复制代码
嘿!原来它不仅能导入Java配置类,还能处理 ImportSelector
和 ImportBeanDefinitionRegistrar
。
顺藤摸瓜,再看看 ImportSelector
接口:
// 省略不重要注释 /** * Interface that determine which @{@link Configuration} class(es) should be imported * based on a given selection criteria, usually one or more annotation attributes. */ 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); } 复制代码
所以,它是用来决定一个被 @Configuration
修饰了的Java配置类,应该被导入哪些配置的。依据是这个Java配置类的所有注解信息(保存在 AnnotationMetadata
里面)。
好,下一步,我们就来看看 AutoConfigurationImportSelector
如何实现。
此类对 ImportSelector
接口的实现如下:
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()); } 复制代码
其中的 getAutoConfigurationEntry
方法又调用了 getCandidateConfigurations
方法,该方法如下:
/** * Return the auto-configuration class names that should be considered. By default * this method will load candidates using {@link SpringFactoriesLoader} */ 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; } 复制代码
所以,到这里,会从包的 META-INF/spring.factories
文件中读取需要导入的自动配置类。更细节的代码不再往下贴了,具体可见 SpringFactoriesLoader
这个类。
接下来的问题是, selectImports
是被谁调用的呢?说实话,调用的链路太深了,很容易被绕晕。下面简单地描述一下主要流程。
作为一个Spring的PostProcessor,这个类的功能是处理有 @Configuration 修饰的Java配置类。关注其核心方法:
// 省略非关键代码 public void processConfigBeanDefinitions(BeanDefinitionRegistry registry) { ConfigurationClassParser parser = new ConfigurationClassParser( this.metadataReaderFactory, this.problemReporter, this.environment, this.resourceLoader, this.componentScanBeanNameGenerator, registry); parser.parse(candidates); parser.validate(); Set<ConfigurationClass> configClasses = new LinkedHashSet<>(parser.getConfigurationClasses()); // Read the model and create bean definitions based on its content if (this.reader == null) { this.reader = new ConfigurationClassBeanDefinitionReader( registry, this.sourceExtractor, this.resourceLoader, this.environment, this.importBeanNameGenerator, parser.getImportRegistry()); } this.reader.loadBeanDefinitions(configClasses); } 复制代码
关键逻辑就是调用 ConfigurationClassParser
类的 parse
方法,然后对解析到的所有 ConfigurationClass
,解析并加载内部定义的Bean。
parse()方法主要是调用 processConfigurationClass
方法。
// 省略非核心代码 protected void processConfigurationClass(ConfigurationClass configClass) throws IOException { if (this.conditionEvaluator.shouldSkip(configClass.getMetadata(), ConfigurationPhase.PARSE_CONFIGURATION)) { return; } // Recursively process the configuration class and its superclass hierarchy. SourceClass sourceClass = asSourceClass(configClass); do { sourceClass = doProcessConfigurationClass(configClass, sourceClass); } while (sourceClass != null); this.configurationClasses.put(configClass, configClass); } 复制代码
此方法做了三件事:
doProcessConfigurationClass() configurationClasses
而第二步的 doProcessConfigurationClass()
具体解析啥呢?其实就是递归地遍历当前Java 配置类的各种@Import、内部类、声明的@ComponentScan等等,当发现其它Java配置类时,再次调用 parse()
方法(或者 processConfigurationClass()
方法),解析那个类。最终的结果是把所有能找到的Java配置类都加入了 configurationClasses
容器中。
由于@SpringBootApplication其实也就是一个带有@Configuration的Java配置类,由Part II可知,它也会被按相同的方式解析。
而@SpringBootApplication所包含的注解最终又导入了 AutoConfigurationImportSelector
类,因此这个类将会被调用到。下面是调用到这个类的 getCandidateConfigurations
方法(Part I提到过)时的结果截图:
到此,所有的Spring Boot Starter中定义的自动配置类都会被扫描到,并将被解析。