Spring 核心类 ConfigurationClassPostProcessor 流程讲解及源码全面分析(一)

本文涉及的产品
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
云解析 DNS,旗舰版 1个月
全局流量管理 GTM,标准版 1个月
简介: Spring 核心类 ConfigurationClassPostProcessor 流程讲解及源码全面分析

Spring 核心类 ConfigurationClassPostProcessor 注入流程以及相关的源码解析如下,首先对其类的注入来源进行详细分析

介绍

在 XML 配置容器方式中,一开始对 XML 文件进行解析,新加了一层层注解 @Configuration、@Bean、@Component 等等,解析&加载的实现,但是其并不影响 Spring 底层的实现,从中体现了 Spring 中强大的扩展性

例如:在 XML 配置【<context:component-scan base-package="com.vnjohn.config">】 以后,通过 ComponentScanBeanDefinitionParser 解析 XML 自定义标签时,会扫描配置的包下所有的类文件,将带有 @Component【@Service、@Controller 这些注解内部都引入了它】 注解的类都找到,加载为 ScannedGenericBeanDefinition【GenericBeanDefinition 子类】 类,放入到 beanDefinitionMap、beanDefinitionNames 集合中,等待 Bean 实例化过程进行加载

在扫描 XML 标签:<context:component-scan> 时,会新增一些注册处理器,它们负责后面的解析、加载工作,其中就承担了常用注解的解析工作(@Component、@ComponentScan、@Configuration、@PropertySource、@Import、@Bean)

以下是介绍在 SpringBoot 中 ConfigurationClassPostProcessor 类是如何被注入的过程

  1. SpringBootApplication#run 内部会创建 ConfigurableApplicationContext 接口下某个实例,Web 下创建的类就是它的实现类 AnnotationConfigServletWebServerApplicationContext
  2. 随即它的构造方法里面就会创建 AnnotatedBeanDefinitionReader 实例
  3. AnnotatedBeanDefinitionReader 构造方法里面又会调用:AnnotationConfigUtils.registerAnnotationConfigProcessors(this.registry)
  4. 此时就会创建好 ConfigurationClassPostProcessor 该类的 BeanDefinition 信息
  5. 在执行 invokeBeanFactoryPostProcessors 方法时会获取到 BDRPP 的实现类【ConfigurationClassPostProcessor & 实现了 PriorityOrdered
  6. 至此,该类 bean 实例就已经创建好了,并且会将标注的相关注解进行 beanDefinition 信息的创建,后续进入 Spring Bean 生命周期的过程中会将这些 BD 进行实例化、初始化,从而完成相关 Bean 创建工作

配置类加载过程

ConfigurationClassPostProcessor#postProcessBeanFactory:enhanceConfigurationClasses 增强所有 @Configuration 修饰的配置类,生成代理类,为了确保其下的 @Bean 方法是能够保持单例的、添加一个新的 ImportAwareBeanPostProcessor,BPP 后置处理类

在介绍 ConfigurationClassPostProcessor 类的执行流程时,先对 @Condition 注解使用进行以下说明,因为在执行过程中会涉及到该注解的逻辑

@Condition 注解使用

类或方法上增加了 @Condition 注解,在解析注解配置时会调用 shouldSkip 方法,基于 @Conditional 判断该对象是否要跳过,如果不满足 @Conditional 中 value 中的条件,就跳过该 Bean,不会注入容器

value 中的条件是具体的匹配逻辑,可以设置多个,如果匹配返回 true:代表可以正常注入,返回 false:代表不会注入容器中

代码示例如下:

// bill 会正常注入,linux 和 windows 会按照条件去匹配是否可以注入
@Conditional({WindowsCondition.class})
@Configuration
public class BeanConfig {
    @Bean(name = "bill")
    public Person person1() {
        return new Person("Bill Gates", 62);
    }
    @Conditional({LinuxCondition.class})
    @Bean("linux")
    public Person person2() {
        return new Person("Linux", 48);
    }
    @Conditional(WindowsCondition.class)
    @Bean("windows")
    public Person person3() {
        return new Person("windows", 50);
    }
}
public class LinuxCondition implements Condition {
    @Override
    public boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata annotatedTypeMetadata) {
        Environment environment = conditionContext.getEnvironment();
        String property = environment.getProperty("os.name");
        if (property.contains("Linux")) {
            return true;
        }
        return false;
    }
}
public class WindowsCondition implements Condition {
    /**
     * @param conditionContext:判断条件能使用的上下文环境
     * @param annotatedTypeMetadata:注解所在位置的注释信息
     */
    @Override
    public boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata annotatedTypeMetadata) {
        // 获取ioc使用的beanFactory
        ConfigurableListableBeanFactory beanFactory = conditionContext.getBeanFactory();
        // 获取类加载器
        ClassLoader classLoader = conditionContext.getClassLoader();
        // 获取当前环境信息
        Environment environment = conditionContext.getEnvironment();
        // 获取bean定义的注册类
        BeanDefinitionRegistry registry = conditionContext.getRegistry();
        // 获得当前系统名
        String property = environment.getProperty("os.name");
        // 包含 Windows 则说明是 windows 系统,返回true
        if (property.contains("Windows")) {
            return true;
        }
        return false;
    }
}

postProcessBeanDefinitionRegistry 内核心方法

注解核心处理类:ConfigurationClassPostProcessor,执行流程如下:

BDRPP#postProcessBeanDefinitionRegistry 方法—>processConfigBeanDefinitions

BDRPP 接口下的 postProcessBeanDefinitionRegistry 方法会优先于 BFPP 接口下方法进行执行,此流程在介绍 refresh 方法时已经详细分析过了.

processConfigBeanDefinitions 前置准备工作

public void processConfigBeanDefinitions(BeanDefinitionRegistry registry) {
    // 创建存放 BeanDefinitionHolder 对象集合
    List<BeanDefinitionHolder> configCandidates = new ArrayList<>();
    // 当前 registry 就是 DefaultListableBeanFactory,获取所有已经注册的 BeanDefinition beanName
    String[] candidateNames = registry.getBeanDefinitionNames();
    // 遍历所有要处理 beanDefinition 名称,筛选对应 beanDefinition(被注解修饰的)
    for (String beanName : candidateNames) {
      // 获取指定名称 BeanDefinition 对象
      BeanDefinition beanDef = registry.getBeanDefinition(beanName);
      // 如果 beanDefinition 中 configurationClass 属性不等于空,那么意味着已经处理过,输出日志信息
      if (beanDef.getAttribute(ConfigurationClassUtils.CONFIGURATION_CLASS_ATTRIBUTE) != null) {
        if (logger.isDebugEnabled()) {
          logger.debug("Bean definition has already been processed as a configuration class: " + beanDef);
        }
      }
      // 判断当前 BeanDefinition 是否是一个配置类,并为 BeanDefinition 设置属性为 lite 或 full,此处设置属性值是为了后续进行调用
      else if (ConfigurationClassUtils.checkConfigurationClassCandidate(beanDef, this.metadataReaderFactory)) {
        // 添加到对应的集合对象中
        configCandidates.add(new BeanDefinitionHolder(beanDef, beanName));
      }
    }
    // 如果没有发现任何配置类,则直接返回
    if (configCandidates.isEmpty()) {
      return;
    }
    // 如果适用,则按照先前确定的@Order的值排序
    configCandidates.sort((bd1, bd2) -> {
      int i1 = ConfigurationClassUtils.getOrder(bd1.getBeanDefinition());
      int i2 = ConfigurationClassUtils.getOrder(bd2.getBeanDefinition());
      return Integer.compare(i1, i2);
    });
    // 判断当前类型是否是 SingletonBeanRegistry 类型
    SingletonBeanRegistry sbr = null;
    if (registry instanceof SingletonBeanRegistry) {
      // 类型的强制转换
      sbr = (SingletonBeanRegistry) registry;
      // 判断是否有自定义 beanName 生成器
      if (!this.localBeanNameGeneratorSet) {
        // 获取自定义 beanName 生成器
        BeanNameGenerator generator = (BeanNameGenerator) sbr.getSingleton(
            AnnotationConfigUtils.CONFIGURATION_BEAN_NAME_GENERATOR);
        // 如果有自定义的命名生成策略
        if (generator != null) {
          // 设置组件扫描 beanName 生成策略
          this.componentScanBeanNameGenerator = generator;
          // 设置 import bean name 生成策略
          this.importBeanNameGenerator = generator;
        }
      }
    }
    // 如果环境对象等于空,那么就重新创建新的环境对象
    if (this.environment == null) {
      this.environment = new StandardEnvironment();
    }
    // 实例化 ConfigurationClassParser 类,并初始化相关的参数,完成配置类的解析工作
    ConfigurationClassParser parser = new ConfigurationClassParser(
        this.metadataReaderFactory, this.problemReporter, this.environment,
        this.resourceLoader, this.componentScanBeanNameGenerator, registry);
    // 创建两个集合对象,
    // 存放相关的BeanDefinitionHolder对象
    Set<BeanDefinitionHolder> candidates = new LinkedHashSet<>(configCandidates);
    // 存放扫描包下的所有bean
    Set<ConfigurationClass> alreadyParsed = new HashSet<>(configCandidates.size());
    do {
      // 解析带有 @Controller、@Import、@ImportResource、@ComponentScan、@ComponentScans、@Bean 的 BeanDefinition
      parser.parse(candidates);
      // 将解析完的 Configuration 配置类进行校验
      // 1、配置类不能是 final
      // 2、@Bean 修饰的方法必须可以重写以支持CGLIB
      parser.validate();
      // 获取所有的bean,包括扫描的bean对象,@Import导入的bean对象
      Set<ConfigurationClass> configClasses = new LinkedHashSet<>(parser.getConfigurationClasses());
      // 清除掉已经解析处理过的配置类
      configClasses.removeAll(alreadyParsed);
      // 判断读取器是否为空,如果为空的话,就创建完全填充好的 ConfigurationClass 实例的读取器
      if (this.reader == null) {
        this.reader = new ConfigurationClassBeanDefinitionReader(
            registry, this.sourceExtractor, this.resourceLoader, this.environment,
            this.importBeanNameGenerator, parser.getImportRegistry());
      }
      // 核心方法,将完全填充好的ConfigurationClass实例转化为BeanDefinition注册入IOC容器
      this.reader.loadBeanDefinitions(configClasses);
      // 添加到已经处理的集合中
      alreadyParsed.addAll(configClasses);
      candidates.clear();
      // 这里判断 registry.getBeanDefinitionCount() > candidateNames.length 目的是为了知道 reader.loadBeanDefinitions(configClasses) 这一步有没有向 BeanDefinitionMap 中添加新的BeanDefinition
      // 实际上就是看配置类(例如 AppConfig 类会向 BeanDefinitionMap 中添加 bean)
      // 如果有,registry.getBeanDefinitionCount() 就会大于 candidateNames.length
      // 这样就需要再次遍历新加入的 BeanDefinition,并判断这些 bean 是否已经被解析过了,如果未解析,需要重新进行解析
      // 这里 AppConfig 类向容器中添加 bean,实际上在 parser.parse() 这一步已经全部被解析了
      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());
        }
        // 如果有未解析的类,则将其添加到 candidates 中,这样 candidates 不为空,就会进入到下一次的 while 的循环中
        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;
      }
    }
    while (!candidates.isEmpty());
    // 注册 ImportRegistry Bean 信息为了支持 ImportAware 配置类
    if (sbr != null && !sbr.containsSingleton(IMPORT_REGISTRY_BEAN_NAME)) {
      sbr.registerSingleton(IMPORT_REGISTRY_BEAN_NAME, parser.getImportRegistry());
    }
    if (this.metadataReaderFactory instanceof CachingMetadataReaderFactory) {
      // 清除相关的缓存,释放内存
      ((CachingMetadataReaderFactory) this.metadataReaderFactory).clearCache();
    }
  }

该方法流程如下:

  1. 先遍历所有被自动加载的 BeanDefinition 信息,一般都是会把标注了 @Configuration 注解的类优先进行注入,然后判断当前 BD 属性 configurationClass 是否为空,若不为空,说明该类型已经被注入,否则就调用 Configuration 工具类检测类型是否标注了 @Configuration 注解,为其设置好相关的属性准备工作,该方法源码及注释如下:
  2. 对配置类进行排序,根据 order 属性值 ,属性值越大的优先进行处理
  3. 判断是否有自定义的 beanName 生成器,若有进行实例化,否则不处理,一般无特殊的要求这里使用的就是 Spring 内部的生成器
  4. 若当前环境对象为空,就创建一个标准环境对象进行后续的值处理工作
  5. 实例化 ConfigurationClassParser 核心类,会调用其 parse 方法对配置类集合进行具体解析(parse->processConfigurationClass)

第一点描述的方法源码及注释如下:

public static boolean checkConfigurationClassCandidate(
    BeanDefinition beanDef, MetadataReaderFactory metadataReaderFactory) {
  // 获取bean定义信息中的class类名
  String className = beanDef.getBeanClassName();
  // 如果className为空,或者bean定义信息中的factoryMethod不等于空,那么直接返回
  if (className == null || beanDef.getFactoryMethodName() != null) {
    return false;
  }
  AnnotationMetadata metadata;
  // 通过注解注入的 bd 都是 AnnotatedGenericBeanDefinition,实现了AnnotatedBeanDefinition
  // spring 内部 bd 是 RootBeanDefinition,实现了 AbstractBeanDefinition
  // 此处主要用于判断是否归属于 AnnotatedBeanDefinition
  if (beanDef instanceof AnnotatedBeanDefinition &&
      className.equals(((AnnotatedBeanDefinition) beanDef).getMetadata().getClassName())) {
    // 从当前bean的定义信息中获取元数据信息
    metadata = ((AnnotatedBeanDefinition) beanDef).getMetadata();
  }
  // 判断是否是 spring 中默认的 BeanDefinition
  else if (beanDef instanceof AbstractBeanDefinition && ((AbstractBeanDefinition) beanDef).hasBeanClass()) {
    // 获取当前 bean 对象 Class 对象
    Class<?> beanClass = ((AbstractBeanDefinition) beanDef).getBeanClass();
    // 如果class实例是下面四种类型或接口的子类、父接口等任何一种情况,直接返回
    if (BeanFactoryPostProcessor.class.isAssignableFrom(beanClass) ||
        BeanPostProcessor.class.isAssignableFrom(beanClass) ||
        AopInfrastructureBean.class.isAssignableFrom(beanClass) ||
        EventListenerFactory.class.isAssignableFrom(beanClass)) {
      return false;
    }
    // 为给定类创建新的AnnotationMetadata实例
    metadata = AnnotationMetadata.introspect(beanClass);
  }
  // 如果上述两种情况都不符合
  else {
    try {
      // 获取className的MetadataReader实例
      MetadataReader metadataReader = metadataReaderFactory.getMetadataReader(className);
      // 读取底层类的完整注释元数据,包括带注解方法的元数据
      metadata = metadataReader.getAnnotationMetadata();
    }
    catch (IOException ex) {
      if (logger.isDebugEnabled()) {
        logger.debug("Could not find class file for introspecting configuration annotations: " +
            className, ex);
      }
      return false;
    }
  }
  // 获取 bean 定义元数据被 @Configuration 注解标注的属性字典值
  Map<String, Object> config = metadata.getAnnotationAttributes(Configuration.class.getName());
  // 如果 bean 被 @Configuration 注解标注,且属性 proxyBeanMethods 为 true(使用代理模式),则将 bean 定义为 full
  if (config != null && !Boolean.FALSE.equals(config.get("proxyBeanMethods"))) {
    beanDef.setAttribute(CONFIGURATION_CLASS_ATTRIBUTE, CONFIGURATION_CLASS_FULL);
  }
  // 如果 bean 被 @Configuration 注解标注,且被注解 @Component,@ComponentScan、@Import、@ImportResource 或者 @Bean 标记的方法,则将 bean 定义标记为 lite
  else if (config != null || isConfigurationCandidate(metadata)) {
    beanDef.setAttribute(CONFIGURATION_CLASS_ATTRIBUTE, CONFIGURATION_CLASS_LITE);
  }
  else {
    return false;
  }
  // bean 定义是一个标记为 full 或 lite 候选项,有设置 order 则设置 order 属性值
  Integer order = getOrder(metadata);
  // 如果值不为空的话,那么直接设置值到具体 beanDefinition
  if (order != null) {
    // 设置 bean 定义 order 值
    beanDef.setAttribute(ORDER_ATTRIBUTE, order);
  }
  return true;
}

如果是 @Configuration 注解并且该注解属性 proxyBeanMethods 配置为 true默认值就是 true,该属性的含义就是为了注明该类下的 @Bean 方法是否被动态代理拦截实现单例,则标识为 full

如果注解属性 proxyBeanMethods 为 false 并且加了 @Bean、@Component、@ComponentScan、@Import、@ImportResource 注解的,则标识为 lite

以上两者都不是的话则不进行处理,否则将其加入到配置类集合中进行后续方法的处理

ConfigurationClassParser#parse 方法

public void parse(Set<BeanDefinitionHolder> configCandidates) {
  // 循环遍历 configCandidates
  for (BeanDefinitionHolder holder : configCandidates) {
    // 获取 BeanDefinition
    BeanDefinition bd = holder.getBeanDefinition();
    // 根据 BeanDefinition 类型的不同,调用 parse 不同的重载方法,实际上最终都是调用 processConfigurationClass 方法
    try {
      // 注解类型
      if (bd instanceof AnnotatedBeanDefinition) {
        parse(((AnnotatedBeanDefinition) bd).getMetadata(), holder.getBeanName());
      }
      // 有class对象的
      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,它是 ImportSelector 的一个子类
  // ImportSelector 被设计成和 @Import 注解同样的效果,但是实现了 ImportSelector 类可以条件性的决定导入某些配置
  // DeferredImportSelector 设计是在所有其他的配置类被处理后才进行处理,因为一个配置类它内部依赖于其他 bean,其他 bean 需要优先进行构建
  this.deferredImportSelectorHandler.process();
}

parse 方法存在于多个重载方法,但最终调用的都是 processConfigurationClass 方法

// 根据注解元数据、beanName 解析配置文件,有注解元数据
protected final void parse(AnnotationMetadata metadata, String beanName) throws IOException {
  processConfigurationClass(new ConfigurationClass(metadata, beanName), DEFAULT_EXCLUSION_FILTER);
}
// 根据 Class、beanName 解析配置文件,有 Class 对象
protected final void parse(Class<?> clazz, String beanName) throws IOException {
  processConfigurationClass(new ConfigurationClass(clazz, beanName), DEFAULT_EXCLUSION_FILTER);
}
// 根据 className、beanName 解析配置文件,读取元数据
protected final void parse(@Nullable String className, String beanName) throws IOException {
  Assert.notNull(className, "No bean class name for configuration class bean definition");
  MetadataReader reader = this.metadataReaderFactory.getMetadataReader(className);
  processConfigurationClass(new ConfigurationClass(reader, beanName), DEFAULT_EXCLUSION_FILTER);
}

区分三种不同的类型,优先级不同而已

  1. 存在于注解,一般在 SpringBoot 下定义的都是这种方式,只有当我们对 spring 进行扩展实现时注入业务相关的 bean,才会是如下两种类型
  2. 存在 Class、beanName,在注入 beanDefinition 时直接指定的就是 .Class
  3. 存在 className、beanName,在注入 beanDefinition 时指定的是类的全限定的名称

deferredImportSelectorHandler#process 方法内部的执行流程在该文章有介绍:
SpringBoot 自动装配流程及源码剖析

ConfigurationClassParser#doProcessConfigurationClass 方法

在实际处理配置类前,再作一些前置校验的工作,避免调用后续方法作无用的动作

protected void  processConfigurationClass(ConfigurationClass configClass, Predicate<String> filter) throws IOException {
  // 判断是否跳过解析
  if (this.conditionEvaluator.shouldSkip(configClass.getMetadata(), ConfigurationPhase.PARSE_CONFIGURATION)) {
    return;
  }
  // 第一次进入的时候,configurationClass size 为 0,existingClass 肯定为 null,在此处理 configuration 重复 import
  // 如果同一个配置类被处理两次,两次都属于被 import 则合并导入类返回,如果配置类不是被导入的,则移除旧的使用新的配置类
  ConfigurationClass existingClass = this.configurationClasses.get(configClass);
  if (existingClass != null) {
    if (configClass.isImported()) {
      if (existingClass.isImported()) {
        // 如果要处理的配置类 configClass 在已经分析处理的配置类记录中已存在,合并两者的importBy属性
        existingClass.mergeImportedBy(configClass);
      }
      // 否则忽略新导入的配置类,因为存在 non-imported 类重写它
      return;
    } else {
      // 明确 bean 定义发现,可能取代了 import,允许移除老的已加载配置类
      this.configurationClasses.remove(configClass);
      this.knownSuperclasses.values().removeIf(configClass::equals);
    }
  }
  // 处理配置类,由于配置类可能存在父类(若父类的全类名是以java开头的,则除外),所有需要将 configClass变成 sourceClass 去解析,然后返回 sourceClass 父类
  // 如果此时父类为空,则不会进行 while 循环去解析,如果父类不为空,则会循环的去解析父类
  // SourceClass 意义:简单的包装类,目的是为了以统一的方式去处理带有注解的类,不管这些类是如何加载的
  // 如果无法理解,可以把它当做一个黑盒,不会影响看 spring 源码主流程
  SourceClass sourceClass = asSourceClass(configClass, filter);
  do {
    // 解析各种注解
    sourceClass = doProcessConfigurationClass(configClass, sourceClass, filter);
  }
  while (sourceClass != null);
  // 将解析的配置类存储起来,这样回到 parse 方法时,能取到值
  this.configurationClasses.put(configClass, configClass);
}
  1. 调用 shouldSkip 方法,基于 @Conditional 标签判断该对象是否要跳过,如果不满足 @Conditional 中 value 中的条件,就跳过该 Bean,不会注入容器
  2. 判断该配置类是否已经被处理过:如果被处理过,则判断当前类和已配置过的类是否都是被 import 导入的,是则对两者的 importedBy 属性进行合并,否则就先进行移除然后重新加入配置类
  3. 前期的校验和准备工作做完以后,再调用 doProcessConfigurationClass 进行具体的注解解析工作

由于 doProcessConfigurationClass 方法内部处理的流程较长,如下分为不同节点进行解析

processMemberClasses

递归处理内部类

// @Configuration 继承了 @Component
if (configClass.getMetadata().isAnnotated(Component.class.getName())) {
  // 递归处理内部类,因为内部类也是一个配置类,配置类上有 @configuration 注解,该注解继承 @Component,if 判断为 true,调用 processMemberClasses 方法,递归解析配置类中的内部类
  processMemberClasses(configClass, sourceClass, filter);
}
private void processMemberClasses(ConfigurationClass configClass, SourceClass sourceClass,Predicate<String> filter) throws IOException {
    // 找到内部类,内部类中也可能是一个配置类
    Collection<SourceClass> memberClasses = sourceClass.getMemberClasses();
    // 如果不等于空的话
    if (!memberClasses.isEmpty()) {
        List<SourceClass> candidates = new ArrayList<>(memberClasses.size());
        // 循环判断内部类是不是配置类 & 内部类不是当前配置类型
        for (SourceClass memberClass : memberClasses) {
            if (ConfigurationClassUtils.isConfigurationCandidate(memberClass.getMetadata()) &&
             !memberClass.getMetadata().getClassName().equals(configClass.getMetadata().getClassName()))      {
                candidates.add(memberClass);
            }
        }
        // 对配置类进行排序操作
        OrderComparator.sort(candidates);
        // 遍历符合规则的类
        for (SourceClass candidate : candidates) {
            if (this.importStack.contains(configClass)) {
                // 出现配置类循环导入,则直接报错
                this.problemReporter.error(new CircularImportProblem(configClass, this.importStack));
            }
            else {
                // 将配置类入栈
                this.importStack.push(configClass);
                try {
                    // 调用 processConfigurationClass 方法,因为内部类中还可能包含内部类,所以需要在做循环解析,实际工作中是不会有这中情况的
                    processConfigurationClass(candidate.asConfigClass(configClass), filter);
                }
                finally {
                    // 解析完出栈
                    this.importStack.pop();
                }
            }
        }
    }
}

获取当前配置类的子类,对子类进行判别其是否属于配置类:@Component、@ComponentScan、@Import、@ImportResource,是就追加到 candidates 集合中,后面对其进行排序.

public static boolean isConfigurationCandidate(AnnotationMetadata metadata) {
  // 不考虑接口
  if (metadata.isInterface()) {
    return false;
  }
  // 检查是否被注解 @Component、@ComponentScan、@Import、@ImportResource 标注
  for (String indicator : candidateIndicators) {
    if (metadata.isAnnotated(indicator)) {
      return true;
    }
  }
  // 最后检查是否有 @Bean 标注的方法
  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;
  }
}

candidates 集合进行遍历,若当前导入栈已存在当前配置类,则直接抛出异常结束:配置类不可重复注入;如果未存在,则调用 processConfigurationClass 方法继续往回走,因为内部类可能也会是一个配置类,所以调用该方法


目录
相关文章
|
3天前
|
监控 Java 应用服务中间件
高级java面试---spring.factories文件的解析源码API机制
【11月更文挑战第20天】Spring Boot是一个用于快速构建基于Spring框架的应用程序的开源框架。它通过自动配置、起步依赖和内嵌服务器等特性,极大地简化了Spring应用的开发和部署过程。本文将深入探讨Spring Boot的背景历史、业务场景、功能点以及底层原理,并通过Java代码手写模拟Spring Boot的启动过程,特别是spring.factories文件的解析源码API机制。
15 2
|
19天前
|
数据采集 监控 前端开发
二级公立医院绩效考核系统源码,B/S架构,前后端分别基于Spring Boot和Avue框架
医院绩效管理系统通过与HIS系统的无缝对接,实现数据网络化采集、评价结果透明化管理及奖金分配自动化生成。系统涵盖科室和个人绩效考核、医疗质量考核、数据采集、绩效工资核算、收支核算、工作量统计、单项奖惩等功能,提升绩效评估的全面性、准确性和公正性。技术栈采用B/S架构,前后端分别基于Spring Boot和Avue框架。
|
9天前
|
前端开发 Java 开发者
Spring生态学习路径与源码深度探讨
【11月更文挑战第13天】Spring框架作为Java企业级开发中的核心框架,其丰富的生态系统和强大的功能吸引了无数开发者的关注。学习Spring生态不仅仅是掌握Spring Framework本身,更需要深入理解其周边组件和工具,以及源码的底层实现逻辑。本文将从Spring生态的学习路径入手,详细探讨如何系统地学习Spring,并深入解析各个重点的底层实现逻辑。
35 9
|
1月前
|
Java Spring
Spring底层架构源码解析(三)
Spring底层架构源码解析(三)
108 5
|
1月前
|
XML Java 数据格式
Spring底层架构源码解析(二)
Spring底层架构源码解析(二)
|
1月前
|
Java BI API
spring boot 整合 itextpdf 导出 PDF,写入大文本,写入HTML代码,分析当下导出PDF的几个工具
这篇文章介绍了如何在Spring Boot项目中整合iTextPDF库来导出PDF文件,包括写入大文本和HTML代码,并分析了几种常用的Java PDF导出工具。
404 0
spring boot 整合 itextpdf 导出 PDF,写入大文本,写入HTML代码,分析当下导出PDF的几个工具
|
2月前
|
SQL 监控 druid
springboot-druid数据源的配置方式及配置后台监控-自定义和导入stater(推荐-简单方便使用)两种方式配置druid数据源
这篇文章介绍了如何在Spring Boot项目中配置和监控Druid数据源,包括自定义配置和使用Spring Boot Starter两种方法。
|
1月前
|
人工智能 自然语言处理 前端开发
SpringBoot + 通义千问 + 自定义React组件:支持EventStream数据解析的技术实践
【10月更文挑战第7天】在现代Web开发中,集成多种技术栈以实现复杂的功能需求已成为常态。本文将详细介绍如何使用SpringBoot作为后端框架,结合阿里巴巴的通义千问(一个强大的自然语言处理服务),并通过自定义React组件来支持服务器发送事件(SSE, Server-Sent Events)的EventStream数据解析。这一组合不仅能够实现高效的实时通信,还能利用AI技术提升用户体验。
162 2
|
8天前
|
缓存 IDE Java
SpringBoot入门(7)- 配置热部署devtools工具
SpringBoot入门(7)- 配置热部署devtools工具
19 2
 SpringBoot入门(7)- 配置热部署devtools工具
|
4天前
|
存储 运维 安全
Spring运维之boot项目多环境(yaml 多文件 proerties)及分组管理与开发控制
通过以上措施,可以保证Spring Boot项目的配置管理在专业水准上,并且易于维护和管理,符合搜索引擎收录标准。
15 2