我们知道,基于Spring Boot,我们只需要在类路径中引入一组第三方框架的starter组件,就能在Spring容器中使用这些框架所提供的各项功能。这在当下的开发过程中已经习以为常,但在Spring Boot还没有诞生之前却是不可想象的。如果我们使用传统的Spring框架,那就需要添加各种繁杂的配置信息才能启动容器。那么,Spring Boot是通过那种机制来做到这一点的呢?这就是本文我们要讨论的内容,即Spring Boot的自动配置机制。
可以说,Spring Boot的自动配置机制应用也非常广泛,在目前主流的开源框架中,都提供了各自的starter组件。例如,Mybatis的starter组件为mybatis-spring-boot-starter。而从扩展性上讲,这也是Spring Boot为开发人员提供了一整套扩展机制,我们可以基于这套扩展机制实现自定义的starter组件。
Spring Boot自动配置机制原理
Spring Boot的自动配置功能强大,但也有一定的复杂度,让我们先来深入理解其背后的实现原理。
@EnableAutoConfiguration注解
我们通过查看@SpringBootApplication注解的定义,发现该注解实际上是一个复合注解,由@SpringBootConfiguration、@ComponentScan和@EnableAutoConfiguration所组成。
我们知道@ComponentScan是传统Spring框架中就内置的注解,而@SpringBootConfiguration注解也很简单,实际上只是对Spring框架中另一个常用组件@Configuration的一种包装,本身没有定义任何内容。所以,我们接下来重点剖析@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 {};
}
可以看到,这里出现了一个新的注解,即@AutoConfigurationPackage。从命名上讲, @AutoConfigurationPackage注解的作用就是自动对某一个代码包进行配置。
另一方面,我们还看到通过@Import注解引入了一个AutoConfigurationImportSelector对。从命名上,我们也不难理解该类的作用是完成对导入的配置信息的自动选择。该类的核心方法getCandidateConfigurations实现了这一目标。
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
List<String> configurations = SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(), getBeanClassLoader());
…
return configurations;
}
这里引出了在Spring Boot中真正负责加载配置信息的SpringFactoriesLoader类。
这些类之间的交互关系如下图所示。
显然,想要完成配置信息的自动选择,我们首先需要执行配置文件的加载操作,这部分功能是由SpringFactoriesLoader来完成的。SpringFactoriesLoader也是Spring Boot自动配置得以实现的关键组件,我们来一起看一下。
SpringFactoriesLoader
SpringFactoriesLoader类似JDK中的SPI机制所使用的ServiceLoader类,区别只是在配置文件的存放位置和配置项对应的键值定义。在SpringFactoriesLoader中,我们需要通过META-INF/spring.factories文件夹获取服务定义文件,并通过EnableAutoConfiguration这个健值来获取具体的配置信息。下图展示了SpringFactoriesLoader和ServiceLoader之间的区别。
SpringFactoriesLoader基于上图中指定的配置文件名和键值获取对应的配置信息,然后基于这些配置信息来实例化配置类,Spring Boot通过反射机制实现了这一目标。SpringFactoriesLoader类中的loadSpringFactories方法展示了这一过程。
private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
//从缓存中获取配置内容,如果存在则直接返回
try {
//基于ClassLoader从META-INF/spring.factories获取配置文件资源地址URL
while (urls.hasMoreElements()) {
//获取配置文件资源
//加载配置项
for (Map.Entry<?, ?> entry : properties.entrySet()) {
//组装配置项Key-Value
}
}
//把配置信息放入缓存
//返回结果
}
}
我们在spring-boot-autoconfigure工程中所使用的spring.factories配置文件中知道了如下所示配置项。
# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration,\
org.springframework.boot.autoconfigure.aop.AopAutoConfiguration,\
org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration,\
org.springframework.boot.autoconfigure.MessageSourceAutoConfiguration,\
org.springframework.boot.autoconfigure.PropertyPlaceholderAutoConfiguration,\
org.springframework.boot.autoconfigure.batch.BatchAutoConfiguration,\
org.springframework.boot.autoconfigure.cache.CacheAutoConfiguration,\
org.springframework.boot.autoconfigure.cassandra.CassandraAutoConfiguration,\
org.springframework.boot.autoconfigure.cloud.CloudAutoConfiguration,\
org.springframework.boot.autoconfigure.context.ConfigurationPropertiesAutoConfiguration,\
…
可以看到在org.springframework.boot.autoconfigure.EnableAutoConfiguration配置项中定义了各种以-AutoConfiguration结尾的配置类。通过SpringFactoriesLoader,Spring Boot就能做到在服务启动的过程中把它们记载到容器中并实现自动化配置。
Mybatis Spring Boot Starter
介绍完Spring Boot中应用程序的自动配置机制之后,我们来做一些实践,通过剖析Mybatis Spring Boot Starter的启动过程来加深对所介绍内容的理解。
在mybatis-spring-boot-starter中存在几个代码工程,我们重点关注mybatis-spring-boot-autoconfigure工程。而在这个代码工程中,最重要的显然就是MybatisAutoConfiguration类。对于Spring Boot中的AutoConfiguration类,我们首先需要重点关注的是类定义上的注解,如下所示。
@org.springframework.context.annotation.Configuration
@ConditionalOnClass({ SqlSessionFactory.class, SqlSessionFactoryBean.class })
@ConditionalOnSingleCandidate(DataSource.class)
@EnableConfigurationProperties(MybatisProperties.class)
@AutoConfigureAfter(DataSourceAutoConfiguration.class)
public class MybatisAutoConfiguration implements InitializingBean {
我们看到这里用到了新的@ConditionalOnClass和@ ConditionalOnSingleCandidate注解,它们就是Spring Boot中的条件注解。在介绍MybatisAutoConfiguration之前,有必要对这些注解做一定展开。
@ConditionalOn系列条件注解
我们在前面的介绍中已经了解到以-AutoConfiguration结尾的自动配置类数量会很多,在一个应用程序的开发过程中,我们通常不会全部使用到。这时候就需要引入一种机制来对这些自动配置类进行过滤。为此,Spring Boot提供了一组@ConditionalOn系列条件注解。通这些注解,我们就可以基于特定的条件来选择性的加载某些配置类。在Spring Boot中常见的条件注解可以参考下图。
在前面介绍的MybatisAutoConfiguration类上,我们发现了@ConditionalOnClass和@ConditionalOnSingleCandidate这两个条件注解。基于这两个条件注解,我们可以明确MybatisAutoConfiguration能够实例化的前提有两个,一个是类路径中存在SqlSessionFactory和SqlSessionFactoryBean,另一个是容器中只存在一个DataSource实例。两者缺一不可,这是一种常用的自动配置控制技巧。
然后,我们在MybatisAutoConfiguration类上看到了一个@EnableConfigurationProperties注解。通过这个注解,所有添加了@ConfigurationProperties 注解的配置类就会自动生效。这里的@EnableConfigurationProperties注解中指定的是MybatisProperties类,该类定义了Mybatis运行时所需要的各种配置信息,而我们在MybatisProperties类上确实也发现了@ConfigurationProperties注解,并指定了prefix为"mybatis"。
@ConfigurationProperties(
prefix = "mybatis"
)
public class MybatisProperties {
...
}
最后,在MybatisAutoConfiguration类上还存在一个@AutoConfigureAfter注解,这个注解可以根据字面意思进行理解,即在完成某一个类的自动配置之后再执行当前类的自动配置,这个需要提前装配的类指的就是DataSourceAutoConfiguration。
MybatisAutoConfiguration
理解上@ConditionalOnXXX、@EnableConfigurationProperties和@AutoConfigureAfter等一系列注解之后,我们回过头来再看MybatisAutoConfiguration类的代码结构就显得比较简单明了。MybatisAutoConfiguration类中核心方法之一就是如下所示的sqlSessionFactory方法。
@Bean
@ConditionalOnMissingBean
public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {
SqlSessionFactoryBean factory = new SqlSessionFactoryBean();
factory.setDataSource(dataSource);
factory.setVfs(SpringBootVFS.class);
if (StringUtils.hasText(this.properties.getConfigLocation())) {
factory.setConfigLocation(this.resourceLoader.getResource(this.properties.getConfigLocation()));
}
applyConfiguration(factory);
//省略一系列配置项设置方法
return factory.getObject();
}
显然,这里基于前面介绍的SqlSessionFactoryBean构建了SqlSessionFactory实例。注意到在该方法上同样添加了一个@ConditionalOnMissingBean注解,标明只有在当前上下文中不存SqlSessionFactoryBean对象时才会执行上述方法。
同样添加了@ConditionalOnMissingBean注解的还有如下所示的sqlSessionTemplate方法。
@Bean
@ConditionalOnMissingBean
public SqlSessionTemplate sqlSessionTemplate(SqlSessionFactory sqlSessionFactory) {
ExecutorType executorType = this.properties.getExecutorType();
if (executorType != null) {
return new SqlSessionTemplate(sqlSessionFactory, executorType);
} else {
return new SqlSessionTemplate(sqlSessionFactory);
}
}
该方法用于构建一个SqlSessionTemplate对象实例。在Mybatis中,SqlSessionTemplate实现了SqlSession接口,相当于是全局唯一的SqlSession实例。
接下来,我们需要在META-INF/spring.factories文件中明确所指定的自动配置类。根据Spring Boot自动配置机制的原理,对于mybatis-spring-boot-autoconfigure工程而言,这个配置项内容应该如下所示。
# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.mybatis.spring.boot.autoconfigure.MybatisAutoConfiguration
至此,整个Mybatis Spring Boot Starter的介绍就告一段落。作为总结,我们可以把创建一个Spring Boot Starter的过程抽象三个步骤。
在日常开发过程中,我们就可以基于这三大步骤来实现一个自定义的Spring Boot Starter。
总结
本文内容详细阐述了Spring Boot自动配置机制的实现原理,从源码角度分析了为什么Spring Boot能够做到自动配置,并结合Mybatis框架分析了它在开源框架中的具体应用。同时,我们在本讲结尾部分还总结了开发一个Spring Boot Starter的三大步骤,开发一个Spring Boot Starter也是常见的需求,我们在开发过程中可以基于本讲的内容加深对其实现原理的理解。