使用@AutoConfigureBefore、After、Order调整Spring Boot自动配置顺序
前言
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.yourbatman.cn/x2y/3aa2234d.html
欢迎关注我的博客,里面有很多精品合集
- 本文转载注明出处(必须带连接,不能只转文字):字母哥博客。
觉得对您有帮助的话,帮我点赞、分享!您的支持是我不竭的创作动力! 。另外,笔者最近一段时间输出了如下的精品内容,期待您的关注。
- 《手摸手教你学Spring Boot2.0》
- 《Spring Security-JWT-OAuth2一本通》
- 《实战前后端分离RBAC权限管理系统》
- 《实战SpringCloud微服务从青铜到王者》
- 《VUE深入浅出系列》
使用@AutoConfigureBefore、After、Order调整Spring Boot自动配置顺序的更多相关文章
- Spring boot 自动配置自定义配置文件
示例如下: 1. 新建 Maven 项目 properties 2. pom.xml <project xmlns="http://maven.apache.org/POM/4 ...
- Spring Boot自动配置原理(转)
第3章 Spring Boot自动配置原理 3.1 SpringBoot的核心组件模块 首先,我们来简单统计一下SpringBoot核心工程的源码java文件数量: 我们cd到spring-boot- ...
- Spring Boot 自动配置的原理、核心注解以及利用自动配置实现了自定义 Starter 组件
本章内容 自定义属性快速入门 外化配置 自动配置 自定义创建 Starter 组件 摘录:读书是读完这些文字还要好好用心去想想,写书也一样,做任何事也一样 图 2 第二章目录结构图 第 2 章 Spr ...
- Springboot 系列(三)Spring Boot 自动配置原理
注意:本 Spring Boot 系列文章基于 Spring Boot 版本 v2.1.1.RELEASE 进行学习分析,版本不同可能会有细微差别. 前言 关于配置文件可以配置的内容,在 Spring ...
- Spring Boot自动配置与Spring 条件化配置
SpringBoot自动配置 SpringBoot的自动配置是一个运行时(应用程序启动时)的过程,简化开发时间,无需浪费时间讨论具体的Spring配置,只需考虑如何利用SpringBoot的自动配置即 ...
- Spring Boot自动配置原理、实战
Spring Boot自动配置原理 Spring Boot的自动配置注解是@EnableAutoConfiguration, 从上面的@Import的类可以找到下面自动加载自动配置的映射. org.s ...
- Spring Boot自动配置
Spring Boot自动配置原理 Spring Boot的自动配置注解是@EnableAutoConfiguration, 从上面的@Import的类可以找到下面自动加载自动配置的映射. org.s ...
- Spring Boot自动配置原理与实践(一)
前言 Spring Boot众所周知是为了简化Spring的配置,省去XML的复杂化配置(虽然Spring官方推荐也使用Java配置)采用Java+Annotation方式配置.如下几个问题是我刚开始 ...
- Spring Boot自动配置如何工作
通过使用Mongo和MySQL DB实现的示例,深入了解Spring Boot的@Conditional注释世界. 在我以前的文章“为什么选择Spring Boot?”中,我们讨论了如何创建Sprin ...
随机推荐
- php symfony/var-dumper 打印插件
$records = array( array( 'id' => 2135, 'first_name' => 'John', 'last_name' => 'Doe', ), arr ...
- 指定web默认首页,导致访问路径的问题
今天写了一个登陆页面,登陆成功跳转时,url中的路径不对 这是目录结构 |-web |---login |-----login.jsp |---success |-----success.jsp 这是 ...
- Oracle数据迁移后由列的直方图统计信息引起的执行计划异常
(一)问题背景 在使用impdp进行数据导入的时候,往往在导入表和索引的统计信息的时候,速度非常慢,因此我在使用impdp进行导入时,会使用exclude=table_statistics排除表的统计 ...
- 你都这么拼了,面试官TM怎么还是无动于衷?
前言 面试,对于每个人而然并不陌生,可以说是必须经历的一个过程了,小到一场考试,大到企业面试,甚至大型选秀...... 有时自己明明很努力了,但偏偏会在面试环节出了插曲,比如,紧张就是最容易出现的了. ...
- 【百度前端学院 Day4】背景边框列表链接和更复杂的选择器
1. 背景 背景指的是元素内容.内边距和边界下层的区域(可用background-clip修改) background-color 背景色 background-image 背景图片(url) b ...
- 在Xcode11上开发“面向低于iOS13版本的App”时的一些注意点
相关知识: https://blog.csdn.net/BUG_delete/article/details/103699563 'AppDelegate' is only available in ...
- CollectionView的cell长按事件实现
原生cell没有长按事件,我们需要使用手势识别来绑定CollectionView.创建并绑定CollectionView如下: (void)viewDidLoad { [super viewDidLo ...
- http的几种请求方法
1.HTTP请求方法有以下8种方法:OPTIONS.GET.HEAD.POST.PUT.DELETE.TRACE和CONNECT. GET GET请求会显示请求指定的资源.一般来说GET方法应该只用于 ...
- NAT网络地址转化和DHCP
DHCP(Dynamic Host Configuration Protocol,动态主机配置协议)通常被应用在大型的局域网络环境中,主要作用是集中的管理.分配IP地址,使网络环境中的主机动态的获得I ...
- 【案例演示】JVM之强引用、软引用、弱引用、虚引用
1.背景 想要理解对象什么时候回收,就要理解到对象引用这个概念,于是有了下文 2.java中引用对象结构图 3.引用详解 3.1.什么是强引用 a.当内存不足,JVM开始垃圾回收,对于强引用的对象,就 ...