本文正在参与 「掘力星计划」 ,赢取创作大礼包,挑战创作激励金。
我们知道,Spring Boot 项目创建完成后,即使不进行任何的配置,也能够顺利地运行,这都要归功于 Spring Boot 的自动化配置。
Spring Boot 默认使用 application.properties 或 application.yml 作为其全局配置文件,我们可以在该配置文件中对各种自动配置属性(server.port、logging.level.* 、spring.config.active.no-profile 等等)进行修改,并使之生效,那么您有没有想过这些属性是否有据可依呢?答案是肯定的。
Spring Boot 官方文档:常见应用属性中对所有的配置属性都进行了列举和解释,我们可以根据官方文档对 Spring Boot 进行配置,但 Spring Boot 中的配置属性数量庞大,仅仅依靠官方文档进行配置也十分麻烦。我们只有了解了 Spring Boot 自动配置的原理,才能更加轻松熟练地对 Spirng Boot 进行配置。本节为你揭开 SpringBoot 自动配置的神秘面纱。
Spring Factories 机制
Spring Boot 的自动配置是基于 Spring Factories 机制实现的。
Spring Factories 机制是 Spring Boot 中的一种服务发现机制,这种扩展机制与 Java SPI 机制十分相似。Spring Boot 会自动扫描所有 Jar 包类路径下 META-INF/spring.factories 文件,并读取其中的内容,进行实例化,这种机制也是 Spring Boot Starter 的基础。
spring.factories
spring.factories 文件本质上与 properties 文件相似,其中包含一组或多组键值对(key=vlaue),其中,key 的取值为接口的完全限定名;value 的取值为接口实现类的完全限定名,一个接口可以设置多个实现类,不同实现类之间使用“,”隔开,例如:\
1 | org.springframework.boot.autoconfigure.AutoConfigurationImportFilter=\ |
Spring Factories 实现原理
spring-core 包里定义了 SpringFactoriesLoader 类,这个类会扫描所有 Jar 包类路径下的 META-INF/spring.factories 文件,并获取指定接口的配置。在 SpringFactoriesLoader 类中定义了两个对外的方法,如下表。
返回值 | 方法 | 描述 |
---|---|---|
List | loadFactories(Class factoryType, @Nullable ClassLoader classLoader) | 静态方法; 根据接口获取其实现类的实例; 该方法返回的是实现类对象列表。 |
List | loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) | 公共静态方法; 根据接口l获取其实现类的名称; 该方法返回的是实现类的类名的列表 |
以上两个方法的关键都是从指定的 ClassLoader中获取spring.factories 文件,并解析得到类名列表,具体代码如下。 loadFactories() 方法能够获取指定接口的实现类对象,具体代码如下。
1 | public static <T> List<T> loadFactories(Class<T> factoryType, { ClassLoader classLoader) |
loadFactoryNames() 方法能够根据接口获取其实现类类名的集合,具体代码如下。
1 | public static List<String> loadFactoryNames(Class<?> factoryType, { ClassLoader classLoader) |
loadSpringFactories() 方法能够读取该项目中所有 Jar 包类路径下 META-INF/spring.factories 文件的配置内容,并以 Map 集合的形式返回,具体代码如下。
1 | private static Map<String, List<String>> loadSpringFactories(ClassLoader classLoader) { |
自动配置的加载
Spring Boot 自动化配置也是基于 Spring Factories 机制实现的,在 spring-boot-autoconfigure-xxx.jar 类路径下的 META-INF/spring.factories 中设置了 Spring Boot 自动配置的内容 ,如下。
1 | # Auto Configure |
以上配置中,value 取值是由多个 xxxAutoConfiguration (使用逗号分隔)组成,每个 xxxAutoConfiguration 都是一个自动配置类。Spring Boot 启动时,会利用 Spring-Factories 机制,将这些 xxxAutoConfiguration 实例化并作为组件加入到容器中,以实现 Spring Boot 的自动配置。
@SpringBootApplication 注解
所有 Spring Boot 项目的主启动程序类上都使用了一个 @SpringBootApplication 注解,该注解是 Spring Boot 中最重要的注解之一 ,也是 Spring Boot 实现自动化配置的关键。 @SpringBootApplication 是一个组合元注解,其主要包含两个注解:@SpringBootConfiguration 和 @EnableAutoConfiguration,其中 @EnableAutoConfiguration 注解是 SpringBoot 自动化配置的核心所在。
@EnableAutoConfiguration 注解
@EnableAutoConfiguration 注解用于开启 Spring Boot 的自动配置功能, 它使用 Spring 框架提供的 @Import 注解通过 AutoConfigurationImportSelector类(选择器)给容器中导入自动配置组件。
图2:@EnableAutoConfiguration 注解
AutoConfigurationImportSelector 类
AutoConfigurationImportSelector 类实现了 DeferredImportSelector 接口,AutoConfigurationImportSelector 中还包含一个静态内部类 AutoConfigurationGroup,它实现了 DeferredImportSelector 接口的内部接口 Group(Spring 5 新增)。 AutoConfigurationImportSelector 类中包含 3 个方法,如下表
返回值 | 方法声明 | 描述 | 内部类方法 | 内部类 |
---|---|---|---|---|
Class<? extends Group> | getImportGroup() | 该方法获取实现了 Group 接口的类,并实例化 | 否 | |
void | process(AnnotationMetadata annotationMetadata, DeferredImportSelector deferredImportSelector) | 该方法用于引入自动配置的集合 | 是 | AutoConfigurationGroup |
Iterable | selectImports() | 遍历自动配置类集合(Entry 类型的集合),并逐个解析集合中的配置类 | 是 |
AutoConfigurationGroup AutoConfigurationImportSelector 内各方法执行顺序如下。
- getImportGroup() 方法
- process() 方法
- selectImports() 方法
下面我们将分别对以上 3 个方法及其调用过程进行介绍。
1. getImportGroup() 方法
AutoConfigurationImportSelector 类中 getImportGroup() 方法主要用于获取实现了 DeferredImportSelector.Group 接口的类,代码如下。
1 | public Class<? extends Group> getImportGroup() { |
2. process() 方法
静态内部类 AutoConfigurationGroup 中的核心方法是 process(),该方法通过调用 getAutoConfigurationEntry() 方法读取 spring.factories 文件中的内容,获得自动配置类的集合,代码如下
1 | public void process(AnnotationMetadata annotationMetadata, DeferredImportSelector deferredImportSelector) { |
getAutoConfigurationEntry() 方法通过调用 getCandidateConfigurations() 方法来获取自动配置类的完全限定名,并在经过排除、过滤等处理后,将其缓存到成员变量中,具体代码如下。
1 | protected AutoConfigurationImportSelector.AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) { |
在 getCandidateConfigurations() 方法中,根据 Spring Factories 机制调用 SpringFactoriesLoader 的 loadFactoryNames() 方法,根据 EnableAutoConfiguration.class (自动配置接口)获取其实现类(自动配置类)的类名的集合,如下图。
图3:getCandidateConfigurations 方法
3. process() 方法 以上所有方法执行完成后,AutoConfigurationImportSelector.AutoConfigurationGroup#selectImports() 会将 process() 方法处理后得到的自动配置类,进行过滤、排除,最后将所有自动配置类添加到容器中。
1 | public Iterable<DeferredImportSelector.Group.Entry> selectImports() { |
自动配置的生效和修改
spring.factories 文件中的所有自动配置类(xxxAutoConfiguration),都是必须在一定的条件下才会作为组件添加到容器中,配置的内容才会生效。这些限制条件在 Spring Boot 中以 @Conditional 派生注解的形式体现,如下表。
注解 | 生效条件 |
---|---|
@ConditionalOnJava | 应用使用指定的 Java 版本时生效 |
@ConditionalOnBean | 容器中存在指定的 Bean 时生效 |
@ConditionalOnMissingBean | 容器中不存在指定的 Bean 时生效 |
@ConditionalOnExpression | 满足指定的 SpEL 表达式时生效 |
@ConditionalOnClass | 存在指定的类时生效 |
@ConditionalOnMissingClass | 不存在指定的类时生效 |
@ConditionalOnSingleCandidate | 容器中只存在一个指定的 Bean 或这个 Bean 为首选 Bean 时生效 |
@ConditionalOnProperty | 系统中指定属性存在指定的值时生效 |
@ConditionalOnResource | 类路径下存在指定的资源文件时生效 |
@ConditionalOnWebApplication | 当前应用是 web 应用时生效 |
@ConditionalOnNotWebApplication | 当前应用不是 web 应用生效 |
下面我们以 ServletWebServerFactoryAutoConfiguration 为例,介绍 Spring Boot 自动配置是如何生效的。
ServletWebServerFactoryAutoConfiguration
ServletWebServerFactoryAutoConfiguration 代码如下。
1 |
|
该类使用了以下注解:
- @Configuration:用于定义一个配置类,可用于替换 Spring 中的 xml 配置文件;
- @Bean:被 @Configuration 注解的类内部,可以包含有一个或多个被 @Bean 注解的方法,用于构建一个 Bean,并添加到 Spring 容器中;该注解与 spring 配置文件中 等价,方法名与 的 id 或 name 属性等价,方法返回值与 class 属性等价;
除了 @Configuration 和 @Bean 注解外,该类还使用 5 个 @Conditional 衍生注解:
- @ConditionalOnClass({ServletRequest.class}):判断当前项目是否存在 ServletRequest 这个类,若存在,则该配置类生效。
- @ConditionalOnWebApplication(type = Type.SERVLET):判断当前应用是否是 Web 应用,如果是的话,当前配置类生效。
- @ConditionalOnClass(name = {“org.apache.catalina.startup.Tomcat”}):判断是否存在 Tomcat 类,若存在则该方法生效。
- @ConditionalOnMissingFilterBean({ForwardedHeaderFilter.class}):判断容器中是否有 ForwardedHeaderFilter 这个过滤器,若不存在则该方法生效。
- @ConditionalOnProperty(value = {“server.forward-headers-strategy”},havingValue = “framework”):判断配置文件中是否存在 server.forward-headers-strategy = framework,若不存在则该方法生效。
ServerProperties
ServletWebServerFactoryAutoConfiguration 类还使用了一个 @EnableConfigurationProperties 注解,通过该注解导入了一个 ServerProperties 类,其部分源码如下。
1 |
|
我们看到,ServletWebServerFactoryAutoConfiguration 使用了一个 @EnableConfigurationProperties 注解,而 ServerProperties 类上则使用了一个 @ConfigurationProperties 注解。这其实是 Spring Boot 自动配置机制中的通用用法。 Spring Boot 中为我们提供了大量的自动配置类 XxxAutoConfiguration 以及 XxxProperties,每个自动配置类 XxxAutoConfiguration 都使用了 @EnableConfigurationProperties 注解,而每个 XxxProperties 上都使用 @ConfigurationProperties 注解。 @ConfigurationProperties 注解的作用,是将这个类的所有属性与配置文件中相关的配置进行绑定,以便于获取或修改配置,但是 @ConfigurationProperties 功能是由容器提供的,被它注解的类必须是容器中的一个组件,否则该功能就无法使用。而 @EnableConfigurationProperties 注解的作用正是将指定的类以组件的形式注入到 IOC 容器中,并开启其 @ConfigurationProperties 功能。因此,@ConfigurationProperties + @EnableConfigurationProperties 组合使用,便可以为 XxxProperties 类实现配置绑定功能。 自动配置类 XxxAutoConfiguration 负责使用 XxxProperties 中属性进行自动配置,而 XxxProperties 则负责将自动配置属性与配置文件的相关配置进行绑定,以便于用户通过配置文件修改默认的自动配置。也就是说,真正“限制”我们可以在配置文件中配置哪些属性的类就是这些 XxxxProperties 类,它与配置文件中定义的 prefix 关键字开头的一组属性是唯一对应的。
注意:XxxAutoConfiguration 与 XxxProperties 并不是一一对应的,大多数情况都是多对多的关系,即一个 XxxAutoConfiguration 可以同时使用多个 XxxProperties 中的属性,一个 XxxProperties 类中属性也可以被多个 XxxAutoConfiguration 使用。