配置类需要标注 @Configuration 却不知原因?那这次就不能给你涨薪喽
专注Java领域分享、成长,拒绝浅尝辄止。关注公众号【BAT的乌托邦】开启专栏式学习,拒绝浅尝辄止。本文 https://www.yourbatman.cn 已收录,里面一并有Spring技术栈、MyBatis、中间件等小而美的专栏供以学习哦。
前言
各位小伙伴大家好,我是A哥。这是继上篇文章:真懂Spring的@Configuration配置类?你可能自我感觉太良好 的原理/源码解释篇。按照本公众号的定位,原理一般跑不了,虽然很枯燥,但还得做,毕竟做难事必有所得,真的掌握了才有底气谈涨薪嘛。
Tips:鉴于经常有些同学无法区分某个功能/某项能力属于Spring Framework
的还是Spring Boot
,你可以参考文章里的【版本约定】目录,那里会说明本文的版本依赖,也就是功能所属喽。比如本文内容它就属于Spring Framework
,和Spring Boot
木有关系。
版本约定
本文内容若没做特殊说明,均基于以下版本:
JDK:
1.8
Spring Framework:
5.2.2.RELEASE
正文
Spring的IoC就像个“大熔炉”,什么都当作Bean放在里面。然而,虽然它们都放在了一起,但是实际在功能上是有区别的,比如我们熟悉的BeanPostProcessor
就属于后置处理器功能的Bean,还有本文要讨论的@Configuration
配置Bean也属于一种特殊的组件。
判断一个Bean是否是Bean的后置处理器很方便,只需看它是否实现了BeanPostProcessor
接口即可;那么如何去确定一个Bean是否是@Configuration配置Bean呢?若是,如何区分是Full模式还是Lite模式呢?这便就是本文将要讨论的内容。
如何判断一个组件是否是@Configuration配置?
首先需要明确:@Configuration
配置前提必须是IoC管理的一个组件(也就是常说的Bean)。Spring使用BeanDefinitionRegistry
注册中心管理着所有的Bean定义信息,那么对于这些Bean信息哪些属于@Configuration
配置呢,这是需要甄选出来的。
判断一个Bean是否是@Configuration
配置类这个逻辑统一交由ConfigurationClassUtils
这个工具类去完成。
ConfigurationClassUtils工具类
见名之意,它是和配置有关的一个工具类,提供几个静态工具方法供以使用。它是Spring 3.1
新增,对于它的作用,官方给的解释是:用于标识@Configuration
类的实用程序(Utilities)。它主要提供了一个方法:checkConfigurationClassCandidate()
用于检查给定的Bean定义是否是配置类的候选对象(或者在配置/组件类中声明的嵌套组件类),并做相应的标记。
checkConfigurationClassCandidate()
它是一个public static工具方法,用于判断某个Bean定义是否是@Configuration
配置。
步骤总结:
根据Bean定义信息解析成为一个注解元数据对象
AnnotationMetadata metadata
1. 可能是个AnnotatedBeanDefinition
,也可能是个StandardAnnotationMetadata
根据注解元数据metadata判断是否是个
@Configuration
配置类,有如下三种可能case:
1. 标注有@Configuration
注解**并且**该注解的proxyBeanMethods = false
,那么mark一下它是Full模式的配置。否则进入下一步判断
2. 标注有@Configuration
注解或者符合Lite模式的条件(上文有说一共有5种可能是Lite模式,源码处在isConfigurationCandidate(metadata)
这个方法里表述),那么mark一下它是Lite模式的配置。否则进入下一步判断
3. 不是配置类,并且返回结果return false
能进行到这一步,说明该Bean肯定是个配置类了(Full模式或者Lite模式),那就取出其
@Order
值(若有的话),然后mark进Bean定义里面去
**这个mark动作很有意义:后面判断一个配置类是Full模式还是Lite模式,甚至判断它是否是个配置类均可通过beanDef.getAttribute(CONFIGURATION_CLASS_ATTRIBUTE)
这样完成判断**。
方法使用处
知晓了checkConfigurationClassCandidate()
能够判断一个Bean(定义)是否是一个配置类,那么它在什么时候会被使用呢?通过查找可以发现它被如下两处使用到:
使用处:
ConfigurationClassPostProcessor.processConfigBeanDefinitions()
处理配置Bean定义阶段。
ConfigurationClassPostProcessor
是个BeanDefinitionRegistryPostProcessor
,会在BeanFactory
**准备好后**执行生命周期方法。因此自然而然的,checkConfigurationClassCandidate()
会在此阶段调用,用于区分出哪些是配置Bean。
值得注意的是:ConfigurationClassPostProcessor
的执行时期是非常早期的(BeanFactory
准备好后就执行嘛),这个时候容器内的Bean定义很少。这个时候只有**主配置类**才被注册了进来,那些想通过@ComponentScan
扫进来的配置类都还没到“时间”,这个时间节点很重要,请注意区分。为了方便你理解,我分别把Spring和Spring Boot在此阶段的Bean定义信息截图展示如下:
以上是Spring环境,对应代码为:
以上是Spring Boot环境,对应代码为:
相比之下,Spring Boot里多了
internalCachingMetadataReaderFactory
这个Bean定义。原因是SB定义了一个CachingMetadataReaderFactoryPostProcessor
把它放进去的,由于此Processor也是个BeanDefinitionRegistryPostProcessor
并且order值为Ordered.HIGHEST_PRECEDENCE
,所以它会优先于ConfigurationClassPostProcessor
执行把它注册进去~
使用处:
ConfigurationClassParser.doProcessConfigurationClass()
**解析**@Configuration
配置类阶段。所处的大阶段同上使用处,仍旧是ConfigurationClassPostProcessor#postProcessBeanDefinitionRegistry()
阶段
这个方法是Spring对配置类解析的最核心步骤,通过它顺带也能够解答你的疑惑了吧:为何你仅需在类上标注一个@Configuration
注解即可让它成为一个配置类?因为被Scan扫描进去了嘛~
通过以上两个使用处的分析和对比,对于@Configuration
配置类的理解,你至少应该掌握了如下讯息:
@Configuration
配置类肯定是个组件,存在于IoC容器里@Configuration
配置类是有主次之分的,主配置类是驱动整个程序的入口,可以是一个,也可以是多个(若存在多个,支持使用@Order排序)我们平时一般只书写次配置类(而且一般写多个),它一般是借助主配置类的
@ComponentScan
能力完成加载进而解析的(当然也可能是@Import
、又或是被其它次配置类驱动的)配置类可以存在嵌套(如内部类),继承,实现接口等特性
聊完了最为重要的checkConfigurationClassCandidate()
方法,当然还有必要看看ConfigurationClassUtils
的另一个工具方法isConfigurationCandidate()
。
isConfigurationCandidate()
它是一个public static工具方法,通过给定的注解元数据信息来判断它是否是一个Configuration
。
步骤总结:
若是接口类型(含注解类型),直接不予考虑,返回false。否则继续判断
若此类上标注有
@Component、@ComponentScan、@Import、@ImportResource
任意一个注解,就判断成功返回true。否则继续判断到此步,就说明此类上没有标注任何注解。若存在@Bean方法,返回true,否则返回false。
**需要特别特别特别注意的是:此方法它的并不考虑@Configuration
注解,是“轻量级”判断,这是它和checkConfigurationClassCandidate()
方法的最主要区别**。当然,后者依赖于前者,依赖它来根据注解元数据判断是否是Lite模式的配置。
Spring 5.2.0版本变化说明
因为本文的讲解和代码均是基于Spring 5.2.2.RELEASE
的,而并不是所有小伙伴都会用到这么新的版本。关于此部分的实现,以Spring 5.2.0版本为分界线实现上有些许差异,所以在此处做出说明。
proxyBeanMethods属性的作用
proxyBeanMethods
属性是Spring 5.2.0版本为@Configuration
注解新增加的一个属性:
它的作用是:是否允许代理@Bean方法。说白了:决定此配置使用Full模式还是Lite模式。为了保持向下兼容,proxyBeanMethods
的默认值是true,使用Full模式配置。
Spring 5.2提出了这个属性项,是期望你在已经了解了它的作用之后,显示的把它置为false的,因为在云原生将要到来的今天,启动速度方面Spring一直在做着努力,也希望你能配合嘛。这不Spring Boot
就“配合”得很好,它在2.2.0版本(依赖于Spring 5.2.0)起就把它的所有的自动配置类的此属性改为了false,即@Configuration(proxyBeanMethods = false)
。
Full模式/Lite模式实现上的差异
由于Spring 5.2.0新增了proxyBeanMethods
属性来控制模式,因此实现上也有些许诧异,请各位注意甄别:
Spring 5.2.0+版本判断实现:
Spring 5.2.0-版本判断实现:
思考题?
既然
isConfigurationCandidate()
判断方法是为checkConfigurationClassCandidate()
服务,那Spring为何也把它设计为public static呢?ConfigurationClassUtils
里还存在对@Order
顺序的解析方法,不是说Spring的Bean是无序的吗?这又如何理解呢?
总结
本文作为上篇文章的续篇,解释了@Configuration配置的Full模式和Lite模式的判断原理,同时顺带的也介绍了什么叫主配置配和次配置类,这个概念(虽然官方并不这么叫)对你理解Spring Framework是非常有帮助的。如果你使用是基于Spring 5.2.0+的版本,在了解了这两篇文章内容的基础上,建议你的配置类均采用Lite模式去做,即显示设置proxyBeanMethods = false
。
另外关于此部分内容,有些更为感兴趣的小伙伴问到:为什么Full模式下通过方法调用指向的仍旧是原来的Bean,保证了只会执行一次呢?开启的是Full模式这只是表象原因,想要回答此问题需要涉及到CGLIB增强实现的深水区内容,为了满足这些好奇(好学)的娃子,计划会在下篇文章继续再拿一篇专程讲解(预计篇幅不短,万字以上),你可订阅我的公众号持续保持关注。
---------
关注A哥
原创不易,码字更不易。关注A哥的公众号【BAT的乌托邦】,开启有深度的专栏式学习,拒绝浅尝辄止
专栏式学习,我们是认真的(关注公众号回复“知识星球”领券后再轻装入驻)
加A哥好友(fsx641385712),备注“Java入群”邀你进入【Java高工、架构师】系列纯纯纯技术群
版权声明: 本文为 InfoQ 作者【YourBatman】的原创文章。
原文链接:【http://xie.infoq.cn/article/34f59f86018d7ecd40d9237d7】。文章转载请联系作者。
评论