Spring源码解析之注解驱动的"发动机"

本文深入解析Spring的ConfigurationClassPostProcessor如何处理注解配置,重点关注@Bean、@Import、@Conditional和@ComponentScan的解析过程。文章介绍了完全模式和轻量模式的区别,并通过示例展示了它们在Bean实例化上的差异。此外,还分析了条件过滤、BeanDefinition的注册等核心环节。
摘要由CSDN通过智能技术生成

前言

本篇文章将解析ConfigurationClassPostProcessor这个类解析配置注解时都干了什么。其中会分析以下几点问题:

  • @Bean注册Bean到容器中
  • @Import导入处理
  • @Conditional条件解析
  • @ComponentScan注解注册Bean到容器中
  • @Configuration配置类的完全模式和轻量模式
  • …还有其他配置注解的解析,本篇文章不作分析,只对以上论点进行重点分析。相信读懂本篇文章的读者有能力自主分析其他的配置解析过程

所以,我们将以上几个论点当作问题,带着问题来分析ConfigurationClassPostProcessor这个类。

BeanFactoryPostProcessor处理

首先,ConfigurationClassPostProcessor是一个BeanFactoryPostProcessor,所以它具有在Bean定义阶段动态修改和添加Bean的定义的能力。而处理解析配置注解的开始,正是从postProcessBeanDefinitionRegistry方法开始的。

public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
   
	// ...
  processConfigBeanDefinitions(registry);
}
public void processConfigBeanDefinitions(BeanDefinitionRegistry registry) {
   
  // 即将被解析的Bean名单
  List<BeanDefinitionHolder> configCandidates = new ArrayList<>();
  // 拿到目前所有的Bean名称
  // 需要注意的是,目前你定义的Bean或许只有一个,就是引导类那个Configuration(如果是springboot的话)
  // 因为在引导类你将其显式的注册进了Spring上下文
  // 现在还没进行@Bean、@ComponentScan、自动装配之类的注册Bean
  // 所以经过接下来的操作,将动态注册一堆Bean上去
  String[] candidateNames = registry.getBeanDefinitionNames();

  for (String beanName : candidateNames) {
   
    // 变为Bean定义对象
    BeanDefinition beanDef = registry.getBeanDefinition(beanName);
    // 这里是重复判断,因为接下来判断如果是配置类需要被解析,会在Bean定义中增加属性
    // 这里就根据Bean定义查找是否有对应属性,如果有表示已经处理过了
    if (ConfigurationClassUtils.isFullConfigurationClass(beanDef) ||
        ConfigurationClassUtils.isLiteConfigurationClass(beanDef)) {
   
      if (logger.isDebugEnabled()) {
   
        logger.debug("Bean definition has already been processed as a configuration class: " + beanDef);
      }
    }
    // 判断是否是配置类
    else if (ConfigurationClassUtils.checkConfigurationClassCandidate(beanDef, this.metadataReaderFactory)) {
   
      // 如果是,添加进处理名单中
      configCandidates.add(new BeanDefinitionHolder(beanDef, beanName));
    }
  }

  // Return immediately if no @Configuration classes were found
  // 名单是空的,也就没有必要往下解析了
  if (configCandidates.isEmpty()) {
   
    return;
  }

  // Sort by previously determined @Order value, if applicable
  // 这里看出,解析的时候是有序的
  configCandidates.sort((bd1, bd2) -> {
   
    int i1 = ConfigurationClassUtils.getOrder(bd1.getBeanDefinition());
    int i2 = ConfigurationClassUtils.getOrder(bd2.getBeanDefinition());
    return Integer.compare(i1, i2);
  });

  // ...

  // Parse each @Configuration class
  // 最主要的解析类,由它负责解析以上获得的Bean名单
  ConfigurationClassParser parser = new ConfigurationClassParser(
    this.metadataReaderFactory, this.problemReporter, this.environment,
    this.resourceLoader, this.componentScanBeanNameGenerator, registry);

  Set<BeanDefinitionHolder> candidates = new LinkedHashSet<>(configCandidates);
  Set<ConfigurationClass> alreadyParsed = new HashSet<>(configCandidates.size());
  
  // 这里是一个循环,具体为什么是循环在下面的解析中会解释到
  // 这里先埋下一个伏笔,记住即可
  do {
   
    // 解析工作
    parser.parse(candidates);
    parser.validate();

    Set<ConfigurationClass> configClasses = new LinkedHashSet<>(parser.getConfigurationClasses());
    configClasses.removeAll(alreadyParsed);

    // ...
    
    // 将解析到的类注册进IOC容器中
    this.reader.loadBeanDefinitions(configClasses);
    alreadyParsed.addAll(configClasses);

    // ...
  }
  while (!candidates.isEmpty());

  // ...
}

我这里省略了一些代码,只看主干部分。

判断是否需要被解析

首先看一下判断是否是配置类的逻辑

ConfigurationClassUtils.checkConfigurationClassCandidate(beanDef, this.metadataReaderFactory)

如果判断是配置类,才会继续往下解析,所以这里看一下其是如何判断的

if (isFullConfigurationCandidate(metadata)) {
   
   beanDef.setAttribute(CONFIGURATION_CLASS_ATTRIBUTE, CONFIGURATION_CLASS_FULL);
}
else if (isLiteConfigurationCandidate(metadata)) {
   
   beanDef.setAttribute(CONFIGURATION_CLASS_ATTRIBUTE, CONFIGURATION_CLASS_LITE);
}

Full完全模式与Lite轻量模式

这里省略了一些获取注解信息的过程,主要是isFullConfigurationCandidate和isLiteConfigurationCandidate这两个方法进行判断的

public static boolean isFullConfigurationCandidate(AnnotationMetadata metadata) {
   
   return metadata.isAnnotated(Configuration.class.getName());
}
private static final Set<String> candidateIndicators = new HashSet<>(8);

static {
   
  candidateIndicators.add(Component.class.getName());
  candidateIndicators.add(ComponentScan.class.getName());
  candidateIndicators.add(Import.class.getName());
  candidateIndicators.add(ImportResource.class.getName());
}

public static boolean isLiteConfigurationCandidate(AnnotationMetadata metadata) {
   
  // Do not consider an interface or an annotation...
  if (metadata.isInterface()) {
   
    return false;
  }

  // Any of the typical annotations found?
  // 此集合为上面所述的那几个注解
  for (String indicator : candidateIndicators) {
   
    if (metadata.isAnnotated(indicator)) {
   
      return true;
    }
  }

  // Finally, let's look for @Bean methods...
  try {
   
    return metadata.hasAnnotatedMethods(Bean.class.getName());
  }
  catch (Throwable ex) {
   
    if (logger.isDebugEnabled()) {
   
      logger.debug("Failed to introspect @Bean methods on class [" + metadata.getClassName() + "]: " + ex);
    }
    return false;
  }
}

从这里可以看出来,有两种配置解析模式:

  • 如果类被打上@Configuration,此为完全解析模式(Full)

  • 如果此类带有以上4个注解或者有存在方法被打上@Bean注解的,都算配置类,都需要被解析,此为轻量解析模式(Lite)

那么有什么区别呢?完全解析模式会使用动态代理的方式将配置类进行动态代理,可以有如下操作

@Bean
public Test test(){
   
  Test test = new Test();
  test.setUser(user());
  return test;
}

@Bean
public User user(){
   
  return new User("xx");
}

Test Bean的依赖User对象,直接调用方法名即可,会将其注入Spring中的Bean,如果是lite模式,就不会注入Spring中的Bean,而是新new一个对象,我们可以试验一下

@EnableAutoConfiguration
public class TestAutoConfigure {
   
    public static void main(String[] args
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值