Spring Boot 自动配置原理
Spring Boot是由Pivotal团队提供的全新框架,其设计目的是用来简化新Spring应用的初始搭建以及开发过程。该框架使用了特定的方式来进行配置,从而使开发人员不再需要定义样板化的配置(applicationContext.xml)。其中自动配置可以说是Spring Boot中最令人愉悦的功能之一,它使得开发人员远离了繁琐配置(任何东西多了都会变得繁琐)。
1、实现思路
Spring Boot提供了自动配置机制,它是通过 类路径、系统环境、外部配置参数、逻辑表达式、spring上下文 等角度去判断当前系统是否需要启用某个技术框架,如果需要则进行自动配置。也就是说还是得配置,只不过Spring Boot把这个配置过程变得智能了一点。这里需要说明的是Spring Boot只是提供了自动配置机制,而具体要什么类需要加入到Spring容器是各个框架需要自己实现的(自定义starter),当然Spring Boot内部有一部分实现。

我们先看一个非Spring Boot项目环境中要使用Mybatis作为项目里的orm框架,我们需要在applicationContext.xml里配置Mybatis的相关的类(SqlSessionFactoryBean),这一过程是我们主动告知Spring我需要配置什么类(Spring Boot也是主动告知,只不过告知这个动作是代码帮我们完成的)。

如果使用Spring Boot的话,主动告知这一过程将是Spring Boot自动配置机制帮我们完成,这里实际上是mybatis-spring-boot-starter实现的

2、基于什么实现
(一)Java配置
Spring Boot是基于Spring3.0以上版本中的Java配置特性来实现的,具体使用规则这里不作说明,如不清楚可以先了解相关知识。Java配置使我们可以用Java类来代替Xml配置文件,于是我们可以想假如把applicationContext.xml里的所有配置都通过Java配置来实现的话,那就实现了我们消灭配置的第一步,如下图:
| Xml配置 | Java配置 |
|---|---|
![]() |
![]() |
(二)关键的类和接口
Spring3.x中提供的一系列类、接口、注解构成了自动配置的基石,我们甚至可以利用它们自己搞一个Spring Boot出来,先来看下主要的类:
| 名称 | 说明 |
|---|---|
ImoprtSelector |
是一个接口,提供了一个方法用于收集需要导入的配置类 |
@Conditional |
可以根据条件Condition 装载不同的Bean |
Condition |
是一个接口,提供一个方法用于返回是否匹配,如果匹配则配置,反之 |
@Import |
导入配置类、普通Java类、实现ImoprtSelector的类 |
@SpringFactoriesloader |
用于加载spring.factories中配置的内容 |
3、源码分析
(一)@SpringBootApplication注解
先从Spring Boot应用的入口开始,即@SpringBootApplication注解,如图

可以看到@SpringBootApplication上添加了@EnableAutoConfiguration,这个注解从名称上看就知其意 (激活自动配置),点进@EnableAutoConfiguration,如图

在@EnableAutoConfiguration上添加了@Import,前文中提过@Import的功能是导入配置类、普通Java类、实现ImoprtSelector的类,这里导入的是EnableAutoConfigurationImportSelector类,它继承AutoConfigurationImportSelector类,而AutoConfigurationImportSelector实现了ImoprtSelector接口,但EnableAutoConfigurationImportSelector已经添加了@Deprecated说明不推荐使用了。如图

ImoprtSelector
这里简单说明下实现ImoprtSelector接口的作用,ImoprtSelector有个selectImports方法(这两个名称简直让人头晕)如图

可以看到selectImports()返回值为应该导入的配置类类名集合,到底应该导入哪些配置类?你自己去决定和实现啦。方法的参数是AnnotationMetadata,它是访问注解元数据的接口,意思是什么类上添加了@Import(ImoprtSelectorImpl.class)注解,那AnnotationMetadata里封装的就是那个类的所有注解信息。
(二)AutoConfigurationImportSelector类

AutoConfigurationImportSelector中方法selectImports()的源码如下:

首先调用AutoConfigurationMetadataLoader.loadMetadata()加载自动配置元数据,它加载了 spring-boot-autoconfigure-1.5.7.RELEASE.jar 下的META-INF/spring-autoconfigure-metadata.properties文件,最后返回了AutoConfigurationMetadata(把它当作一个properties),文件的内容如下:

我们发现这个properties文件的key比较复杂,它格式是一个完整类名(F)+另一个简单类名(S),这样做的目的从源码可以推断应该是想达到命名空间的效果,其实就是为了方便取值。等号右边配置的东西具体用于什么地方需要根据(S)来确定,就上图中ConditionOnClass等号右边值配的是类名,以,号隔开,用于判断 是否存在于类路径。
接着调用了getCandidateConfigurations()方法

getCandidateConfigurations()方法会去获取所有自动配置类的名称,源码如下:

利用SpringFactoriesLoader加载 spring-boot-autoconfigure-1.5.7.RELEASE.jar 下的META-INF/spring.factories文件,文件是properties格式如图:

可以看到配在org.springframework.boot.autoconfigure.EnableAutoConfiguration下的全是自动配置类,这些类以字符串数组的形式返回,也就是getCandidateConfigurations()的方法返回值。
继续看方法selectImports()中的代码

得到自动配置类名称集合后调用了removeDuplicates()(删除重复的配置类),接着sort()(排序),接着去掉使用exclude或者excludeName所排除的类,然后调用filter(),最后触发所有注册的回调。其中filter()方法是自动配置的关键,源码如下:

上面代码中调用getAutoConfigurationImportFilters()方法返回了一个AutoConfigurationImportFilter接口,真实运行时它是实现类OnClassCondition,然后调用match()方法,该方法的返回值是boolean[],如果是flase说明不匹配则跳过自动配置。细节将在下一节说明
。
(三)OnClassCondition类

match()方法返回的boolean[]来判断到底需不需要导入自动配置(注意boolean[]值的意义是是否需要导入自动配置,而不是开始配置),那返回boolean[]到底有何意义?我们看方法match()的源码:

方法getOutcomes()返回的是是否匹配的结果,这个是否匹配需要结合META-INF/spring.factories和META-INF/spring-autoconfigure-metadata.properties来看,前者指定了需要自动配置的类,后者指定了以自动配置类为key,多个类名为值,通过这些信息就可以判断是否匹配了。ConditionOutcome类则匹配结果的封装,它包含了是否匹配、条件消息等信息,可以把它当作领域对象。方法getOutcomes()的源码如下:
多个类名:这里的类是用于判断 是否存在于类路径

将需要自动配置的类二分处理,一半开启线程进行处理,另一半标准处理,最后合并结果。有意思的地方是源码作者在注释中写到 //More threads make things worse 更多的线程会变得更糟(为什么不是分3次或者4次呢?疑问脸)。
最后我们忽略中间的层层封装直达底层,究竟OnClassCondition是怎么判断需不需要导入自动配置类,看如下源码:

通过上面代码可以知道它是通过当前系统类路径下是否存在className来判断的,如果存在就导入这个自动配置类,反之忽略。到此Spring Boot帮我们从类路径的角度去判断了需不需要启用自动配置。
className:
META-INF/spring-autoconfigure-metadata.properties中等号右边的值
(四)其他角度
Spring Boot提供了自动配置机制,它是通过 类路径、系统环境、外部配置参数、逻辑表达式、spring上下文 等角度去判断当前系统是否需要启用某个技术框架,目前我们分析了从 类路径 的角度去判断的源码,其他的角度思路一致,只是具体实现不一样,它们都继承自SpringBootCondition。

SpringBootCondition中的抽象方法getMatchOutcome()便是子类去实现是否匹配的,那OnClassCondition 中为什么使用的match()方法去实现的?实际上OnClassCondition 有两套实现,一个是match(),另一个就是getMatchOutcome(),前者是Spring Boot强制的通过类路径去推断了一次,后者是使用@ConditionalOnClass注解的时候调用

以此类推其他的@ConditionalOn*都是通过@Conditional注解来实现的,下面列举了常用的条件判断注解
@Conditional扩展 |
作用 |
|---|---|
@ConditionalOnJava |
系统中的jdk版本是否符合要求 |
@ConditionalOnExpression |
满足SpEL表达式 |
@ConditionalOnBean |
Spring容器中存在指定的Bean |
@ConditionalOnMissingBean |
Spring容器中不存在指定的Bean |
@ConditionalOnClass |
某个类在类路径中 |
@ConditionalOnMissingClass |
某个类不在类路径中 |
@ConditionalOnJndi |
存在JNDI路径 |
@ConditionalOnNotWebApplication |
当前系统不是Web环境 |
@ConditionalOnWebApplication |
当前系统是Web环境 |
@ConditionalOnProperty |
指定的属性是否有指定的值 |
@ConditionalOnResource |
类路径下是否存在指定的资源文件 |
(五)一个例子
我们以MybatisAutoConfiguration自动配置类为例,源码如下:

可以看到类上添加了@ConditionalOnClass({SqlSessionFactory.class,SqlSessionFactoryBean.class})注解,说明当存在SqlSessionFactory、SqlSessionFactoryBean类的时候启动自动配置,接着添加了@ConditionalOnBean(DataSource.class),说明当存在DataSource实例时会启动自动配置。
sqlSessionFactory()方法上添加了@ConditionalOnMissingBean注解,说明当不存在SqlSessionFactory实例的时候会创建一个SqlSessionFactory实例。
@EnableConfigurationProperties(MybatisProperties.class)作用是指定一个类用来接收外部配置文件参数.
3、总结
Spring Boot自动配置原理总体还是基于Spring提供的特性,牢固掌握Spring核心的重要性不言而喻。

