Spring Boot 自动化配置
自动化配置绝对是Spring 4.0最重要的新特性,没有之一(虽然现在已经到6.0了)。正是基于自动化配置,才有了SpringBoot 1.0版本,实现了开箱即用,大幅提升了开发效率。
自动化配置是做什么的
最早的时候写Spring,都要一个个手动配置成bean才能用,而且还是用xml文件配置,代码又长又不好记。后来又出现了用Java代码的方式配置bean。这些都是手动配置。
Spring也知道这样太复杂了,就增加了一个Scan机制,可以通过目录扫描的机制把整个项目下的所有bean都扫描出来。但这种方式依然有缺陷,只能扫自己代码目录下的bean,扫不到引入的jar包里的bean。
所谓的自动化配置,就是一种可以自动发现jar包内的bean的机制,这样第三方jar包引入后,无需额外的bean配置,真正做到了开箱即用。
但随着jar包引入越来越多,bean也越来越多,存在更多冲突的可能。因此自动化配置必须考虑bean冲突问题,大多数自动化配置的bean都是带条件配置的,不满足条件就不触发配置,这样就能做到最大程度的避免冲突。
自动化配置原理解析
- 自动化配置是通过引入
@EnableAutoConfiguration注解实现的 @SpringBootApplication中已经包含了该注解,因此SpringBoot应用自带自动化配置特性- 在 spring-boot-autoconfigure-x.x.x.RELEASE.jar/META-INF/spring.factories 文件的 org.springframework.boot.autoconfigure.EnableAutoConfiguration 配置项中定义了候选配置类。之所以说是候选,是因为还要根据配置条件来决定
- 配置条件定义在 spring-boot-autoconfigure-x.x.x.RELEASE.jar/META-INF/spring-autoconfigure-metadata.properties 文件中
- 读取所有的配置条件,加上配置类中声明的条件,去重,配置项排序:先按字典顺序,再按
@AutoConfigureOrder,再考虑@AutoConfigureBefore和@AutoConfigureAfter - 根据顺序加载候选配置类,最终满足条件的候选配置加载到spring上下文
SpringBoot 2.7
在SpringBoot 2.7+中,已经把候选配置类移到src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports文件中了,这个文件里每一行写一个配置类。
自动化配置的加载时机
自动化配置是在SpringBoot生命周期的started阶段完成的,代码入口是ConfigurationClassParser.parse(),这里会解析所有配置类,当解析到注解中包含@Import(XXXImportSelector.class)时,就会调用相应的ImportSelector.selectImports()来获取具体需要自动化配置的类。
再看@EnableAutoConfiguration注解,里面包含了EnableAutoConfigurationImportSelector,自动化配置的核心代码都在这里。
关于ImportSelector和DeferredImportSelector
ImportSelector的作用是筛选出所有需要进行配置的类DeferredImportSelector继承了ImportSelector,可以实现延迟配置
这里所谓的“延迟”是指延迟到其他所有的bean都注册完毕(相关代码在ConfigurationClassParser.parse())。
EnableAutoConfigurationImportSelector实现了DeferredImportSelector,因此自动化配置的bean一定是在最后加载。
条件化配置
在 org.springframework.boot.autoconfigure.condition 包中定义了许多与条件相关的注解,举例如下:
| 注解 | 含义 |
|---|---|
| @ConditionalOnProperty | 当某个Property存在并等于某个值时配置 |
| @ConditionalOnJava | 当Java版本满足条件时配置 |
| @ConditionalOnBean | 当Spring上下文中存在某个bean时才进行配置 |
| @ConditionalOnClass | 当classpath中存在某个类时才进行配置 |
在 org.springframework.boot.autoconfigure 包中定义了一些与配置顺序相关的注解,举例如下:
| 注解 | 含义 |
|---|---|
| @AutoConfigureBefore | 表示该配置类在指定的类之前配置 |
| @AutoConfigureAfter | 表示该配置类在指定的类之后配置 |
| @AutoConfigureOrder | 自动配置顺序 |
条件可以写在@Configuration、@Bean或 META-INF/spring-autoconfigure-metadata.properties 文件中。
举个栗子🌰
// WebMVC的自动化配置类,这个类有在spring.factories中被定义
@Configuration(proxyBeanMethods = false)
// 只有在应用类型是Servlet时才配置
@ConditionalOnWebApplication(type = Type.SERVLET)
// 当classpath中存在以下几个类时进行配置
@ConditionalOnClass({ Servlet.class, DispatcherServlet.class, WebMvcConfigurer.class })
// 当BeanFactory中不存在WebMvcConfigurationSupport时配置(这个类用于覆盖默认web配置)
@ConditionalOnMissingBean(WebMvcConfigurationSupport.class)
// 定义配置顺序
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE + 10)
// 定义配置顺序
@AutoConfigureAfter({ DispatcherServletAutoConfiguration.class, TaskExecutionAutoConfiguration.class,
ValidationAutoConfiguration.class })
public class WebMvcAutoConfiguration {
// ......
}条件化配置与profile的区别
两者都可以实现在满足一定条件下对bean进行配置,但区别如下:
- profile在spring 3.0就支持了,条件化配置在spring 4.0才支持(公司现在还在使用spring3,所以别无选择😭)
- profile相当于给bean打标签,开关也是基于标签的,灵活度较低,当标签数量较多时,还是需要相当的维护成本
- 条件化配置更加灵活,支持多种条件,且条件直接写在配置类上,代码可读性上会更好
- 在使用场景上,条件化配置更多适用于技术框架等,需要把jar包提供给其他人使用时,条件化配置可以有效减少使用方的配置;而profile更多适用于项目内部,例如对不同的运行环境打上标签,每个环境设置不同的数据库连接配置等。
- profile可以在启动脚本中设置,也就是在运行时才决定,当然也可以在编译时决定。但是条件化配置好像只能在编译时决定。
扩展阅读
其实 @Conditional 注解才是真正的核心,当spring提供的自动化配置注解都无法满足需求时,不妨可以考虑使用 @Conditional。