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