likes
comments
collection
share

Spring 源码阅读 37:postProcessBeanDefinitionRegistry 对 @Configuration 配置的解析和处理(2)

作者站长头像
站长
· 阅读数 44

概述

这一篇接着上篇,继续分析 ConfigurationClassPostProcessor 中postProcessBeanDefinitionRegistry方法对标记了@``Configuration注解的类的解析和处理。

建议两篇一起阅读。

处理过程

下图是postProcessBeanDefinitionRegistry方法中执行具体解析过程的processConfigBeanDefinitions方法的源代码。

Spring 源码阅读 37:postProcessBeanDefinitionRegistry 对 @Configuration 配置的解析和处理(2)

上一篇分析了前半部分,也就是do-while循环之前的部分。Spring 通过对所有注册到容器中的 BeanDefinition 进行筛选,找到了所有符合候选条件的配置类对应的 BeanDefinition,并且进行了一些准备性的处理。

接下来在do-while循环中,对这些符合候选条件的 BeanDefinition,也就是candidates集合中的元素,进行处理。下面开始。

配置类的解析

进入do-while语句块之后,第一步就是使用实现创建好的配置类解析器对candidates集合进行解析。

parser.parse(candidates);

我们进入这个方法查询解析的过程。

// org.springframework.context.annotation.ConfigurationClassParser#parse(java.util.Set<org.springframework.beans.factory.config.BeanDefinitionHolder>)
public void parse(Set<BeanDefinitionHolder> configCandidates) {
   for (BeanDefinitionHolder holder : configCandidates) {
      BeanDefinition bd = holder.getBeanDefinition();
      try {
         if (bd instanceof AnnotatedBeanDefinition) {
            parse(((AnnotatedBeanDefinition) bd).getMetadata(), holder.getBeanName());
         }
         else if (bd instanceof AbstractBeanDefinition && ((AbstractBeanDefinition) bd).hasBeanClass()) {
            parse(((AbstractBeanDefinition) bd).getBeanClass(), holder.getBeanName());
         }
         else {
            parse(bd.getBeanClassName(), holder.getBeanName());
         }
      }
      catch (BeanDefinitionStoreException ex) {
         throw ex;
      }
      catch (Throwable ex) {
         throw new BeanDefinitionStoreException(
               "Failed to parse configuration class [" + bd.getBeanClassName() + "]", ex);
      }
   }

   this.deferredImportSelectorHandler.process();
}

针对candidates集合中的每一个 BeanDefinition,这里会根据它具体的类型,以 BeanDefinition 的元数据和 Bean 名称为参数,执行对应的parse重载方法,因为我们要处理的 BeanDefinition 是基于注解生成的,因此,我们进入第一个if分支中的parse方法查看。

// org.springframework.context.annotation.ConfigurationClassParser#parse(org.springframework.core.type.AnnotationMetadata, java.lang.String)
protected final void parse(AnnotationMetadata metadata, String beanName) throws IOException {
   processConfigurationClass(new ConfigurationClass(metadata, beanName), DEFAULT_EXCLUSION_FILTER);
}

这里将元数据和 Bean 名称封装成了一个ConfigurationClass 对象作为参数,调用了processConfigurationClass方法。

// org.springframework.context.annotation.ConfigurationClassParser#processConfigurationClass
protected void processConfigurationClass(ConfigurationClass configClass, Predicate<String> filter) throws IOException {
   if (this.conditionEvaluator.shouldSkip(configClass.getMetadata(), ConfigurationPhase.PARSE_CONFIGURATION)) {
      return;
   }

   ConfigurationClass existingClass = this.configurationClasses.get(configClass);
   if (existingClass != null) {
      if (configClass.isImported()) {
         if (existingClass.isImported()) {
            existingClass.mergeImportedBy(configClass);
         }
         // Otherwise ignore new imported config class; existing non-imported class overrides it.
         return;
      }
      else {
         // Explicit bean definition found, probably replacing an import.
         // Let's remove the old one and go with the new one.
         this.configurationClasses.remove(configClass);
         this.knownSuperclasses.values().removeIf(configClass::equals);
      }
   }

   // Recursively process the configuration class and its superclass hierarchy.
   SourceClass sourceClass = asSourceClass(configClass, filter);
   do {
      sourceClass = doProcessConfigurationClass(configClass, sourceClass, filter);
   }
   while (sourceClass != null);

   this.configurationClasses.put(configClass, configClass);
}

方法中的第一个if语句是根据配置类的@Conditional注解,来判断是否跳过当前配置类的解析,这里的逻辑不在本文的讨论范畴之内,我们假设不跳过,接着分析后续的逻辑。

接下来,会在this.configurationClasses查找当前的配置类,并赋值给existingClass变量,当第一次解析配置类的时候,此时的existingClass一定是空的,因此第二个if语句块中的步骤不会被执行。

在两个if语句块之后,会递归地通过doProcessConfigurationClass方法解析配置类及其继承的父类。解析完成之后的配置类对象,会被添加到this.configurationClasses容器中。

因此,我们接下来重点看一下doProcessConfigurationClass方法。

Spring 源码阅读 37:postProcessBeanDefinitionRegistry 对 @Configuration 配置的解析和处理(2)

这个方法也比较长,其核心逻辑就是对当前配置类的配置信息进行解析和处理,包括递归处理内部类的配置信息、@PropertySource注解的配置信息、@ComponentScan注解的配置信息、@Import注解的配置信息、@ImportResource注解的配置信息、被@Bean注解标记的方法,并且,如果包含父类的话,还会返回父类的信息供递归处理。

以上所有被解析的信息,都会作为 ConfigurationClass 对象的内容,保存到解析器的configurationClasses集合中。

回到最初的processConfigBeanDefinitions方法中,当parse方法执行完之后,解析器还会通过validate方法对解析的结果进行验证,确保解析到的都是符合 Spring 规定的配置。

parser.validate();

随后,还会从解析器中获取所有解析得到的 ConfigurationClass,并排除掉alreadyParsed集合中的元素,避免后续重复处理。

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

至此,配置类的解析工作就完成了,解析的结果就是所有已经被处理的配置内容,和一个 ConfigurationClass 结合configClasses

@Bean方法的解析

接着看后面的代码。

// Read the model and create bean definitions based on its content
if (this.reader == null) {
   this.reader = new ConfigurationClassBeanDefinitionReader(
         registry, this.sourceExtractor, this.resourceLoader, this.environment,
         this.importBeanNameGenerator, parser.getImportRegistry());
}
this.reader.loadBeanDefinitions(configClasses);
alreadyParsed.addAll(configClasses);

首先,创建了一个 ConfigurationClassBeanDefinitionReader 类型的reader对象,然后通过调用它的loadBeanDefinitions方法,加载configClasses中的 BeanDefinition。之后,将configClasses中所有的元素添加到alreadyParsed中,代表alreadyParsed中所有的配置类都解析完成了。

虽然在容器进行包路径扫描的时候,所有的配置类都创建了相应的 BeanDefinition,但是配置类中可以通过为一个方法添加@``Bean注解来配置 Bean 的信息,因此,这一部分的loadBeanDefinitions方法是为了处理配置类中被标记了@``Bean注解的方法。

进入loadBeanDefinitions方法查看源码。

// org.springframework.context.annotation.ConfigurationClassBeanDefinitionReader#loadBeanDefinitions
public void loadBeanDefinitions(Set<ConfigurationClass> configurationModel) {
   TrackedConditionEvaluator trackedConditionEvaluator = new TrackedConditionEvaluator();
   for (ConfigurationClass configClass : configurationModel) {
      loadBeanDefinitionsForConfigurationClass(configClass, trackedConditionEvaluator);
   }
}

这里,对集合中的每一个 ConfigurationClass 调用了loadBeanDefinitionsForConfigurationClass方法。这里的 TrackedConditionEvaluator 是用来通过@Conditional注解来判断是不是要跳过当前的配置类处理。

private void loadBeanDefinitionsForConfigurationClass(
      ConfigurationClass configClass, TrackedConditionEvaluator trackedConditionEvaluator) {

   if (trackedConditionEvaluator.shouldSkip(configClass)) {
      String beanName = configClass.getBeanName();
      if (StringUtils.hasLength(beanName) && this.registry.containsBeanDefinition(beanName)) {
         this.registry.removeBeanDefinition(beanName);
      }
      this.importRegistry.removeImportingClass(configClass.getMetadata().getClassName());
      return;
   }

   if (configClass.isImported()) {
      registerBeanDefinitionForImportedConfigurationClass(configClass);
   }
   for (BeanMethod beanMethod : configClass.getBeanMethods()) {
      loadBeanDefinitionsForBeanMethod(beanMethod);
   }

   loadBeanDefinitionsFromImportedResources(configClass.getImportedResources());
   loadBeanDefinitionsFromRegistrars(configClass.getImportBeanDefinitionRegistrars());
}

如果当前的配置类是由别的配置类引入的,或者引入了别的配置类,都会有相应的处理。我们此时重点看方法体中for循环的部分,在这里,会通过配置类的getBeanMethods方法,将配置类中所有的 BeanMethod 获取到,并对其执行loadBeanDefinitionsForBeanMethod方法。

从前面对parse方法的解析中可以看到,此处获取到的 BeanMethod 就是当前配置类中所有被标记了@``Bean注解的方法集合。我们进入loadBeanDefinitionsForBeanMethod方法中查看源码。

Spring 源码阅读 37:postProcessBeanDefinitionRegistry 对 @Configuration 配置的解析和处理(2)

这个方法的代码量也非常可观。但是大部分逻辑都与之前分析过的 BeanDefinition 加载逻辑类似。首先,会读取@``Bean注解的信息,并根据这些信息创建一个 ConfigurationClassBeanDefinition 类型的对象。然后,将方法作为 BeanDefinition 的工厂方法,因为创建对应的 Bean 实例的时候,就是通过当前的方法来创建的。最后,再通过容器的registerBeanDefinition方法将 BeanDefinition 注册到容器中。

分析完这部分之后,再次回到processConfigBeanDefinitions方法查看后续的代码。

处理新增的 BeanDefinition

candidates.clear();
if (registry.getBeanDefinitionCount() > candidateNames.length) {
   String[] newCandidateNames = registry.getBeanDefinitionNames();
   Set<String> oldCandidateNames = new HashSet<>(Arrays.asList(candidateNames));
   Set<String> alreadyParsedClasses = new HashSet<>();
   for (ConfigurationClass configurationClass : alreadyParsed) {
      alreadyParsedClasses.add(configurationClass.getMetadata().getClassName());
   }
   for (String candidateName : newCandidateNames) {
      if (!oldCandidateNames.contains(candidateName)) {
         BeanDefinition bd = registry.getBeanDefinition(candidateName);
         if (ConfigurationClassUtils.checkConfigurationClassCandidate(bd, this.metadataReaderFactory) &&
               !alreadyParsedClasses.contains(bd.getBeanClassName())) {
            candidates.add(new BeanDefinitionHolder(bd, candidateName));
         }
      }
   }
   candidateNames = newCandidateNames;
}

首先将candidates集合清空,代表目前candidates集合中的类都处理完毕了。

接下来,判断容器中的 BeanDefinition 是不是比之前更多了,这里多出来的部分主要就是来自于机遇@``Bean方法创建的 BeanDefinition。后面的步骤,就是将这些新增的 BeanDefinition 找出来,创建对应的 BeanDefinitionHolder 并添加到candidates中。

这样,只要candidates不为空,do-while循环就会接着以同样的逻辑递归处理这些新增的元素,直到没有新的元素被添加进来。

至此,do-while循环中的内容就分析完了。

结尾部分

接下来,看方法的最后一部分代码。

// Register the ImportRegistry as a bean in order to support ImportAware @Configuration classes
if (sbr != null && !sbr.containsSingleton(IMPORT_REGISTRY_BEAN_NAME)) {
   sbr.registerSingleton(IMPORT_REGISTRY_BEAN_NAME, parser.getImportRegistry());
}

if (this.metadataReaderFactory instanceof CachingMetadataReaderFactory) {
   // Clear cache in externally provided MetadataReaderFactory; this is a no-op
   // for a shared cache since it'll be cleared by the ApplicationContext.
   ((CachingMetadataReaderFactory) this.metadataReaderFactory).clearCache();
}

最后就是收尾工作,注册了一个 ImportRegistry 单例对象,并清除了缓存信息。

总结

本文分析了processConfigBeanDefinitions方法中解析配置类的主要逻辑部分,Spring 会递归处理已经筛选过的配置类以及其中包含的@``Bean注解标记的方法和配置信息。

下一篇开始分析另一个后处理器方法postProcessBeanFactory

转载自:https://juejin.cn/post/7151041170510970893
评论
请登录