【小家Spring】Spring解析@Configuration注解的处理器:ConfigurationClassPostProcessor(ConfigurationClassParser)(中)

本文涉及的产品
全局流量管理 GTM,标准版 1个月
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
云解析 DNS,旗舰版 1个月
简介: 【小家Spring】Spring解析@Configuration注解的处理器:ConfigurationClassPostProcessor(ConfigurationClassParser)(中)

判断是配置类了,将配置类放入到configCandidates这个BeanDefinitionHolder的集合中存储,进行下一步的操作


ConfigurationClassParser#parse


  public void parse(Set<BeanDefinitionHolder> configCandidates) {
    this.deferredImportSelectors = new LinkedList<>();
    for (BeanDefinitionHolder holder : configCandidates) {
      BeanDefinition bd = holder.getBeanDefinition();
      try {
        // 我们使用的注解驱动,所以会到这个parse进来处理。其实内部调用都是processConfigurationClass进行解析的
        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);
      }
    }
    // 最最最后面才处理实现了DeferredImportSelector接口的类,最最后哦~~
    processDeferredImportSelectors();
  }


  public void parse(Set<BeanDefinitionHolder> configCandidates) {
    this.deferredImportSelectors = new LinkedList<>();
    for (BeanDefinitionHolder holder : configCandidates) {
      BeanDefinition bd = holder.getBeanDefinition();
      try {
        // 我们使用的注解驱动,所以会到这个parse进来处理。其实内部调用都是processConfigurationClass进行解析的
        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);
      }
    }
    // 最最最后面才处理实现了DeferredImportSelector接口的类,最最后哦~~
    processDeferredImportSelectors();
  }


该方法做了三件事如下:


1.实例化deferredImportSelectors

2.遍历configCandidates ,进行处理.根据BeanDefinition 的类型 做不同的处理,一般都会调用ConfigurationClassParser#parse 进行解析

3.处理ImportSelect


下面看看处理的核心方法processConfigurationClass:

  protected void processConfigurationClass(ConfigurationClass configClass) throws IOException {
    //ConfigurationCondition继承自Condition接口
    // ConfigurationPhase枚举类型的作用:ConfigurationPhase的作用就是根据条件来判断是否加载这个配置类
    // 两个值:PARSE_CONFIGURATION 若条件不匹配就不加载此@Configuration
    // REGISTER_BEAN:无论如何,所有@Configurations都将被解析。
    if (this.conditionEvaluator.shouldSkip(configClass.getMetadata(), ConfigurationPhase.PARSE_CONFIGURATION)) {
      return;
    }
    // 如果这个配置类已经存在了,后面又被@Import进来了~~~会走这里 然后做属性合并~
    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.
    // 请注意此处:while递归,只要方法不返回null,就会一直do下去~~~~~~~~
    SourceClass sourceClass = asSourceClass(configClass);
    do {
      // doProcessConfigurationClassz这个方法是解析配置文件的核心方法,此处不做详细分析
      sourceClass = doProcessConfigurationClass(configClass, sourceClass);
    } while (sourceClass != null);
    // 保存我们所有的配置类  注意:它是一个LinkedHashMap,所以是有序的  这点还比较重要~~~~和bean定义信息息息相关
    this.configurationClasses.put(configClass, configClass);
  }
  // 解析@Configuration配置文件,然后加载进Bean的定义信息们
  // 这个方法非常的重要,可以看到它加载Bean定义信息的一个顺序~~~~
  @Nullable
  protected final SourceClass doProcessConfigurationClass(ConfigurationClass configClass, SourceClass sourceClass)
      throws IOException {
    // 先去看看内部类  这个if判断是Spring5.x加上去的,这个我认为还是很有必要的。
    // 因为@Import、@ImportResource这种属于lite模式的配置类,但是我们却不让他支持内部类了
    if (configClass.getMetadata().isAnnotated(Component.class.getName())) {
      // Recursively process any member (nested) classes first
      // 基本逻辑:内部类也可以有多个(支持lite模式和full模式,也支持order排序)
      // 若不是被import过的,那就顺便直接解析它(processConfigurationClass())  
      // 另外:该内部class可以是private  也可以是static~~~(建议用private)
      // 所以可以看到,把@Bean等定义在内部类里面,是有助于提升Bean的优先级的~~~~~
      processMemberClasses(configClass, sourceClass);
    }
    // Process any @PropertySource annotations
    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
    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
    //getImports方法的实现 很有意思
    processImports(configClass, sourceClass, getImports(sourceClass), true);
    // Process any @ImportResource annotations
    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
    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;
  }


有关this.conditionEvaluator.shouldSkip()实际判断逻辑大概为:和@Conditional注解以及TypeFilter相关,具体处理逻辑这里省略


ConfigurationClass代表一个配置类,它内部维护了一些已经解析好的但是还没有被加入进Bean定义信息的原始信息,有必要做如下解释:


// @since 3.0  它就是普通的类,基本只有get set方法
final class ConfigurationClass {
  private final AnnotationMetadata metadata;
  private final Resource resource;
  @Nullable
  private String beanName;
  private final Set<ConfigurationClass> importedBy = new LinkedHashSet<>(1);
  // 存储该配置类里所有标注@Bean注解的方法~~~~
  private final Set<BeanMethod> beanMethods = new LinkedHashSet<>();
  // 用Map保存着@ImportResource 导入进来的资源们~
  private final Map<String, Class<? extends BeanDefinitionReader>> importedResources = new LinkedHashMap<>();
  // 用Map保存着@Import中实现了`ImportBeanDefinitionRegistrar`接口的内容~
  private final Map<ImportBeanDefinitionRegistrar, AnnotationMetadata> importBeanDefinitionRegistrars = new LinkedHashMap<>();
  final Set<String> skippedBeanMethods = new HashSet<>();
}


最终我归纳出一个扫描Bean的顺序(注意并不是Bean定义真正注册的顺序),解析@Configuration配置文件的顺序:


1.内部配置类:–> 它里面还可以有普通配置类一模一样的功能,但优先级最高,最终会放在configurationClasses这个map的第一位


2.@PropertySource:这个和Bean定义没啥关系了,属于Spring配置PropertySource的范畴。这个属性优先级相对较低


3.@ComponentScan:注意,注意,注意重说三。 这里扫描到的Bean定义,就直接register注册了,直接注册了,注解注册了。所以它的时机是非常早的。(另外:如果注册进去的Bean定义信息如果还是配置类,这里会继续parse(),所以最终能被扫描到的组件,最终都会当作一个配置类来处理,所以最终都会放进configurationClasses这个Map里面去)


4.@Import:相对复杂点,如下:

     1.若就是一个普通类(标注@Configuration与否都无所谓反正会当作一个配置类来处理,也会放进configurationClasses缓存进去)


    2.实现了ImportSelector:递归最红都成为第一步的类。若实现的是DeferredImportSelector接口,它会放在deferredImportSelectors属性里先保存着,等着外部的所有的configCandidates配置类全部解析完成后,统一processDeferredImportSelectors()。它的处理方式一样的,最终也是转为第一步的类


    3.实现了ImportBeanDefinitionRegistrar:放置ConfigurationClass.importBeanDefinitionRegistrars属性里保存着


5.@ImportResource:一般用来导入xml文件。它是先放在ConfigurationClass.importedResources属性里放着


6.@Bean:找到所有@Bean的方法,然后保存到ConfigurationClass.beanMethods属性里


7.processInterfaces:处理该类实现的接口们的default方法(标注@Bean的有效)


8.处理父类:拿到父类,每个父类都是一个配置文件来处理(比如要有任何注解)。备注:!superclass.startsWith("java")全类名不以java打头,且没有被处理过(因为一个父类可议N个子类,但只能被处理一次)


9.return null:若全部处理完成后就返回null,停止递归。


由上可见,这九步中,唯独只有@ComponentScan扫描到的Bean这个时候的Bean定义信息是已经注册上去了的,其余的都还没有真正注册到注册中心。


注意:bean的注册的先后顺序,将直接影响到Bean的覆盖(默认就行Map,后注册的肯定先注册的,当然还和scope有关)

关于相同BeanName的覆盖问题,参考:【小家Spring】聊聊Spring的bean覆盖(同名beanName/beanId问题),介绍Spring名称生成策略接口BeanNameGenerator


其余的请继续往下看:


Bean定义信息的注册顺序:


由上面步骤可知,已经解析好的所有配置类(包含内部类、扫描到的组件等等)都已经全部放进了本类的configurationClasses这个属性Map里面。因此只需要知道它在什么时候被解析的就可以知道顺序了。


解析代码:ConfigurationClassPostProcessor#processConfigBeanDefinitions方法里:

public void processConfigBeanDefinitions(BeanDefinitionRegistry registry) {
  ...
  do {
    parser.parse(candidates);
    parser.validate();
    ...
    // 此处就拿出了我们已经处理好的所有配置类们(该配置文件下的所有组件们~~~~)
    Set<ConfigurationClass> configClasses = new LinkedHashSet<>(parser.getConfigurationClasses());
    configClasses.removeAll(alreadyParsed);
    ...
    // reader:ConfigurationClassBeanDefinitionReader最终真正实现的Bean的注册
    // 关于它的原始代码 下面有着重分析,因此此处只说一个结论,顺序
    this.reader.loadBeanDefinitions(configClasses);
}


此处结合我的一个案例说明,此时configClasses值如下:


image.png

而此时Bean注册中心里已经存在的Bean定义信息如下图:(我们发现只有Scan的才真正进入注册中心,其余比如@Import的都还不在里面~)


image.png


this.reader.loadBeanDefinitions(configClasses)开始处理,参阅ConfigurationClassBeanDefinitionReader.loadBeanDefinitions()方法:


class ConfigurationClassBeanDefinitionReader {
  // 对每个@Configuration 类文件做遍历(所以 Config配置文件的顺序还是挺重要的)
  public void loadBeanDefinitions(Set<ConfigurationClass> configurationModel) {
    TrackedConditionEvaluator trackedConditionEvaluator = new TrackedConditionEvaluator();
    for (ConfigurationClass configClass : configurationModel) {
      loadBeanDefinitionsForConfigurationClass(configClass, trackedConditionEvaluator);
    }
  }
  // private 方法来解析每一个已经解析好的@Configuration配置文件~~~
  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());
  }
}


1.最先处理注册@Import进来的Bean定义~,判断依据是:configClass.isImported()。官方解释为:Return whether this configuration class was registered via @{@link Import} or automatically registered due to being nested within another configuration class 这句话的意思是说@Import或者内部类或者通过别的配置类放进来的都是被导入进来的~~~~


2.第二步开始注册@Bean进来的:若是static方法,beanDef.setBeanClassName(configClass.getMetadata().getClassName()) + beanDef.setFactoryMethodName(methodName);若是实例方法:beanDef.setFactoryBeanName(configClass.getBeanName())+ beanDef.setUniqueFactoryMethodName(methodName) 总之对使用者来说 没有太大的区别


3.注册importedResources进来的bean们。就是@ImportResource这里来的Bean定义


4.执行ImportBeanDefinitionRegistrar#registerBeanDefinitions()注册Bean定义信息~(也就是此处执行ImportBeanDefinitionRegistrar的接口方法)

this.reader.loadBeanDefinitions(configClasses)执行完成后,拥有的bean定义截图如下:(导入的最终也都作为独立的Bean注册进来了~)

image.png



相关文章
|
10天前
|
监控 Java 应用服务中间件
高级java面试---spring.factories文件的解析源码API机制
【11月更文挑战第20天】Spring Boot是一个用于快速构建基于Spring框架的应用程序的开源框架。它通过自动配置、起步依赖和内嵌服务器等特性,极大地简化了Spring应用的开发和部署过程。本文将深入探讨Spring Boot的背景历史、业务场景、功能点以及底层原理,并通过Java代码手写模拟Spring Boot的启动过程,特别是spring.factories文件的解析源码API机制。
39 2
|
29天前
|
搜索推荐 Java Spring
Spring Filter深度解析
【10月更文挑战第21天】Spring Filter 是 Spring 框架中非常重要的一部分,它为请求处理提供了灵活的控制和扩展机制。通过合理配置和使用 Filter,可以实现各种个性化的功能,提升应用的安全性、可靠性和性能。还可以结合具体的代码示例和实际应用案例,进一步深入探讨 Spring Filter 的具体应用和优化技巧,使对它的理解更加全面和深入。
|
1月前
|
Java Spring
Spring底层架构源码解析(三)
Spring底层架构源码解析(三)
113 5
|
1月前
|
XML Java 数据格式
Spring底层架构源码解析(二)
Spring底层架构源码解析(二)
|
1月前
|
XML Java 数据格式
Spring IOC容器的深度解析及实战应用
【10月更文挑战第14天】在软件工程中,随着系统规模的扩大,对象间的依赖关系变得越来越复杂,这导致了系统的高耦合度,增加了开发和维护的难度。为解决这一问题,Michael Mattson在1996年提出了IOC(Inversion of Control,控制反转)理论,旨在降低对象间的耦合度,提高系统的灵活性和可维护性。Spring框架正是基于这一理论,通过IOC容器实现了对象间的依赖注入和生命周期管理。
71 0
|
1月前
|
缓存 Java 程序员
Map - LinkedHashSet&Map源码解析
Map - LinkedHashSet&Map源码解析
70 0
|
1月前
|
算法 Java 容器
Map - HashSet & HashMap 源码解析
Map - HashSet & HashMap 源码解析
57 0
|
1月前
|
存储 Java C++
Collection-PriorityQueue源码解析
Collection-PriorityQueue源码解析
62 0
|
1月前
|
安全 Java 程序员
Collection-Stack&Queue源码解析
Collection-Stack&Queue源码解析
84 0
|
11天前
|
存储 安全 Linux
Golang的GMP调度模型与源码解析
【11月更文挑战第11天】GMP 调度模型是 Go 语言运行时系统的核心部分,用于高效管理和调度大量协程(goroutine)。它通过少量的操作系统线程(M)和逻辑处理器(P)来调度大量的轻量级协程(G),从而实现高性能的并发处理。GMP 模型通过本地队列和全局队列来减少锁竞争,提高调度效率。在 Go 源码中,`runtime.h` 文件定义了关键数据结构,`schedule()` 和 `findrunnable()` 函数实现了核心调度逻辑。通过深入研究 GMP 模型,可以更好地理解 Go 语言的并发机制。
下一篇
无影云桌面