配置类需要标注@Configuration却不知原因?那这次就不能给你涨薪喽(上)

简介: 配置类需要标注@Configuration却不知原因?那这次就不能给你涨薪喽(上)

版本约定


本文内容若没做特殊说明,均基于以下版本:


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定义信息截图展示如下:


image.png


以上是Spring环境,对应代码为:

new AnnotationConfigApplicationContext(AppConfig.class);
• 1


image.png


以上是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()阶段
相关文章
|
安全 Java 程序员
SCPPO(二十二):读取配置文件---程序猿必不可少的技能
SCPPO(二十二):读取配置文件---程序猿必不可少的技能
|
Java Spring 容器
SpringIOC注入三种方式灵活运用(第十四课)
SpringIOC注入三种方式灵活运用(第十四课)
95 0
|
Java Spring 容器
面试官:@Configuration 和 @Component 注解的区别?大部分人都会答错!
面试官:@Configuration 和 @Component 注解的区别?大部分人都会答错!
122 0
面试官:@Configuration 和 @Component 注解的区别?大部分人都会答错!
|
存储 Java 程序员
你自我介绍说很懂Spring配置类,那你怎么解释这个现象?(上)
你自我介绍说很懂Spring配置类,那你怎么解释这个现象?(上)
你自我介绍说很懂Spring配置类,那你怎么解释这个现象?(上)
|
设计模式 Java API
你自我介绍说很懂Spring配置类,那你怎么解释这个现象?(中)
你自我介绍说很懂Spring配置类,那你怎么解释这个现象?(中)
你自我介绍说很懂Spring配置类,那你怎么解释这个现象?(中)
|
Java Spring 容器
你自我介绍说很懂Spring配置类,那你怎么解释这个现象?(下)
你自我介绍说很懂Spring配置类,那你怎么解释这个现象?(下)
你自我介绍说很懂Spring配置类,那你怎么解释这个现象?(下)
|
XML Java 数据库连接
1. 不吹不擂,第一篇就能提升你对Bean Validation数据校验的认知(下)
1. 不吹不擂,第一篇就能提升你对Bean Validation数据校验的认知(下)
1. 不吹不擂,第一篇就能提升你对Bean Validation数据校验的认知(下)
|
Oracle Java 关系型数据库
1. 不吹不擂,第一篇就能提升你对Bean Validation数据校验的认知(上)
1. 不吹不擂,第一篇就能提升你对Bean Validation数据校验的认知(上)
1. 不吹不擂,第一篇就能提升你对Bean Validation数据校验的认知(上)
|
Java Spring 容器
Spring配置类深度剖析-总结篇(手绘流程图,可白嫖)(上)
Spring配置类深度剖析-总结篇(手绘流程图,可白嫖)(上)
Spring配置类深度剖析-总结篇(手绘流程图,可白嫖)(上)
|
Java 调度 Spring
Spring配置类深度剖析-总结篇(手绘流程图,可白嫖)(下)
Spring配置类深度剖析-总结篇(手绘流程图,可白嫖)(下)
Spring配置类深度剖析-总结篇(手绘流程图,可白嫖)(下)