一个人的价值体现在能够帮助多少人. 自己编码好, 价值能得到很好的体现. 若你做出来的东西能够帮助别人开发, 大大减少开发的时间, 那就功德无量.
作者: A 哥(YourBatman)
公众号: BAT 的乌托邦(ID:BAT-utopia)
文末是否有彩蛋: 有
目录
前言
正文
配置类为何需要顺序?
Spring 下控制配置执行顺序
Spring Boot 下控制配置执行顺序
Spring Boot 内置的控制配置顺序举例
三大注解使用的误区(重要)
错误使用示例
三大注解使用的正确姿势
使用细节注意事项
三大注解解析时机浅析
总结
前言
各位小伙伴大家好, 我是 A 哥. Spring Boot 是 Spring 家族具有划时代意义的一款产品, 它发展自 Spring Framework 却又高于它, 这种高于主要表现在其最重要的三大特性, 而相较于这三大特性中更为重要的便是 Spring Boot 的自动配置(AutoConfiguration). 与其说是自动, 倒不如说是 "智能", 该框架看起来好像 "更聪明" 了. 因此它也顺理成章的成为了构建微服务的基础设施, 稳坐第一宝座.
生活之道, 在于取舍. 编程何尝不是, 任何决定都会是一把双刃剑, Spring Boot 的自动配置解决了 Spring Framework 使用起来的众多痛点, 让开发效率可以得到指数级提升(想一想, 这不就是功德无量吗?). 成也萧何败也萧何, 也正是因为它的太智能, 倘若出了问题就会让程序员两眼一抹黑, 无从下手.
瑕不掩瑜, Spring Boot 前进的步伐浩浩荡荡, 学就完了
这不, 我就在前几天收到一个 "求助", 希望使用 @AutoConfigureBefore 来控制配置的顺序, 但并未能如愿. 本文就针对这个场景 case 稍作展开, 讨论下使用 @AutoConfigureBefore,@AutoConfigureAfter,@AutoConfigureOrder 三大注解控制自动配置执行顺序的正确姿势.
提示: Spring Boot 的自动配置是通过 @EnableAutoConfiguration 注解驱动的, 默认是开启状态. 你也可以通过 spring.boot.enableautoconfiguration = false 来关闭它, 回退到 Spring Framework 时代. 显然这不是本文需要讨论的内容~
正文
本文将要聊的重点是 Spring Boot 自动配置 + 顺序控制, 自动配置大家都耳熟能详, 那么 "首当其冲" 就是知晓这个问题: 配置类的执行为何需要控制顺序?
配置类为何需要顺序?
我们已经知道 Spring 容器它对 Bean 的初始化是无序的, 我们并不能想当然的通过 @Order 注解来控制其执行顺序. 一般来说, 对于容器内普通的 Bean 我们只需要关注依赖关系即可, 而并不需要关心其绝对的顺序, 而依赖关系的管理 Spring 的是做得很好的, 这不连循环依赖它都可以搞定麽.
@Configuration 配置类它也是一个 Bean, 但对于配置类来说, 某些场景下的执行顺序是必须的, 是需要得到保证的. 比如很典型的一个非 A 即 B 的 case: 若容器内已经存在 A 了, 就不要再把 B 放进来. 这种 case 即使用中文理解, 就能知道对 A 的 "判断" 必须要放在 B 的前面, 否则可能导致程序出问题.
那么针对于配置的执行顺序, 传统 Spring 和 Spring Boot 下各自是如何处理的, 表现如何呢?
Spring 下控制配置执行顺序
在传统的 Spring Framework 里, 一个 @Configuration 注解标注的类就代表一个配置类, 当存在多个 @Configuration 时, 他们的执行顺序是由使用者靠手动指定的, 就像这样:
- // 手动控制 Config1 Config2 的顺序
- ApplicationContext context = new AnnotationConfigApplicationContext(Config1.class, Config2.class);
当然, 你可能就疑问了说: 即使在传统 Spirng 里, 我也从没有自己使用过 AnnotationConfigApplicationContext 来显示加载配置啊, 都是使用 @Configuration 定义好配置类后, 点击 Run 一把唆的. 没错, 那是因为你是在 web 环境下使用 Spring,IoC 容器是借助 Web 容器 (如 Tomcat 等) 来驱动的, Spring 对此部分封装得非常好, 所以做到了对使用者几乎无感知.
关于这部分的内容, 此处就不深究了, 毕竟本文重点不在这嘛. 但可以给出给小结论:@Configuration 配置被加载进容器的方式大体上可分为两种:
手动. 构建 ApplicationContext 时由构建者手动传入, 可手动控制顺序
自动. 被 @ComponentScan 自动扫描进去, 无法控制顺序
绝大多数情况下我们都是使用自动的方式, 所以在 Spring 下对配置的顺序并无感知. 其实这也是需求驱使, 因为在传统 Spring 下我们并无此需求, 所以对它无感是合乎逻辑的. 另说一句, 虽然我们并不能控制 Bean 的顺序, 但是我们是可以干涉它的, 比如: 控制依赖关系, 提升优先级,"间接" 控制执行顺序... 当然喽这是后面文章的内容, 敬请关注.
Spring Boot 下控制配置执行顺序
Spring Boot 下对自动配置的管理对比于 Spring 它就是黑盒, 它会根据当前容器内的情况来动态的判断自动配置类的加载与否, 以及加载的顺序, 所以可以说: Spring Boot 的自动配置它对顺序是有强要求的. 需求驱使, Spring Boot 给我们提供了 @AutoConfigureBefore,@AutoConfigureAfter,@AutoConfigureOrder(下面统称这三个注解为 "三大注解")这三个注解来帮我们解决这种诉求.
需要注意的是: 三大注解是 Spring Boot 提供的而非 Spring Framework. 其中前两个是 1.0.0 就有了,@AutoConfigureOrder 属于 1.3.0 版本新增, 表示绝对顺序(数字越小, 优先级越高). 另外, 这几个注解并不互斥, 可以同时标注在同一个 @Configuration 自动配置类上.
Spring Boot 内置的控制配置顺序举例
为方便大家理解, 我列出一个 Spring Boot 它自己的使用作为示例学一学. 以大家最为熟悉的 WebMvc 的自动配置场景为例:
- @Configuration(proxyBeanMethods = false)
- @AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE + 10)
- @AutoConfigureAfter({
- DispatcherServletAutoConfiguration.class, TaskExecutionAutoConfiguration.class, ValidationAutoConfiguration.class
- })
- public class WebMvcAutoConfiguration {
- ...
- }
- @Configuration(proxyBeanMethods = false)
- @AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE)
- @AutoConfigureAfter(ServletWebServerFactoryAutoConfiguration.class)
- public class DispatcherServletAutoConfiguration {
- ...
- }
- @Configuration(proxyBeanMethods = false)
- @AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE)
- public class ServletWebServerFactoryAutoConfiguration {
- ...
- }
这几个配置是 WebMVC 的核心配置, 他们之间是有顺序关系的:
WebMvcAutoConfiguration
被加载的前提是:
DispatcherServletAutoConfiguration,TaskExecutionAutoConfiguration,ValidationAutoConfiguration
这三个哥们都已经完成初始化
DispatcherServletAutoConfiguration
被加载的前提是:
ServletWebServerFactoryAutoConfiguration
已经完成初始化
ServletWebServerFactoryAutoConfiguration
被加载的前提是:
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE)
最高优先级, 也就是说它无其它依赖, 希望自己是最先被初始化的
当碰到多个配置都是最高优先级的时候, 且互相之前没有关系的话, 顺序也是不定的. 但若互相之间存在依赖关系(如本利的
DispatcherServletAutoConfiguration
和
ServletWebServerFactoryAutoConfiguration
), 那就按照相对顺序走
在 WebMvcAutoConfiguration 加载后, 在它之后其实还有很多配置会尝试执行, 例如:
- @AutoConfigureAfter(WebMvcAutoConfiguration.class)
- class FreeMarkerServletWebConfiguration extends AbstractFreeMarkerConfiguration {
- ...
- }
- @AutoConfigureAfter(WebMvcAutoConfiguration.class)
- public class GroovyTemplateAutoConfiguration {
- ...
- }
- @AutoConfigureAfter({
- WebMvcAutoConfiguration.class, WebFluxAutoConfiguration.class
- })
- public class ThymeleafAutoConfiguration {
- ...
- }
- @AutoConfigureAfter(WebMvcAutoConfiguration.class)
- public class LifecycleMvcEndpointAutoConfiguration {
- ...
- }
这些都很容易理解: 如果都不是 Web 环境, 加载一些模版引擎的并无必要嘛.
三大注解使用的误区(重要)
根据我的切身体会, 针对这三大注解, 实在有太多人把它误用了, 想用但是用了却又不生效, 于是就容易触发一波 "骂街" 操作, 其实这也是我书写本文的最大动力所在: 纠正你的错误使用, 告诉你正确姿势.
错误使用示例
我见到的非常多的小伙伴这么来使用三大注解: 我这里使用 "伪代码" 进行模拟
- @Configuration
- public class B_ParentConfig {
- B_ParentConfig() {
- System.out.println("配置类 ParentConfig 构造器被执行...");
- }
- }
- @Configuration
- public class A_SonConfig {
- A_SonConfig() {
- System.out.println("配置类 SonConfig 构造器被执行...");
- }
- }
- @Configuration
- public class C_DemoConfig {
- public C_DemoConfig(){
- System.out.println("我是被自动扫描的配置, 初始化啦....");
- }
- }
- @SpringBootApplication
- public class Application {
- public static void main(String[] args) {
- SpringApplication.run(Application.class, args).close();
- }
- }
通过名称能知道我想要的达到的效果是: ParentConfig 先加载, SonConfig 后加载.(DemoConfig 作为一个参考配置, 作为日志参考使用即可)
启动应用, 控制台打印:
配置类 SonConfig 构造器被执行...
配置类 ParentConfig 构造器被执行...
我是被自动扫描的配置, 初始化啦....
Son 优先于 Parent 被加载了, 这明显不符合要求. 因此, 我看到很多小伙伴就这么干:
- @AutoConfigureBefore(A_SonConfig.class)
- @Configuration
- public class B_ParentConfig {
- B_ParentConfig() {
- System.out.println("配置类 ParentConfig 构造器被执行...");
- }
- }
通过 @AutoConfigureBefore 控制, 表示在 A_SonConfig 之前执行此配置. 语义层面上看, 貌似没有任何问题, 再次启动应用:
配置类 SonConfig 构造器被执行...
配置类 ParentConfig 构造器被执行...
我是被自动扫描的配置, 初始化啦....
what a fuck. 看到没, 我没骗你吧, 骂街了骂街了
竟然没生效? 代码不会骗人,
@AutoConfigureBefore
的语义也没有问题, 而是你使用的姿势不对, 下面我会给你正确姿势.
三大注解使用的正确姿势
针对以上 case, 要想达到预期效果, 正确姿势只需要下面两步:
把 A_SonConfig 和 B_ParentConfig 挪动到 Application 扫描不到的包内, 切记: 一定且必须是扫描不到的包内
当前工程里增加配置
META-INF/spring.factories
, 内容为(配置里 Son 和 Parent 前后顺序对结果无影响):
org.springframework.boot.autoconfigure.EnableAutoConfiguration=com.fsx.autoconfig.A_SonConfig,com.fsx.autoconfig.B_ParentConfig
再次启动应用看看, 打印输出:
我是被自动扫描的配置, 初始化啦....
配置类 ParentConfig 构造器被执行...
配置类 SonConfig 构造器被执行...
完美. 符合预期, Parent 终于在 Son 之前完成了初始化, 也就是说我们的 @AutoConfigureBefore 注解生效了.
使用细节注意事项
针对此使用姿势, 虽然很正确, 并不是完全没有 "副作用" 的, 有如下细节平时也需要引起注意:
若你不用
@AutoConfigureBefore
这个注解, 单单就想依赖于 spring.factories 里的先后顺序的来控制实际的加载顺序, 答案是不可以, 控制不了
例子中有个小细节: 我每次都故意输出了
我是被自动扫描的配置, 初始化啦....
这句话, 可以发现被扫描进去配置实例化是在它前面(见错误示例), 而通过 spring.factories 方式进去是在它的后面(见正确姿势)
从这个小细节可以衍生得到结论: Spring Boot 的自动配置均是通过 spring.factories 来指定的, 它的优先级最低(执行时机是最晚的); 通过扫描进来的一般都是你自己自定义的配置类, 所以优先级是最高的, 肯定在自动配置之前加载
从这你应该学到: 若你要指定扫描的包名, 请千万不要扫描到形如
org.springframework
这种包名, 否则 "天下大乱"(当然喽为了防止这种情况出现, Spring Boot 做了容错的. 它有一个类专门检测这个 case 防止你配置错了, 具体参见
ComponentScanPackageCheck
默认实现)
请尽量不要让自动配置类既被扫描到了, 又放在 spring.factories 配置了, 否则后者会覆盖前者, 很容易造成莫名其妙的错误
小总结, 对于三大注解的正确使用姿势是应该是: 请使用在你的自动配置里(一般是你自定义 starter 时使用), 而不是使用在你业务工程中的 @Configuration 里, 因为那会毫无效果.
三大注解解析时机浅析
为了更好的辅助理解, 加强记忆, 本文将这三大注解解析时机简要的絮叨一下, 知道了它被解析的时机, 自然就很好解释为何你那么写是无效的喽.
这三个注解的解析都是交给 AutoConfigurationSorter 来排序, 处理的, 做法类似于 AnnotationAwareOrderComparator 去解析排序 @Order 注解. 核心代码如下:
- class AutoConfigurationSorter {
- // 唯一给外部调用的方法: 返回排序好的 Names, 因此返回的是个 List 嘛(ArrayList)
- List<String> getInPriorityOrder(Collection<String> classNames) {
- ...
- // 先按照自然顺序排一波
- Collections.sort(orderedClassNames);
- // 在按照 @AutoConfigureBefore 这三个注解排一波
- orderedClassNames = sortByAnnotation(classes, orderedClassNames);
- return orderedClassNames;
- }
- ...
- }
此排序器被两个地方使用到:
AutoConfigurationImportSelector
:Spring 自动配置处理器, 用于加载所有的自动配置类. 它实现了
DeferredImportSelector
接口: 这也顺便解释了为何自动配置是最后执行的原因~
AutoConfigurations: 表示自动配置 @Configuration 类.
这个排序的 "解析 / 排序" 过程还是比较复杂的, 本文点到为止, 观其大意即可. 你可以简单粗暴的记住结论:@AutoConfigureBefore,@AutoConfigureAfter,@AutoConfigureOrder 这三个注解只能作用于自动配置类, 而不能是自定义的 @Configuration 配置类.
总结
关于 Spring Boot 自动配置顺序相关的三大注解 @AutoConfigureBefore,@AutoConfigureAfter,@AutoConfigureOrder 就先介绍到这了, 本文主要用意是为了帮助大家规范此些 "常用注解" 的使用, 规避一些误区, 端正使用姿势, 避免犯错时又丈二和尚.
我看到不少文章, 生产上的代码都使用错了(估计有没有效果自己的都不知道, 又或者刚好歪打正着确实是在 xxx 后面执行而以为生效了), 希望本文能帮助到你.
来源: https://www.cnblogs.com/yourbatman/p/13258700.html