SpringBoot 02:源码分析——自动装配
SpringBoot自动配置根据我们添加的jar依赖项自动配置SpringBoot应用程序。
SpringBootApplication注解分析
例如,如果类路径中存在H2数据库Jar,而我们尚未手动配置任何与数据库相关的bean,则Spring Boot的自动配置功能会在项目中自动对其进行配置。
我们可以启用自动通过使用注解 @EnableAutoConfiguration 配置功能。但是此注解不使用,因为它包装在 @SpringBootApplication 注解内。注解**@SpringBootApplication**是三个注解的组合: @ComponentScan,@EnableAutoConfiguration,和 @SpringBootConfiguration 。但是,我们使用@SpringBootApplication批注而不是使用@EnableAutoConfiguration。
@SpringBootApplication = @ComponentScan + @EnableAutoConfiguration + @SpringBootConfiguration。

@SpringBootConfiguration
本质就是@Configuration,Spring在纯注解模式下需要的annotation配置
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Configuration
@Indexed
public @interface SpringBootConfiguration {
@AliasFor(
annotation = Configuration.class
)
boolean proxyBeanMethods() default true;
}
@ComponentScan
复习:Configuration+ComponentScan -> Spring纯注解所需要的最基本annotation方式配置

难点:@EnableAutoConfiguration
主要功能:执行自动配置。
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import({AutoConfigurationImportSelector.class})
public @interface EnableAutoConfiguration {
String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";
Class<?>[] exclude() default {};
String[] excludeName() default {};
}
可以看到, EnableAutoConfiguration有两个重要的注解:**@AutoConfigurationPackage、@Import({AutoConfigurationImportSelector.class})**,分别有两个重要作用。
@AutoConfigurationPackage
作用:把主程序所在包的所有平级类加载并且放入容器,但是不包括子包。整个过程是通过@Import({Registrar.class})实现完成的。
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Import({Registrar.class})
public @interface AutoConfigurationPackage {
String[] basePackages() default {};
Class<?>[] basePackageClasses() default {};
}
@AutoConfigurationPackage就是通过@Import({Registrar.class})这个注解将Registrar类导入实现把主程序所在包的所有平级类加载并且放入容器。
关键:Registrar类
Registrar是AutoConfigurationPackage抽象类中的一个静态内部类。
static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports {
Registrar() {
}
public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
AutoConfigurationPackages.register(registry, (String[])(new AutoConfigurationPackages.PackageImports(metadata)).getPackageNames().toArray(new String[0]));
}
public Set<Object> determineImports(AnnotationMetadata metadata) {
return Collections.singleton(new AutoConfigurationPackages.PackageImports(metadata));
}
}
这里registerBeanDefinitions方法的作用就是把主程序所在包的所有平级类加载进容器。里面调用了AutoConfigurationPackages抽象类的register方法,传递了 (String[])(new AutoConfigurationPackages.PackageImports(metadata)).getPackageNames().toArray(new String[0])这样一个参数,通过metadata获得了获得装配类所在的包,然后调用AutoConfigurationPackages.register方法进行所在包的所有平级类的加载。
这里metadata为:

也就是我们SpringBootApplication配置类的全类名
public static void register(BeanDefinitionRegistry registry, String... packageNames) {
// 如果获得的registry的BEAN有BeanDefinition,则直接从BEAN中获取BeanDefinition
if (registry.containsBeanDefinition(BEAN)) {
// 配置BasePackagesBeanDefinition
AutoConfigurationPackages.BasePackagesBeanDefinition beanDefinition = (AutoConfigurationPackages.BasePackagesBeanDefinition)registry.getBeanDefinition(BEAN);
// 给BasePackagesBeanDefinition加载BasePackages
beanDefinition.addBasePackages(packageNames);
} else {// 否则,给registry新建一个BeanDefinition
registry.registerBeanDefinition(BEAN, new AutoConfigurationPackages.BasePackagesBeanDefinition(packageNames));
}
}
// 基本包定义类,用ArrayList存储包名
static final class BasePackages {
private final List<String> packages;
private boolean loggedBasePackageInfo;
BasePackages(String... names) {
List<String> packages = new ArrayList();
String[] var3 = names;
int var4 = names.length;
for(int var5 = 0; var5 < var4; ++var5) {
String name = var3[var5];
if (StringUtils.hasText(name)) {
packages.add(name);
}
}
this.packages = packages;
}
static final class BasePacLinkedHashSetkagesBeanDefinition extends GenericBeanDefinition {
// 本质就是一个LinkedHashSet
private final Set<String> basePackages = new LinkedHashSet();
BasePackagesBeanDefinition(String... basePackages) {
this.setBeanClass(AutoConfigurationPackages.BasePackages.class);
this.setRole(2);
this.addBasePackages(basePackages);
}
public Supplier<?> getInstanceSupplier() {
return () -> {
return new AutoConfigurationPackages.BasePackages(StringUtils.toStringArray(this.basePackages));
};
}
private void addBasePackages(String[] additionalBasePackages) {
this.basePackages.addAll(Arrays.asList(additionalBasePackages));
}
}
@import(AutoConfigurationImportSelector.class)
Spring boot应用中使用了注解@SpringBootApplication,该注解隐含地导入了AutoConfigurationImportSelector,如下注解依赖链所示 :
// 注解链
@SpringBootApplication
=> @EnableAutoConfiguration
=> @Import(AutoConfigurationImportSelector.class)
SpringBoot自动配置的核心就在@EnableAutoConfiguration注解上,这个注解通过@Import(AutoConfigurationImportSelector)来完成自动配置。
所以说,SpringBoot自动配置的奥秘就隐藏在AutoConfigurationImportSelector类中。
首先有以下问题:
1.@Import注解是什么?有什么用?
@Import注解是用来导入配置类或者一些需要前置加载的类并放入容器,以下是@Imort注解类的源码
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Import {
Class<?>[] value();
}
@Target表明了他能作用的范围,可以作用于类、接口、枚举类。
属性只有一个Value,表示的是一个类对象数组,例如value={xx.class,yy.class},表示将xx和yy交给Spring容器管理。(感觉和类加载器有点关系)
2.ImportSelector接口是什么?有什么用?
通过ImportSelector 方式导入的类
新建TestC.class
public class TestC { public void fun(String str) { System.out.println(str); } public void printName() { System.out.println("类名 :" + Thread.currentThread().getStackTrace()[1].getClassName()); } }
2.新建SelfImportSelector.class 实现ImportSelector 接口,注入TestC.class//TODO ImportSelector 相关解释
public class SelfImportSelector implements ImportSelector {
@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
return new String[]{"com.test.importdemo.TestC"};
}
}
3.ImportConfig上面引入SelfImportSelector.class
@Import({SelfImportSelector.class})
@Configuration
public class ImportConfig {
}
4.测试结果
@Autowired
TestC testC;
@Test
public void TestC() {
testC.printName();
}
打印:
ImportAnnotionTest in 7.23 seconds (JVM running for 9.065)
类名 :com.test.importdemo.TestC
2020-08-16 14:23:15.330 INFO 1196 --- [
由此得出结论, @Import注解是用来导入配置类需要的类,而实现了ImportSelector接口的类需要Override一个方法,返回一个需要导入配置类的 类名数组,这样,这些导入配置类需要的类 就可以 一次型通过@Import注解被导入到Spring容器中了。
AutoConfigurationImportSelector类
了解一个类,我们首先就要先看这个类的定义:
public class AutoConfigurationImportSelector implements DeferredImportSelector, BeanClassLoaderAware,
ResourceLoaderAware, BeanFactoryAware, EnvironmentAware, Ordered {
我们可以看到它实现了DeferredImportSelector接口,那么接下来再来看看DeferredImportSelector接口的定义:
public interface DeferredImportSelector extends ImportSelector {
可以看到,DeferredImportSelector接口又继承了ImportSelector接口。这样,我们就明白了,AutoConfigurationImportSelector类必定实现了selectImports()方法,这个方法应该就是SpringBoot能够实现自动配置的核心。
实现接口方法:selectImports
public String[] selectImports(AnnotationMetadata annotationMetadata) {
// 判断SpringBoot是否开启自动配置
if (!this.isEnabled(annotationMetadata)) {
return NO_IMPORTS;
} else {
// 获取需要被引入的自动配置信息
AutoConfigurationImportSelector.AutoConfigurationEntry autoConfigurationEntry = this.getAutoConfigurationEntry(annotationMetadata);
return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
}
}
可以看到,selectImports源码没有多少。isEnabled()方法判断SpringBoot是否开启了自动配置。若开启就通过getAutoConfigurationEntry()来获取需要配置的Bean全限定名数组,否则就直接返回空数组。
isEnabled():判断SpringBoot是否开启自动配置
protected boolean isEnabled(AnnotationMetadata metadata) {
if (getClass() == AutoConfigurationImportSelector.class) {
// 若调用该方法的类是AutoConfigurationImportSelector,那么就获取EnableAutoConfiguration.ENABLED_OVERRIDE_PROPERTY的值,默认为true
return getEnvironment().getProperty(EnableAutoConfiguration.ENABLED_OVERRIDE_PROPERTY, Boolean.class, true);
}
return true;
}
EnableAutoConfiguration.ENABLED_OVERRIDE_PROPERTY是什么?我们看下它的定义:
String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";
看到这里,我们可以猜到这就是在配置文件application.yml或者application.properties中的配置。因此,我们可以在配置文件中来决定SpringBoot是否开启自动配置。
当我们没有配置的时候,默认就是开启自动配置的。
getAutoConfigurationEntry()方法:获取需要自动配置的bean信息
话不多说,先上源码:
protected AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {
// 判断是否开启自动配置
if (!isEnabled(annotationMetadata)) {
return EMPTY_ENTRY;
}
// 获取@EnableAutoConfiguration注解的属性
AnnotationAttributes attributes = getAttributes(annotationMetadata);
// 从spring.factories文件中获取配置类的全限定名数组
List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
// 去重
configurations = removeDuplicates(configurations);
// 获取注解中exclude或excludeName排除的类集合
Set<String> exclusions = getExclusions(annotationMetadata, attributes);
// 检查被排除类是否可以实例化,是否被自动配置所使用,否则抛出异常
checkExcludedClasses(configurations, exclusions);
// 去除被排除的类
configurations.removeAll(exclusions);
// 使用spring.factories配置文件中配置的过滤器对自动配置类进行过滤
configurations = getConfigurationClassFilter().filter(configurations);
// 抛出事件
fireAutoConfigurationImportEvents(configurations, exclusions);
return new AutoConfigurationEntry(configurations, exclusions);
}
在这里,我们只需要知道这个getAutoConfigurationEntry()方法是用来获取需要自动配置的bean信息,以及里面每个方法做了什么,有个大概的印象就可以了。
下面会对每个方法作更近一步的讲解。
getAttributes():获取@EnableAutoConfiguration注解属性
protected AnnotationAttributes getAttributes(AnnotationMetadata metadata) {
String name = getAnnotationClass().getName();
AnnotationAttributes attributes = AnnotationAttributes.fromMap(metadata.getAnnotationAttributes(name, true));
Assert.notNull(attributes, () -> "No auto-configuration attributes found. Is " + metadata.getClassName()
+ " annotated with " + ClassUtils.getShortName(name) + "?");
return attributes;
}

这里我们可以看到,这个方法就是获取@EnableAutoConfiguration注解的属性。
getCandidateConfigurations():从spring.factories文件获取需要配置的bean
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
//获取需要配置的bean全限定名列表。
List<String> configurations =
new ArrayList(SpringFactoriesLoader.loadFactoryNames(this.getSpringFactoriesLoaderFactoryClass(), this.getBeanClassLoader()));
ImportCandidates.load(AutoConfiguration.class, this.getBeanClassLoader()).forEach(configurations::add);
Assert.notEmpty(configurations, "No auto configuration classes found in META-INF/spring.factories nor in META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports. If you are using a custom packaging, make sure that file is correct.");
return configurations;
}
getCandidateConfigurations()方法通过SpringFactoriesLoader的loadFactoryNames()方法从所有的spring.factories文件中获取需要配置的bean全限定名列表。
getSpringFactoriesLoaderFactoryClass
这里我们看到,AutoConfigurationImportSelector的getSpringFactoriesLoaderFactoryClass 被写死返回本包(org.springframework.boot.autoconfigure)下的EnableAutoConfiguration.class。
protected Class<?> getSpringFactoriesLoaderFactoryClass() {
return EnableAutoConfiguration.class;
}
也就是说,loadFactoryNames()方法获取的也是EnableAutoConfiguration这个注解类下的属性。
然后说说SpringFactoriesLoader这个类
SpringFactoriesLoader
SpringFactoriesLoader会扫描所有jar包类路径下的META-INF/spring.factories文件(写死的),并获取指定接口的配置。

当调用返回值类型为Map<String,List<String>>类型的loadFactories时,内部调用loadSpringFactories,访问本包(org.springframework.boot.autoconfigure)下的META-INF文件夹中的spring.factories。

然后聊聊spring.factories文件,先看看AutoConfigurationImportSelector在什么位置?
AutoConfigurationImportSelector类存在于org.springframework.boot.autoconfigure这个包下,这个包下存储了大量自动化配置类,SpringBoot所有的自动化工作都是在这个包下作用的。

这些自动化的配置类怎么被加载进容器呢?答案就在META-INF包下的spring.factories与spring-autoconfigure-metadata.properties。spring.factories文件本质上与properties文件相似,其中包含一组或多组键值对。其中,key的取值是接口的全限定名,value的取值是接口实现类的全限定名。一个接口可以设置多个实现类,不同实现类之间使用,隔开。例如:
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration,\
org.springframework.boot.autoconfigure.aop.AopAutoConfiguration
然而在2.71版本,我并没有在spring.factories文件找到EnableAutoConfiguration,而是找到一些相关的配置,有 过滤掉不符合条件注解配置类的过滤器 的包名 等等,可能是因为版本更新对代码进行了修改,这里不多作文章。


removeDuplicates():去重
protected final <T> List<T> removeDuplicates(List<T> list) {
return new ArrayList<>(new LinkedHashSet<>(list));
}
我们知道SpringFactoriesLoader的loadFactoryNames()方法会从所有jar包类路径下的META-INF/spring.factories读取配置。就是说会从不同的spring.factories文件中读取配置,那么就有可能会出现配置了相同的类,这里就是对这些数据进行去重。
getExclusions():获取注解的exclude和excludeName属性配置的需要排除的类全限定名集合
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;
}
getExclusions方法就是将上面getAttributes()方法获取到@EnableAutoConfiguration注解的exclude和excludeName属性的值加入到excluded集合中。
checkExcludedClasses():检查排除类
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);
}
}
检查被排除的类是否可以实例化以及自动配置类列表是否包含该类。如果存在无效排除类,那么就抛出异常。
getConfigurationClassFilter():获取配置类过滤器
private ConfigurationClassFilter getConfigurationClassFilter() {
if (this.configurationClassFilter == null) {
// 获取AutoConfigurationImportFilter过滤器列表
List<AutoConfigurationImportFilter> filters = getAutoConfigurationImportFilters();
for (AutoConfigurationImportFilter filter : filters) {
invokeAwareMethods(filter);
}
this.configurationClassFilter = new ConfigurationClassFilter(this.beanClassLoader, filters);
}
return this.configurationClassFilter;
}
getConfigurationClassFilter()方法通过getAutoConfigurationImportFilters()方法获取到spring.factories文件中AutoConfigurationImportFilter接口的配置,然后将其封装到ConfigurationClassFilter对象中。
AutoConfigurationImportSelector总结
AutoConfigurationImportSelector类是通过@EnableAutoConfiguration注解引入的,主要作用就是将所有SpringBoot要引入的自动配置类加载并放入容器之中,这些类的名称都是xxxAutoConfiguration,并且他们的全名都是配置在META-INF/spring.factories配置文件中的EnableAutoConfiguration(新版本有所不同),除了这些还有一个强大的功能就是过滤掉不符合条件注解配置类。
AutoConfigurationImportSelector的getSpringFactoriesLoaderFactoryClass 被写死返回本包(org.springframework.boot.autoconfigure)下的EnableAutoConfiguration.class。
当调用SpringFactoriesLoader.loadFactoryNames()时,内部调用loadSpringFactories,访问本包(org.springframework.boot.autoconfigure)下的META-INF文件夹中的spring.factories,变量都是所有要加载的xxxAutoConfiguration类。
每一个xxxAutoConfiguration类都有@Configuration 注解,表明这个类是参与Spring配置的。
每一个xxxAutoConfiguration类都有@EnableConfigurationProperties((xxxProperties.class)) annotation,xxxProperties类就是用于存储自动配置信息的类
AutoConfigurationImportSelector类是ImportSelector的实现类,实现了selectImports()方法。selectImports()方法又调用getAutoConfigurationEntry()方法从spring.factories文件中读取配置类的全限定名列表,并进行过滤,最终得到需要自动配置的类全限定名列表。
问题:为什么有了ComponentScan,还需要AutoConfigurationPackage
AutoConfigurationPackage处理的是主程序所在包平级的所有类,ComponentScan(缺省写法,不写value或者basePackages,就是当前主程序所在包)处理的是主程序所在包的平级和子包的所有类。
AutoConfigurationPackage运行在先,ComponentScan运行在后
AutoConfigurationPackage不运行的话,ComponentScan无法运行(AutoConfigurationPackage为ComponentScan做了一系列初始化工作)
SpringBoot自动配置原理总结
这里我们看到,AutoConfigurationImportSelector的getSpringFactoriesLoaderFactoryClass 被写死返回本包(org.springframework.boot.autoconfigure)下的EnableAutoConfiguration.class。
体会:初看源码的感觉就像掉进了秦始皇的陵墓一样,令人惊叹的同时又令人充满着困惑,真的是佩服能够书写这种史诗级代码的程序员,这头发掉的值!~
参考资料
SpringBoot自动配置之AutoConfigurationImportSelector - SpringBoot自动配置(二)