专一Java领域分享、成长,拒绝浅尝辄止。关注公众号【 BAT的乌托邦】开启专栏式学习,拒绝浅尝辄止。本文 https://www.yourbatman.cn 已收录,里面一并有Spring技术栈、MyBatis、中间件等小而美的专栏供以学习哦。
各位小伙伴你们好,我是A哥。这是继上篇文章:真懂Spring的@Configuration配置类?你可能自我感受太良好 的原理/源码解释篇。按照本公众号的定位,原理通常跑不了,虽然很枯燥,但还得作,毕竟作难事必有所得,真的掌握了才有底气谈涨薪嘛。java
Tips:鉴于常常有些同窗没法区分某个功能/某项能力属于Spring Framework
的仍是Spring Boot
,你能够参考文章里的【版本约定】目录,那里会说明本文的版本依赖,也就是功能所属喽。好比本文内容它就属于Spring Framework
,和Spring Boot
木有关系。架构
本文内容若没作特殊说明,均基于如下版本:工具
1.8
5.2.2.RELEASE
Spring的IoC就像个“大熔炉”,什么都看成Bean放在里面。然而,虽然它们都放在了一块儿,可是实际在功能上是有区别的,好比咱们熟悉的BeanPostProcessor
就属于后置处理器功能的Bean,还有本文要讨论的@Configuration
配置Bean也属于一种特殊的组件。post
判断一个Bean是不是Bean的后置处理器很方便,只需看它是否实现了BeanPostProcessor
接口便可;那么如何去肯定一个Bean是不是@Configuration配置Bean呢?如果,如何区分是Full模式仍是Lite模式呢?这便就是本文将要讨论的内容。学习
首先须要明确:@Configuration
配置前提必须是IoC管理的一个组件(也就是常说的Bean)。Spring使用BeanDefinitionRegistry
注册中心管理着全部的Bean定义信息,那么对于这些Bean信息哪些属于@Configuration
配置呢,这是须要甄选出来的。this
判断一个Bean是不是@Configuration
配置类这个逻辑统一交由ConfigurationClassUtils
这个工具类去完成。spa
见名之意,它是和配置有关的一个工具类,提供几个静态工具方法供以使用。它是Spring 3.1
新增,对于它的做用,官方给的解释是:用于标识@Configuration
类的实用程序(Utilities)。它主要提供了一个方法:checkConfigurationClassCandidate()
用于检查给定的Bean定义是不是配置类的候选对象(或者在配置/组件类中声明的嵌套组件类),并作相应的标记。debug
它是一个public static工具方法,用于判断某个Bean定义是不是@Configuration
配置。设计
ConfigurationClassUtils: public static boolean checkConfigurationClassCandidate(BeanDefinition beanDef, MetadataReaderFactory metadataReaderFactory) { ... // 根据Bean定义信息,拿到器对应的注解元数据 AnnotationMetadata metadata = xxx; ... // 根据注解元数据判断该Bean定义是不是配置类。如果:那是Full模式仍是Lite模式 Map<String, Object> config = metadata.getAnnotationAttributes(Configuration.class.getName()); if (config != null && !Boolean.FALSE.equals(config.get("proxyBeanMethods"))) { beanDef.setAttribute(CONFIGURATION_CLASS_ATTRIBUTE, CONFIGURATION_CLASS_FULL); } else if (config != null || isConfigurationCandidate(metadata)) { beanDef.setAttribute(CONFIGURATION_CLASS_ATTRIBUTE, CONFIGURATION_CLASS_LITE); } else { return false; } ... // 到这。它确定是一个完整配置(Full or Lite) 这里进一步把@Order排序值放上去 Integer order = getOrder(metadata); if (order != null) { beanDef.setAttribute(ORDER_ATTRIBUTE, order); } return true; }
步骤总结:3d
根据Bean定义信息解析成为一个注解元数据对象AnnotationMetadata metadata
AnnotatedBeanDefinition
,也多是个StandardAnnotationMetadata
根据注解元数据metadata判断是不是个@Configuration
配置类,有以下三种可能case:
@Configuration
注解而且该注解的proxyBeanMethods = false
,那么mark一下它是Full模式的配置。不然进入下一步判断@Configuration
注解或者符合Lite模式的条件(上文有说一共有5种多是Lite模式,源码处在isConfigurationCandidate(metadata)
这个方法里表述),那么mark一下它是Lite模式的配置。不然进入下一步判断return false
@Order
值(如有的话),而后mark进Bean定义里面去这个mark动做颇有意义:后面判断一个配置类是Full模式仍是Lite模式,甚至判断它是不是个配置类都可经过beanDef.getAttribute(CONFIGURATION_CLASS_ATTRIBUTE)
这样完成判断。
知晓了checkConfigurationClassCandidate()
可以判断一个Bean(定义)是不是一个配置类,那么它在何时会被使用呢?经过查找能够发现它被以下两处使用到:
ConfigurationClassPostProcessor.processConfigBeanDefinitions()
处理配置Bean定义阶段。ConfigurationClassPostProcessor: public void processConfigBeanDefinitions(BeanDefinitionRegistry registry) { // 拿出当前全部的Bean定义信息,一个个的检查是不是配置类 String[] candidateNames = registry.getBeanDefinitionNames(); for (String beanName : candidateNames) { BeanDefinition beanDef = registry.getBeanDefinition(beanName); if (beanDef.getAttribute(ConfigurationClassUtils.CONFIGURATION_CLASS_ATTRIBUTE) != null) { logger.debug("Bean definition has already been processed as a configuration class: " + beanDef); } // 若是该Bean定义不是配置类,那就继续判断一次它是不是配置类,如果就加入结果集合里 else if (ConfigurationClassUtils.checkConfigurationClassCandidate(beanDef, this.metadataReaderFactory)) { configCandidates.add(new BeanDefinitionHolder(beanDef, beanName)); } } ... }
ConfigurationClassPostProcessor
是个BeanDefinitionRegistryPostProcessor
,会在BeanFactory
准备好后执行生命周期方法。所以天然而然的,checkConfigurationClassCandidate()
会在此阶段调用,用于区分出哪些是配置Bean。
值得注意的是:ConfigurationClassPostProcessor
的执行时期是很是早期的(BeanFactory
准备好后就执行嘛),这个时候容器内的Bean定义不多。这个时候只有主配置类才被注册了进来,那些想经过@ComponentScan
扫进来的配置类都还没到“时间”,这个时间节点很重要,请注意区分。为了方便你理解,我分别把Spring和Spring Boot在此阶段的Bean定义信息截图展现以下:
以上是Spring环境,对应代码为:
new AnnotationConfigApplicationContext(AppConfig.class);
以上是Spring Boot环境,对应代码为:
@SpringBootApplication public class Boot2Demo1Application { public static void main(String[] args) { SpringApplication.run(Boot2Demo1Application.class, args); } }
相比之下,Spring Boot里多了internalCachingMetadataReaderFactory
这个Bean定义。缘由是SB定义了一个CachingMetadataReaderFactoryPostProcessor
把它放进去的,因为此Processor也是个BeanDefinitionRegistryPostProcessor
而且order值为Ordered.HIGHEST_PRECEDENCE
,因此它会优先于ConfigurationClassPostProcessor
执行把它注册进去~
ConfigurationClassParser.doProcessConfigurationClass()
解析 @Configuration
配置类阶段。所处的大阶段同上使用处,仍旧是ConfigurationClassPostProcessor#postProcessBeanDefinitionRegistry()
阶段ConfigurationClassParser: @Nullable protected final SourceClass doProcessConfigurationClass(ConfigurationClass configClass, SourceClass sourceClass) throws IOException { ... // 先解析nested内部类(内部类会存在@Bean方法嘛~) ... // 解析@PropertySource资源,加入到environment环境 ... // 解析@ComponentScan注解,把组件扫描进来 scannedBeanDefinitions = ComponentScanAnnotationParser.parse(componentScan, ...); // 把扫描到的Bean定义信息依旧须要一个个的判断,是不是配置类 // 如果配置类,就继续看成一个@Configuration配置类来解析parse() 递归嘛 for (BeanDefinitionHolder holder : scannedBeanDefinitions) { ... if (ConfigurationClassUtils.checkConfigurationClassCandidate(bdCand, this.metadataReaderFactory)) { parse(bdCand.getBeanClassName(), holder.getBeanName()); } } ... // 解析@Import注解 ... // 解析@ImportResource注解 ... // 解析当前配置里配置的@Bean方法 ... // 解析接口默认方法(由于配置类可能实现接口,而后接口默认方法可能标注有@Bean ) ... // 处理父类(递归,直到父类为java.打头的为止) }
这个方法是Spring对配置类解析的最核心步骤,经过它顺带也可以解答你的疑惑了吧:为什么你仅需在类上标注一个@Configuration
注解便可让它成为一个配置类?由于被Scan扫描进去了嘛~
经过以上两个使用处的分析和对比,对于@Configuration
配置类的理解,你至少应该掌握了以下讯息:
@Configuration
配置类确定是个组件,存在于IoC容器里@Configuration
配置类是有主次之分的,主配置类是驱动整个程序的入口,能够是一个,也能够是多个(若存在多个,支持使用@Order排序)@ComponentScan
能力完成加载进而解析的(固然也多是@Import
、又或是被其它次配置类驱动的)聊完了最为重要的checkConfigurationClassCandidate()
方法,固然还有必要看看ConfigurationClassUtils
的另外一个工具方法isConfigurationCandidate()
。
它是一个public static工具方法,经过给定的注解元数据信息来判断它是不是一个Configuration
。
ConfigurationClassUtils: static { candidateIndicators.add(Component.class.getName()); candidateIndicators.add(ComponentScan.class.getName()); candidateIndicators.add(Import.class.getName()); candidateIndicators.add(ImportResource.class.getName()); } public static boolean isConfigurationCandidate(AnnotationMetadata metadata) { // 不考虑接口 or 注解 说明:注解的话也是一种“特殊”的接口哦 if (metadata.isInterface()) { return false; } // 只要该类上标注有以上4个注解任意一个,都算配置类 for (String indicator : candidateIndicators) { if (metadata.isAnnotated(indicator)) { return true; } } // 若一个注解都没标注,那就看有木有@Bean方法 如有那也算配置类 return metadata.hasAnnotatedMethods(Bean.class.getName()); }
步骤总结:
@Component、@ComponentScan、@Import、@ImportResource
任意一个注解,就判断成功返回true。不然继续判断须要特别特别特别注意的是:此方法它的并不考虑@Configuration
注解,是“轻量级”判断,这是它和checkConfigurationClassCandidate()
方法的最主要区别。固然,后者依赖于前者,依赖它来根据注解元数据判断是不是Lite模式的配置。
由于本文的讲解和代码均是基于Spring 5.2.2.RELEASE
的,而并非全部小伙伴都会用到这么新的版本。关于此部分的实现,以Spring 5.2.0版本为分界线实现上有些许差别,因此在此处作出说明。
proxyBeanMethods
属性是Spring 5.2.0版本为@Configuration
注解新增长的一个属性:
@Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Documented @Component public @interface Configuration { @AliasFor(annotation = Component.class) String value() default ""; // @since 5.2 boolean proxyBeanMethods() default true; }
它的做用是:是否容许代理@Bean方法。说白了:决定此配置使用Full模式仍是Lite模式。为了保持向下兼容,proxyBeanMethods
的默认值是true,使用Full模式配置。
Spring 5.2提出了这个属性项,是指望你在已经了解了它的做用以后,显示的把它置为false的,由于在云原生将要到来的今天,启动速度方面Spring一直在作着努力,也但愿你能配合嘛。这不Spring Boot
就“配合”得很好,它在2.2.0版本(依赖于Spring 5.2.0)起就把它的全部的自动配置类的此属性改成了false,即@Configuration(proxyBeanMethods = false)
。
因为Spring 5.2.0新增了proxyBeanMethods
属性来控制模式,所以实现上也有些许诧异,请各位注意甄别:
Spring 5.2.0+版本判断实现:
ConfigurationClassUtils: Map<String, Object> config = metadata.getAnnotationAttributes(Configuration.class.getName()); if (config != null && !Boolean.FALSE.equals(config.get("proxyBeanMethods"))) { beanDef.setAttribute(CONFIGURATION_CLASS_ATTRIBUTE, CONFIGURATION_CLASS_FULL); } else if (config != null || isConfigurationCandidate(metadata)) { beanDef.setAttribute(CONFIGURATION_CLASS_ATTRIBUTE, CONFIGURATION_CLASS_LITE); } else { return false; }
Spring 5.2.0-版本判断实现:
ConfigurationClassUtils: if (isFullConfigurationCandidate(metadata)) { beanDef.setAttribute(CONFIGURATION_CLASS_ATTRIBUTE, CONFIGURATION_CLASS_FULL); } else if (isLiteConfigurationCandidate(metadata)) { beanDef.setAttribute(CONFIGURATION_CLASS_ATTRIBUTE, CONFIGURATION_CLASS_LITE); } else { return false; }
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加强实现的深水区内容,为了知足这些好奇(好学)的娃子,计划会在下篇文章继续再拿一篇专程讲解(预计篇幅不短,万字以上),你可订阅个人公众号持续保持关注。