我们知道,基于 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 这三个独立注解所组成。
图 1 @SpringBootApplication 注解的组成结构
我们知道@ComponentScan 是传统 Spring 框架中对内置的注解,而@SpringBootConfiguration 注解也很简单,实际上只是对 Spring 框架中另一个常用注解@Configuration 的一种包装,本身没有定义任何内容。所以,我们接下来重点剖析@EnableAutoConfiguration 注解。
可以看到,这里出现了一个新的注解,即@AutoConfigurationPackage。从命名上讲, @AutoConfigurationPackage 注解的作用就是自动对某一个代码包进行配置。
另一方面,我们还看到这里通过@Import 注解引入了一个 AutoConfigurationImportSelector 类。从命名上,我们也不难理解该类的作用是完成对导入的配置信息的自动选择。AutoConfigurationImportSelector 类的核心方法 getCandidateConfigurations 实现了这一目标。
这里引出了在 Spring Boot 中真正负责加载配置信息的 SpringFactoriesLoader 类。这些类之间的交互关系如图 2 所示:
图 2 AutoConfigurationImportSelector 类层结构图
显然,想要完成配置信息的自动选择,我们首先需要执行配置文件的加载操作,这部分功能是由 SpringFactoriesLoader 来完成的。SpringFactoriesLoader 也是 Spring Boot 自动配置得以实现的关键组件,我们来一起看一下。
SpringFactoriesLoader
SpringFactoriesLoader 类似 JDK 实现 SPI 机制时所使用的 ServiceLoader 类,区别只是配置文件的存放位置和配置项对应的键值定义不同。在 SpringFactoriesLoader 中,我们需要通过 META-INF/spring.factories 文件目录,来获取服务定义文件,并通过 EnableAutoConfiguration 这个配置键来获取具体的配置信息。图 3 展示了 SpringFactoriesLoader 和 ServiceLoader 之间的区别。
图 3 SpringFactoriesLoader 和 ServiceLoader 区别示意图
图 3 SpringFactoriesLoader 和 ServiceLoader 区别示意图
SpringFactoriesLoader 基于图 3 指定的配置文件名和配置键获取对应的配置信息,然后基于这些配置信息来实例化配置类,这里 Spring Boot 用到的是反射机制。SpringFactoriesLoader 类中的 loadSpringFactories 方法展示了这一过程。
同时,我们在 spring-boot-autoconfigure 工程的 spring.factories 配置文件中找到了如下所示配置项。
可以看到,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 类,我们首先需要重点关注的是类定义上的注解。
我们看到这里用到了@ConditionalOnClass 和@ConditionalOnSingleCandidate 这两个注解,它们就是 Spring Boot 中的条件注解。在介绍 MybatisAutoConfiguration 之前,有必要对这些注解做一定展开。
@ConditionalOn 系列条件注解
我们在前面的内容中已经了解到以-AutoConfiguration 结尾的自动配置类数量会很多,在一个应用程序的开发过程中,我们通常不会全部用到它们。这时候就需要引入一种机制来对这些自动配置类进行过滤。为此,Spring Boot 提供了一组@ConditionalOn 系列条件注解。通过这些注解,我们就可以基于特定的条件有选择性地加载某些配置类。在 Spring Boot 中常见的条件注解可以参考图 4。
图 4 常见@ConditionalOn 系列注解及其作用
在前面介绍的 MybatisAutoConfiguration 类的时候,出现了@ConditionalOnClass 和@ConditionalOnSingleCandidate 这两个条件注解。基于这两个条件注解,我们可以明确 MybatisAutoConfiguration 能够实例化的前提有两个:一是类路径中存在 SqlSessionFactory 和 SqlSessionFactoryBean;另一个则是容器中只存在一个 DataSource 实例。两者缺一不可,这是一种常用的自动配置控制技巧。
然后,我们在 MybatisAutoConfiguration 类上看到了一个@EnableConfigurationProperties 注解。通过这个注解,所有添加了@ConfigurationProperties 注解的配置类就会自动生效。这里的@EnableConfigurationProperties 注解中指定的是 MybatisProperties 类,该类定义了 MyBatis 运行时所需要的各种配置信息,而我们在 MybatisProperties 类上确实也发现了@ConfigurationProperties 注解,并设置了 prefix 为“mybatis”。
最后,在 MybatisAutoConfiguration 类上还存在一个@AutoConfigureAfter 注解,这个注解可以根据字面意思进行理解,即在完成某一个类的自动配置之后再执行当前类的自动配置。这个需要提前装配的类指的就是 DataSourceAutoConfiguration。
MybatisAutoConfiguration
理解了@ConditionalOnXXX、@EnableConfigurationProperties 和@AutoConfigureAfter 等一系列注解之后,我们回过头来再看 MybatisAutoConfiguration 类的代码结构就显得比较简单了。MybatisAutoConfiguration 类中的一个核心方法就是如下所示的 sqlSessionFactory 方法。
显然,这里基于前面介绍的 SqlSessionFactoryBean 构建了 SqlSessionFactory 实例。注意到在该方法上同样添加了一个@ConditionalOnMissingBean 注解,标明只有在当前上下文中不存在 SqlSessionFactoryBean 对象时才会执行上述方法。
同样,添加了@ConditionalOnMissingBean 注解的,还有如下所示的 sqlSessionTemplate 方法。
该方法用于构建一个 SqlSessionTemplate 对象实例。在 MyBatis 中,SqlSessionTemplate 实现了 SqlSession 接口,相当于是全局唯一的 SqlSession 实例。
接下来,我们需要在 META-INF/spring.factories 文件中明确所指定的自动配置类。根据 Spring Boot 自动配置机制的原理,对于 mybatis-spring-boot-autoconfigure 工程而言,这个配置项内容应该如下所示。
至此,整个 MyBatis Spring Boot Starter 的介绍就告一段落。作为总结,我们可以把创建一个 Spring Boot Starter 的过程抽象成三个步骤。
图 5 创建 Spring Boot Starter 的三个步骤
在日常开发过程中,我们就可以基于这三大步骤来实现一个自定义的 Spring Boot Starter。
总结
今天,我们详细阐述了 Spring Boot 自动配置机制的实现原理,从源码角度分析了为什么 Spring Boot 能够做到自动配置,并结合 MyBatis 框架分析了它在开源框架中的具体应用。同时,我们在本讲结尾部分还总结了开发一个 Spring Boot Starter 的三大步骤。实现一个自定义 Spring Boot Starter 也是日常开发的常见需求,我们在开发过程中可以基于本讲的内容加深对其实现原理的理解。
最后,我想给你留一道思考题:你能简要描述实现一个自定义 Spring Boot Starter 需要哪些开发步骤吗?