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

前言

本篇文章将解析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) {

        ConfigurableApplicationContext context = new SpringApplicationBuilder(TestAutoConfigure.class)
                // 非WEB
                .web(WebApplicationType.NONE)
                .run(args);

        Test test = context.getBean("test", Test.class);
        System.out.println(test.getUser());
        System.out.println(context.getBean("user", User.class));
        context.close();
    }

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

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

此模式为lite模式,因为配置类并没有@Configuration注解,此时控制台打印
在这里插入图片描述
可以看到,两个User对象是不同实例的,其中有一个对象不是Spring中的User。那么我们此时加上@Configuration注解在引导类上,会是怎样的结果呢?
在这里插入图片描述
可以看到,此时两个User都是一个实例,都为Spring中的Bean了。由此可以看出完全模式和轻量模式的区别。轻量模式由于没有被动态代理,看起来比较轻量一点,所以叫做轻量模式。但它不享受这种方法调用的装配方式。而完全模式则会被CGLIB动态代理,我们来试验一下

System.out.println(context.getBean(TestAutoConfigure.class));

首先从上下文中拿到我们的引导类这个Bean,打印其className
在这里插入图片描述
可以看到,此时是被动态代理过的。

解析BeanDefinition

让我们回到解析配置注解的主干上。在解析过程中,值得一提的是,解析过程是一个循环,在此留下这个疑问即可,继续往下看。我们知道,最终会走到parser的parse这个解析方法中去

public void parse(Set<BeanDefinitionHolder> configCandidates) {
  for (BeanDefinitionHolder holder : configCandidates) {
    BeanDefinition bd = holder.getBeanDefinition();
    // 下面三种方式解析Bean,其实都差不多,不同方式都是一个结果
    // 这里不求甚解
    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);
    }
  }

  // 这里需要被读者记住,这里将延时import在解析完所有之后会进行处理
  this.deferredImportSelectorHandler.process();
}

如果读者有看过上一篇自动挡装配Enable模式的文章,就会知道其中Import还可以延时处理,其中延时的Imoport在解析过程中是不进行处理的,而是放入一个队列中,在所有解析完成之后才进行处理,而处理的时机就在这里。这里我们继续往下看

protected final void parse(AnnotationMetadata metadata, String beanName) throws IOException {
	processConfigurationClass(new ConfigurationClass(metadata, beanName));
}
protected void processConfigurationClass(ConfigurationClass configClass) throws IOException {
  // @Conditional注解的条件过滤
  if (this.conditionEvaluator.shouldSkip(configClass.getMetadata(), ConfigurationPhase.PARSE_CONFIGURATION)) {
    return;
  }

  // ...

  // Recursively process the configuration class and its superclass hierarchy.
  SourceClass sourceClass = asSourceClass(configClass);
  do {
    // 主要解析方法
    sourceClass = doProcessConfigurationClass(configClass, sourceClass);
  }
  while (sourceClass != null);

  // 若解析完成,将其放入configurationClasses这个集合中去
  this.configurationClasses.put(configClass, configClass);
}

这里值得一提的就是@Conditional注解的条件过滤,下面我们来详细分析一下

@Conditional条件过滤

首先直接进入shouldSkip方法

public boolean shouldSkip(@Nullable AnnotatedTypeMetadata metadata, @Nullable ConfigurationPhase phase) {
  // 如果没有注解元信息,或者没有被标注@Conditional注解,直接跳过,不进行过滤
  if (metadata == null || !metadata.isAnnotated(Conditional.class.getName())) {
    return false;
  }

  // ...

  List<Condition> conditions = new ArrayList<>();
  // 获取condition类的全限定类名
  for (String[] conditionClasses : getConditionClasses(metadata)) {
    for (String conditionClass : conditionClasses) {
      // 将其实例化出来,加入集合中去
      Condition condition = getCondition(conditionClass, this.context.getClassLoader());
      conditions.add(condition);
    }
  }

  // 排序
  AnnotationAwareOrderComparator.sort(conditions);

  for (Condition condition : conditions) {
    ConfigurationPhase requiredPhase = null;
    if (condition instanceof ConfigurationCondition) {
      requiredPhase = ((ConfigurationCondition) condition).getConfigurationPhase();
    }
    // 执行匹配方法condition.matches
    if ((requiredPhase == null || requiredPhase == phase) && !condition.matches(this.context, metadata)) {
      return true;
    }
  }

  return false;
}

这里值得一提的是,getConditionClasses方法,此方法可以拿到Conditional类的全类名,什么意思呢?这里举一个例子,像我们熟知的@ConditionalOnBean、@ConditionalOnClass,都是@Conditional的派生注解,其作用都是例如假设当某Bean存在时、当某Class存在时:

@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Conditional(OnBeanCondition.class)
public @interface ConditionalOnBean {
  // ...
}

可以看到,其元标注了@Conditional,其实根本还是在@Conditional注解上,而上面所说的getConditionClasses方法,正是拿到了@Conditional中的value(OnBeanCondition.class),总之,由以上逻辑来看,若是ConditionalOnBean注解,就会拿到OnBeanCondition这个类并且实例化出来,执行其matches方法,所以问题的核心就在于,@Conditional注解中的value的match方法,其返回值决定了该如何过滤Bean的加载。有兴趣的读者可以进一步阅读OnBeanCondition的match方法,其主要是判断BeanFactory中是否有对应的BeanDefinition。

可以看到,在方法参数中最后一个参数是表示阶段的,其中阶段有两种:

  1. PARSE_CONFIGURATION :解析配置阶段
  2. REGISTER_BEAN :注册Bean阶段

以上都只是解析阶段,所以参数传的是PARSE_CONFIGURATION。

有些条件过滤是看阶段的,比如@ConditionalOnBean注解,判断Bean阶段不应该是解析配置阶段,而应该是注册Bean阶段,因为解析阶段可能Bean都还没完全解析完,不应该过早进行判断,到了注册Bean阶段,就可以是解析完所有Bean的合适时机了,此时机就可以进行条件过滤。

下面我们回到解析主干中去。

解析配置注解

接下来,进入doProcessConfigurationClass方法,看看是如何解析配置注解的

protected final SourceClass doProcessConfigurationClass(ConfigurationClass configClass, SourceClass sourceClass)
  throws IOException {

  // 如果是Component注解,递归解析之
  if (configClass.getMetadata().isAnnotated(Component.class.getName())) {
    // Recursively process any member (nested) classes first
    processMemberClasses(configClass, sourceClass);
  }

  // Process any @PropertySource annotations
  // 处理@PropertySource注解
  for (AnnotationAttributes propertySource : AnnotationConfigUtils.attributesForRepeatable(
    sourceClass.getMetadata(), PropertySources.class,
    org.springframework.context.annotation.PropertySource.class)) {
    if (this.environment instanceof ConfigurableEnvironment) {
      processPropertySource(propertySource);
    }
    else {
      logger.info("Ignoring @PropertySource annotation on [" + sourceClass.getMetadata().getClassName() +
                  "]. Reason: Environment must implement ConfigurableEnvironment");
    }
  }

  // Process any @ComponentScan annotations
  // 处理@ComponentScan注解
  Set<AnnotationAttributes> componentScans = AnnotationConfigUtils.attributesForRepeatable(
    sourceClass.getMetadata(), ComponentScans.class, ComponentScan.class);
  if (!componentScans.isEmpty() &&
      !this.conditionEvaluator.shouldSkip(sourceClass.getMetadata(), ConfigurationPhase.REGISTER_BEAN)) {
    for (AnnotationAttributes componentScan : componentScans) {
      // The config class is annotated with @ComponentScan -> perform the scan immediately
      Set<BeanDefinitionHolder> scannedBeanDefinitions =
        this.componentScanParser.parse(componentScan, sourceClass.getMetadata().getClassName());
      // Check the set of scanned definitions for any further config classes and parse recursively if needed
      for (BeanDefinitionHolder holder : scannedBeanDefinitions) {
        BeanDefinition bdCand = holder.getBeanDefinition().getOriginatingBeanDefinition();
        if (bdCand == null) {
          bdCand = holder.getBeanDefinition();
        }
        if (ConfigurationClassUtils.checkConfigurationClassCandidate(bdCand, this.metadataReaderFactory)) {
          parse(bdCand.getBeanClassName(), holder.getBeanName());
        }
      }
    }
  }

  // Process any @Import annotations
  // 处理@Import注解
  processImports(configClass, sourceClass, getImports(sourceClass), true);

  // Process any @ImportResource annotations
  // 处理@ImportResource注解
  AnnotationAttributes importResource =
    AnnotationConfigUtils.attributesFor(sourceClass.getMetadata(), ImportResource.class);
  if (importResource != null) {
    String[] resources = importResource.getStringArray("locations");
    Class<? extends BeanDefinitionReader> readerClass = importResource.getClass("reader");
    for (String resource : resources) {
      String resolvedResource = this.environment.resolveRequiredPlaceholders(resource);
      configClass.addImportedResource(resolvedResource, readerClass);
    }
  }

  // Process individual @Bean methods
  // 处理@Bean注解的方法
  Set<MethodMetadata> beanMethods = retrieveBeanMethodMetadata(sourceClass);
  for (MethodMetadata methodMetadata : beanMethods) {
    configClass.addBeanMethod(new BeanMethod(methodMetadata, configClass));
  }

  // Process default methods on interfaces
  processInterfaces(configClass, sourceClass);

  // Process superclass, if any
  if (sourceClass.getMetadata().hasSuperClass()) {
    String superclass = sourceClass.getMetadata().getSuperClassName();
    if (superclass != null && !superclass.startsWith("java") &&
        !this.knownSuperclasses.containsKey(superclass)) {
      this.knownSuperclasses.put(superclass, configClass);
      // Superclass found, return its annotation metadata and recurse
      return sourceClass.getSuperClass();
    }
  }

  // No superclass -> processing is complete
  return null;
}

在这里,我们可以很清晰的看出处理的脉络,接下来根据我们所感兴趣的模块进行一一分析

@ComponentScan注解的解析

这个注解主要是扫描指定包名,注册Bean到IOC容器中。我们熟知的@Component以及其派生类例如@Service、@Controller、@Configuration等等都是此注解扫描的,并将其注册到Spring中。

// Process any @ComponentScan annotations
// 这里主要拿到@ComponentScan注解的元信息,里面包含注解中的属性值
// 如果是ComponentScans,也会分解成一个个的ComponentScan
Set<AnnotationAttributes> componentScans = AnnotationConfigUtils.attributesForRepeatable(
  sourceClass.getMetadata(), ComponentScans.class, ComponentScan.class);
// 从这里可以看出,这里也会进行@Conditional的条件过滤
// 并且阶段是REGISTER_BEAN,表示要开始注册Bean了
if (!componentScans.isEmpty() &&
    !this.conditionEvaluator.shouldSkip(sourceClass.getMetadata(), ConfigurationPhase.REGISTER_BEAN)) {
  for (AnnotationAttributes componentScan : componentScans) {
    // The config class is annotated with @ComponentScan -> perform the scan immediately
    // 扫描并注册Bean
    Set<BeanDefinitionHolder> scannedBeanDefinitions =
      this.componentScanParser.parse(componentScan, sourceClass.getMetadata().getClassName());
    // Check the set of scanned definitions for any further config classes and parse recursively if needed
    for (BeanDefinitionHolder holder : scannedBeanDefinitions) {
      BeanDefinition bdCand = holder.getBeanDefinition().getOriginatingBeanDefinition();
      if (bdCand == null) {
        bdCand = holder.getBeanDefinition();
      }
      // 这里还会判断是否需要像配置类解析那样进行解析
      if (ConfigurationClassUtils.checkConfigurationClassCandidate(bdCand, this.metadataReaderFactory)) {
        // 如果需要,则解析之,此方法就是刚开始的方法
        parse(bdCand.getBeanClassName(), holder.getBeanName());
      }
    }
  }
}

可以看到,在扫描并注册后,还会判断是否是需要被配置的Bean,如果是就继续进行此Bean的处理。

我们这里关键看this.componentScanParser的parse方法,扫描并注册Bean

public Set<BeanDefinitionHolder> parse(AnnotationAttributes componentScan, final String declaringClass) {
  // 主要扫描的类
  ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner(this.registry,
                                                                              componentScan.getBoolean("useDefaultFilters"), this.environment, this.resourceLoader);

  // ...略过一些ComponentScan注解属性的配置
  
  Set<String> basePackages = new LinkedHashSet<>();
  // 获取basePackages属性值
  // 下面主要针对属性值,获取需要被扫描的包名
  String[] basePackagesArray = componentScan.getStringArray("basePackages");
  for (String pkg : basePackagesArray) {
    String[] tokenized = StringUtils.tokenizeToStringArray(this.environment.resolvePlaceholders(pkg),
                                                           ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS);
    Collections.addAll(basePackages, tokenized);
  }
  // 获取需要被注册的Class
  for (Class<?> clazz : componentScan.getClassArray("basePackageClasses")) {
    basePackages.add(ClassUtils.getPackageName(clazz));
  }
  // 如果没有配置需要扫描的包,就使用declaringClass的包名
  if (basePackages.isEmpty()) {
    basePackages.add(ClassUtils.getPackageName(declaringClass));
  }

  // ...
  
  // 扫描并注册
  return scanner.doScan(StringUtils.toStringArray(basePackages));
}

值得一提的是,如果没有配置包名,将扫描默认包,而这个默认包值declaringClass是多少呢?可以回顾上面,此参数是配置类的全限定类名

this.componentScanParser.parse(componentScan, sourceClass.getMetadata().getClassName());

sourceClass即为当前我们正在解析的配置类,而此类名将解析为包名,作为默认的扫描包名

if (basePackages.isEmpty()) {
  basePackages.add(ClassUtils.getPackageName(declaringClass));
}
public static String getPackageName(String fqClassName) {
  Assert.notNull(fqClassName, "Class name must not be null");
  // 拿到最后一个.的位置
  int lastDotIndex = fqClassName.lastIndexOf(PACKAGE_SEPARATOR);
  // 截取,这样包名就有了
  return (lastDotIndex != -1 ? fqClassName.substring(0, lastDotIndex) : "");
}

同时,这也是SpringBoot可以扫描Bean的原理,可以知道,在springboot引导类的注解上有@SpringBootApplication注解,而此注解元标注了@Component注解

@ComponentScan(excludeFilters = {
      @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
      @Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {
}

看到这里,就可以理解为什么SpringBoot中,我们的引导类一定要放在包外面,这样,引导类之内的包都可以扫描到@Service、@Controller之类的Bean。就像下图所示的那样
在这里插入图片描述
因为它会默认扫描@ComponentScan所在类的包。

至于扫描并注册的方法scanner.doScan这里不多赘述,此内容是AnnotationConfigApplicationContext注解配置上下文的内容。

@Bean方法的解析

这里回到解析主干上,可以知道,解析@Bean注解的过程如下

// Process individual @Bean methods
// 将配置类中@Bean注解的方法拿出,并封装为MethodMetadata对象
Set<MethodMetadata> beanMethods = retrieveBeanMethodMetadata(sourceClass);
for (MethodMetadata methodMetadata : beanMethods) {
  // 添加到ConfigurationClass对象中
  configClass.addBeanMethod(new BeanMethod(methodMetadata, configClass));
}

这里主要是拿到@Bean标注的方法,然后放入ConfigurationClass对象中,在后面注册Bean时会拿出来,并执行方法注册Bean,在后面的loadBeanDefinition方法会看到。

@Import注解的解析

此注解的解析在上一篇文章中,分析Enable模式的时候已经介绍过了,在这里会进行更详细的一个分析。接下来直接进入解析方法

// Process any @Import annotations
processImports(configClass, sourceClass, getImports(sourceClass), true);
private void processImports(ConfigurationClass configClass, SourceClass currentSourceClass,
                            Collection<SourceClass> importCandidates, boolean checkForCircularImports) {

  // importCandidates集合就是Import的value
  if (importCandidates.isEmpty()) {
    return;
  }

	// ...
  
  this.importStack.push(configClass);
  try {
    for (SourceClass candidate : importCandidates) {
      // 判断是否是ImportSelector的实现
      if (candidate.isAssignable(ImportSelector.class)) {
        // Candidate class is an ImportSelector -> delegate to it to determine imports
        Class<?> candidateClass = candidate.loadClass();
        // 实例化成ImportSelector类
        ImportSelector selector = BeanUtils.instantiateClass(candidateClass, ImportSelector.class);
        // 如果有aware方法,在这里会进行aware设置
        ParserStrategyUtils.invokeAwareMethods(
          selector, this.environment, this.resourceLoader, this.registry);
        // 延迟导入,其中ImportSelector接口有一个子类是DeferredImportSelector
        // 表示该Import要延迟导入,此时不做解析,只是将其保存在一个集合中去
        // 在所有配置类解析完成之后,才会对DeferredImportSelector实现类要导入的类进行解析
        if (selector instanceof DeferredImportSelector) {
          // 封装DeferredImportSelector类,将其信息保存到集合中
          this.deferredImportSelectorHandler.handle(
            configClass, (DeferredImportSelector) selector);
        }
        // 若不是延迟导入,那就直接开始导入工作
        else {
          // 调用selector的方法,获取需要被注册的Bean的类名
          String[] importClassNames = selector.selectImports(currentSourceClass.getMetadata());
          Collection<SourceClass> importSourceClasses = asSourceClasses(importClassNames);
          // 递归解析该需要被注册的Bean
          processImports(configClass, currentSourceClass, importSourceClasses, false);
        }
      }
      // 又或者是ImportBeanDefinitionRegistrar的实现?
      else if (candidate.isAssignable(ImportBeanDefinitionRegistrar.class)) {
        // Candidate class is an ImportBeanDefinitionRegistrar ->
        // delegate to it to register additional bean definitions
        Class<?> candidateClass = candidate.loadClass();
        ImportBeanDefinitionRegistrar registrar =
          BeanUtils.instantiateClass(candidateClass, ImportBeanDefinitionRegistrar.class);
        ParserStrategyUtils.invokeAwareMethods(
          registrar, this.environment, this.resourceLoader, this.registry);
        // 这里的做法是先将ImportBeanDefinitionRegistrar存放起来
        // 在稍后一点再调用其方法注册BeanDefinition
        configClass.addImportBeanDefinitionRegistrar(registrar, currentSourceClass.getMetadata());
      }
      // 到这里,判断不是以上的接口编程实现
      // 或者是上面import接口编程返回出来的需要被导入的那些类,也会走到这一步
      // 将此类当作配置类解析
      else {
        // Candidate class not an ImportSelector or ImportBeanDefinitionRegistrar ->
        // process it as an @Configuration class
        this.importStack.registerImport(
          currentSourceClass.getMetadata(), candidate.getMetadata().getClassName());
        // 接下来还会到doProcessConfigurationClass方法再进行解析
        // 还会走一样的流程进行解析配置注解
        processConfigurationClass(candidate.asConfigClass(configClass));
      }
    }
  }
  // ...
}

此方法可以总结为如下三个判断:

  1. 如果是ImportSelector的子类
    • 如果是DeferredImportSelector,延迟Import,加入集合。至于延迟加载的处理时机,在解析的开头就有埋下伏笔,记得的读者可以回过头去看看
    • 如果不是延迟import,直接执行接口方法,获取需要被导入的Bean类名,递归Import之,若返回出来的类名是普通类,或者是配置类,递归之后会进入第三个分支
  2. 如果是ImportBeanDefinitionRegistrar的子类
    • 将其保存下来,在后面载入BeanDefinition方法会进行调用其接口的方法注册Bean,在后面我们就可以看到其调用时机了
  3. 如果不是上述两种情况,将把当前类当作一个Configuration配置类一样进行递归解析

在解析过程中,有较多的递归,难免会绕晕,建议读者在大脑中反复模拟方法的运转,利用假设法来推导一个例子到某一步将会发生怎样的操作,大致跑一遍大概也就会理解这其中的奥秘了。

延迟导入

回顾我们刚开始导入的地方,有这么一段代码

public void parse(Set<BeanDefinitionHolder> configCandidates) {
  for (BeanDefinitionHolder holder : configCandidates) {
    BeanDefinition bd = holder.getBeanDefinition();
    // ...
    parse(((AnnotatedBeanDefinition) bd).getMetadata(), holder.getBeanName());
  }
  
  // 延迟导入的处理
  this.deferredImportSelectorHandler.process();
}

最终会走到processGroupImports方法中去

public void processGroupImports() {
  for (DeferredImportSelectorGrouping grouping : this.groupings.values()) {
    grouping.getImports().forEach(entry -> {
      ConfigurationClass configurationClass = this.configurationClasses.get(
        entry.getMetadata());
      try {
        // 还是执行上面的导入方法
        processImports(configurationClass, asSourceClass(configurationClass),
                       asSourceClasses(entry.getImportClassName()), false);
      }
      // ...
    });
  }
}

最终还是会执行我们上面分析过的processImports方法,所以延迟导入其实和普通导入无异,只不过时机不同。

到这里,我们要分析的解析过程就差不多结束了,当然还有其他注解的解析,这部分就留给读者自主完成,这里进行解析的总结

解析总结

  • @ComponentScan:直接将扫描的Bean注册到IOC容器中,默认扫描路径是打上该注解的类所在的包
  • @Bean:存放在ConfigurationClass类属性中,稍后处理
  • @Import:递归导入所需要被导入的类,其中有延时导入,在后面会分析到

回到解析方法中,可以看到,经过我们这么一导入,就会将导入构造的ConfigurationClass类放入ConfigurationClassParser解析类的成员变量configurationClasses中

this.configurationClasses.put(configClass, configClass);

所以,现在configurationClasses集合中存放着一堆被解析出来的Bean,其中延迟导入的那些Bean在集合的最底层部分,注册时机会比普通Bean都稍微晚一点,因为configurationClasses的数据结构是有序的

private final Map<ConfigurationClass, ConfigurationClass> configurationClasses = new LinkedHashMap<>();

知道这个是非常重要的,因为我们熟知的SpringBoot自动装配中,正是一个延迟导入的实现,至于为什么,将在解析@ConditionalOnBean注解失效问题具体说明。

注册Bean阶段

以上就完成了解析工作,接下来进入到将解析到的配置信息,都注册到IOC容器中去。这里我们回到最开始的processConfigBeanDefinitions方法中,其中do-while循环中有这么一段方法

this.reader.loadBeanDefinitions(configClasses);
public void loadBeanDefinitions(Set<ConfigurationClass> configurationModel) {
  // 和Conditionnal同理
  TrackedConditionEvaluator trackedConditionEvaluator = new TrackedConditionEvaluator();
  for (ConfigurationClass configClass : configurationModel) {
    // 遍历加载ConfigurationClass集合
    loadBeanDefinitionsForConfigurationClass(configClass, trackedConditionEvaluator);
  }
}
private void loadBeanDefinitionsForConfigurationClass(
  ConfigurationClass configClass, TrackedConditionEvaluator trackedConditionEvaluator) {

	// Conditional条件过滤
  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;
  }

  // 如果该类是被Import进来的
  if (configClass.isImported()) {
    // 将其注册到IOC容器中
    registerBeanDefinitionForImportedConfigurationClass(configClass);
  }
  for (BeanMethod beanMethod : configClass.getBeanMethods()) {
    // 注册那些Bean方法到IOC容器中
    loadBeanDefinitionsForBeanMethod(beanMethod);
  }

  loadBeanDefinitionsFromImportedResources(configClass.getImportedResources());
  // 此时会处理我们上面说的还未处理的ImportBeanDefinitionRegistrar
  loadBeanDefinitionsFromRegistrars(configClass.getImportBeanDefinitionRegistrars());
}

这里可以看看loadBeanDefinitionsFromRegistrars方法的处理

private void loadBeanDefinitionsFromRegistrars(Map<ImportBeanDefinitionRegistrar, AnnotationMetadata> registrars) {
  registrars.forEach((registrar, metadata) ->
                     registrar.registerBeanDefinitions(metadata, this.registry));
}

可以看到,ImportBeanDefinitionRegistrar的registerBeanDefinitions方法将在此时机进行调用。

以上注册Bean的逻辑较为复杂,大多是有关Spring的操作,由于这里主要解析注解驱动,所以对Spring是如何注册Bean的不作过多赘述,读者此时只需要知道在这里将注册BeanDefinition到BeanFactory即可。

注解驱动处理总结

到这里就差不多完成了注解的解析和注册工作,接下来我们回到ConfigurationClassPostProcessor的processConfigBeanDefinitions方法,其中do-while循环有这么一段逻辑:

do {
  parser.parse(candidates);
  parser.validate();

  // 拿到我们上面说的ConfigurationClasses集合
  // 此集合存放着解析到的配置类
  Set<ConfigurationClass> configClasses = new LinkedHashSet<>(parser.getConfigurationClasses());
  configClasses.removeAll(alreadyParsed);

  // 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());
  }
  // 注册解析到的Bean
  this.reader.loadBeanDefinitions(configClasses);
  alreadyParsed.addAll(configClasses);

  candidates.clear();
  // candidateNames.length是未解析之前Bean的数量
  // registry.getBeanDefinitionCount()是现在Bean的数量
  // 主要是以Bean数量判断是否有新的Bean注册到IOC容器中去了
  // 也就是说,这里开始操作那些,新注册进来的Bean
  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) {
      // 如果旧的不存在,证明此时的Bean即为新注册的Bean
      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;
  }
}
while (!candidates.isEmpty());

可以看到,这里是一个循环,为什么要这么做呢?首先我们来捋一捋整个的流程:

  1. 解析需要被解析的Bean
  2. 注册那些解析出来的Bean
  3. 将上面流程注册到的Bean再加入candidates队列中,回到第一步,解析这些Bean
    • 注册到的Bean的来源可以是@Import导入进来的,@Bean方法定义的,@ComponentScan扫描注册进来的等等
  4. 上述三个步骤一直循环,直到candidates队列为空,也就是说解析不到新的Bean为止

在整个解析过程中有很多地方做了去重,和判断是否已经解析过的逻辑,所以有的地方看似会重复注册,但其实不会。完整的看下来,可以发现有很多地方都用了递归的操作,第一次看这个类的源码难免会有点难以理解,希望读者可以用真实Bean的例子在脑海中反复跑一遍流程,反复揣摩,阅读源码难免会有一点难度,最重要的就是心静,耐心的好好推敲,反复阅读。

发布了84 篇原创文章 · 获赞 85 · 访问量 10万+

猜你喜欢

转载自blog.csdn.net/qq_41737716/article/details/98028103