版本约定
本文内容若没做特殊说明,均基于以下版本:
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配置。
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; }
步骤总结:
1.根据Bean定义信息解析成为一个注解元数据对象AnnotationMetadata metadata
1.可能是个AnnotatedBeanDefinition,也可能是个StandardAnnotationMetadata
2.根据注解元数据metadata判断是否是个@Configuration配置类,有如下三种可能case:
1.标注有@Configuration注解并且该注解的proxyBeanMethods = false,那么mark一下它是Full模式的配置。否则进入下一步判断
2.标注有@Configuration注解或者符合Lite模式的条件(上文有说一共有5种可能是Lite模式,源码处在isConfigurationCandidate(metadata)这个方法里表述),那么mark一下它是Lite模式的配置。否则进入下一步判断
3.不是配置类,并且返回结果return false
2.能进行到这一步,说明该Bean肯定是个配置类了(Full模式或者Lite模式),那就取出其@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); • 1
以上是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()阶段