Spring Boot是Spring家族具有划时代意义的一款产品,它发展自Spring Framework却又高于它,这种高于主要表现在其最重要的 三大特性 ,而相较于这三大特性中更为重要的便是 Spring Boot的自动配置 (AutoConfiguration)。与其说是自动,倒不如说是“智慧”,该框架看起来好像“更聪明”了。因此它也顺理成章的成为了构建微服务的基础设施,稳坐第一宝座。
Spring Boot
Spring Framework
AutoConfiguration
生活之道,在于取舍。程序设计何尝不是,任何决定都会是一把双刃剑,Spring Boot的自动配置解决了Spring Framework使用起来的众多痛点,让开发效率可以得到 指数级提升 (想一想,这不就是功德无量吗?) 。成也萧何败也萧何,也正是因为它的太智慧,倘若出了问题就会让程序设计师两眼一抹黑,无从下手。
瑕不掩瑜,Spring Boot前进的步伐浩浩荡荡,学就完了
这不,我就在前几天收到一个“求助”,希望使用@AutoConfigureBefore来 控制配置的顺序 ,但并未能如愿。本文就针对这个场景case稍作展开,讨论下使用@AutoConfigureBefore、@AutoConfigureAfter、@AutoConfigureOrder三大注解控制自动配置执行顺序的 正确姿势 。
@AutoConfigureBefore
@AutoConfigureBefore、@AutoConfigureAfter、@AutoConfigureOrder
提示:Spring Boot的自动配置是通过@EnableAutoConfiguration注解驱动的,预设是开启状态。你也可以通过spring.boot.enableautoconfiguration = false来关闭它,回退到Spring Framework时代。显然这不是本文需要讨论的内容~
spring.boot.enableautoconfiguration = false
本文将要聊的重点是Spring Boot自动配置+ 顺序控制,自动配置大家都耳熟能详,那么“首当其冲”就是知晓这个问题:配置类的执行为何需要控制顺序?
我们已经知道Spring容器它对Bean的初始化是 无序 的,我们并不能想当然的通过@Order注解来控制其执行顺序。一般来说,对于容器内普通的Bean我们只需要关注依赖关系即可,而并不需要关心其绝对的顺序,而依赖关系的管理Spring的是做得很好的,这不连 回圈依赖 它都可以搞定么。
@Order
@Configuration配置类它也是一个Bean,但对于配置类来说,某些场景下的执行顺序是 必须的 ,是需要得到保证的。比如很典型的一个非A即B的case:若容器内已经存在A了,就不要再把B放进来。这种case即使用中文理解,就能知道对A的“判断”必须要放在B的前面,否则可能导致程序出问题。
@Configuration
那么针对于配置的执行顺序,传统Spring和Spring Boot下各自是如何处理的,表现如何呢?
在传统的Spring Framework里,一个@Configuration注解标注的类就代表一个配置类,当存在多个@Configuration时,他们的 执行顺序 是由使用者靠手动指定的,就像这样:
// 手動控制Config1 Config2的順序 ApplicationContext context = new AnnotationConfigApplicationContext(Config1.class, Config2.class);
当然,你可能就疑问了说:即使在传统Spirng里,我也从没有自己使用过AnnotationConfigApplicationContext来显示载入配置啊,都是使用@Configuration定义好配置类后,点选Run一把唆的。没错,那是因为你是在web环境下使用Spring,IoC容器是借助web容器(如Tomcat等)来驱动的,Spring对此部分封装得非常好,所以做到了对使用者几乎无感知。
AnnotationConfigApplicationContext
Run
关于这部分的内容,此处就不深究了,毕竟本文重点不在这嘛。但可以给出给小结论:@Configuration配置被载入进容器的方式大体上可分为两种:
ApplicationContext
@ComponentScan
绝大多数情况下我们都是使用 自动 的方式,所以在Spring下对配置的顺序并无感知。其实这也是需求驱使,因为在传统Spring下我们并无此需求,所以对它无感是合乎逻辑的。另说一句,虽然我们并不能控制Bean的顺序,但是我们是可以干涉它的,比如:控制依赖关系、提升优先顺序、“间接”控制执行顺序...当然喽这是后面文章的内容,敬请关注。
Spring Boot下对 自动配置 的管理对比于Spring它就是黑盒,它会根据当前容器内的情况来 动态的 判断自动配置类的载入与否、以及载入的顺序,所以可以说:Spring Boot的自动配置它对顺序是有 强要求 的。需求驱使,Spring Boot给我们提供了@AutoConfigureBefore、@AutoConfigureAfter、@AutoConfigureOrder(下面统称这三个注解为“三大注解”)这三个注解来帮我们解决这种诉求。
需要注意的是:三大注解是Spring Boot提供的而非Spring Framework。其中前两个是1.0.0就有了,@AutoConfigureOrder属于1.3.0版本新增,表示绝对顺序(数字越小,优先顺序越高)。另外,这几个注解 并不互斥 ,可以同时标注在同一个@Configuration自动配置类上。
@AutoConfigureOrder
为方便大家理解,我列出一个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
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE)
在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之前执行此配置。语义层面上看,貌似没有任何问题,再次启动应用:
A_SonConfig
what a fuck。看到没,我没骗你吧,骂街了骂街了
竟然没生效?程序不会骗人,@AutoConfigureBefore的语义也没有问题,而是你使用的姿势不对,下面我会给你正确姿势。
针对以上case,要想达到预期效果,正确姿势只需要下面两步:
B_ParentConfig
META-INF/spring.factories
org.springframework.boot.autoconfigure.EnableAutoConfiguration=com.fsx.autoconfig.A_SonConfig,com.fsx.autoconfig.B_ParentConfig
再次启动应用看看,列印输出:
我是被自動掃描的配置,初始化啦.... 配置類ParentConfig構造器被執行... 配置類SonConfig構造器被執行...
完美 。符合预期,Parent终于在Son之前完成了初始化,也就是说我们的@AutoConfigureBefore注解生效了。
针对此使用姿势,虽然很正确,并不是完全没有“副作用”的,有如下细节平时也需要引起注意:
我是被自動掃描的配置,初始化啦....
spring.factories
org.springframework
ComponentScanPackageCheck
小总结,对于三大注解的正确使用姿势是应该是:请使用在你的 自动配置 里(一般是你自定义starter时使用),而不是使用在你业务工程中的@Configuration里,因为那会毫无效果。
为了更好的辅助理解,加强记忆,本文将这三大注解解析时机 简要的 絮叨一下,知道了它被解析的时机,自然就很好解释为何你那么写是无效的喽。
这三个注解的解析都是交给AutoConfigurationSorter来排序、处理的,做法类似于AnnotationAwareOrderComparator去解析排序@Order注解。核心代码如下:
AutoConfigurationSorter
AnnotationAwareOrderComparator
class AutoConfigurationSorter { // 唯一給外部呼叫的方法:返回排序好的Names,因此返回的是個List嘛(ArrayList) List<String> getInPriorityOrder(Collection<String> classNames) { ... // 先按照自然順序排一波 Collections.sort(orderedClassNames); // 在按照@AutoConfigureBefore這三個註解排一波 orderedClassNames = sortByAnnotation(classes, orderedClassNames); return orderedClassNames; } ... }
此排序器被两个地方使用到:
AutoConfigurationImportSelector
DeferredImportSelector
AutoConfigurations
这个排序的“解析/排序”过程还是比较复杂的,本文点到为止,观其大意即可。你可以简单粗暴的记住结论:@AutoConfigureBefore、@AutoConfigureAfter、@AutoConfigureOrder这三个注解只能作用于 自动配置类 ,而不能是自定义的@Configuration配置类。
关于Spring Boot自动配置顺序相关的三大注解@AutoConfigureBefore、@AutoConfigureAfter、@AutoConfigureOrder就先介绍到这了,本文主要用意是为了帮助大家规范此些“常用注解”的使用,规避一些误区,端正使用姿势,避免犯错时又丈二和尚。
我看到不少文章、生产上的程序都使用错了(估计有没有效果自己的都不知道,又或者刚好歪打正着确实是在xxx后面执行而以为生效了),希望本文能帮助到你。
原文链接:https://www.cnblogs.com/zimug/p/13264814.html