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

简介: 【小家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



相关文章
|
5月前
|
数据采集 人工智能 Java
1天消化完Spring全家桶文档!DevDocs:一键深度解析开发文档,自动发现子URL并建立图谱
DevDocs是一款基于智能爬虫技术的开源工具,支持1-5层深度网站结构解析,能将技术文档处理时间从数周缩短至几小时,并提供Markdown/JSON格式输出与AI工具无缝集成。
202 1
1天消化完Spring全家桶文档!DevDocs:一键深度解析开发文档,自动发现子URL并建立图谱
|
5月前
|
安全 Java API
深入解析 Spring Security 配置中的 CSRF 启用与 requestMatchers 报错问题
本文深入解析了Spring Security配置中CSRF启用与`requestMatchers`报错的常见问题。针对CSRF,指出默认已启用,无需调用`enable()`,只需移除`disable()`即可恢复。对于`requestMatchers`多路径匹配报错,分析了Spring Security 6.x中方法签名的变化,并提供了三种解决方案:分次调用、自定义匹配器及降级使用`antMatchers()`。最后提醒开发者关注版本兼容性,确保升级平稳过渡。
635 2
|
6月前
|
存储 Java 文件存储
微服务——SpringBoot使用归纳——Spring Boot使用slf4j进行日志记录—— logback.xml 配置文件解析
本文解析了 `logback.xml` 配置文件的详细内容,包括日志输出格式、存储路径、控制台输出及日志级别等关键配置。通过定义 `LOG_PATTERN` 和 `FILE_PATH`,设置日志格式与存储路径;利用 `&lt;appender&gt;` 节点配置控制台和文件输出,支持日志滚动策略(如文件大小限制和保存时长);最后通过 `&lt;logger&gt;` 和 `&lt;root&gt;` 定义日志级别与输出方式。此配置适用于精细化管理日志输出,满足不同场景需求。
1448 1
|
5月前
|
前端开发 安全 Java
Spring Boot 便利店销售系统项目分包设计解析
本文深入解析了基于Spring Boot的便利店销售系统分包设计,通过清晰的分层架构(表现层、业务逻辑层、数据访问层等)和模块化设计,提升了代码的可维护性、复用性和扩展性。具体分包结构包括`controller`、`service`、`repository`、`entity`、`dto`、`config`和`util`等模块,职责分明,便于团队协作与功能迭代。该设计为复杂企业级应用开发提供了实践参考。
213 0
|
3月前
|
Java 数据库连接 API
Java 对象模型现代化实践 基于 Spring Boot 与 MyBatis Plus 的实现方案深度解析
本文介绍了基于Spring Boot与MyBatis-Plus的Java对象模型现代化实践方案。采用Spring Boot 3.1.2作为基础框架,结合MyBatis-Plus 3.5.3.1进行数据访问层实现,使用Lombok简化PO对象,MapStruct处理对象转换。文章详细讲解了数据库设计、PO对象实现、DAO层构建、业务逻辑封装以及DTO/VO转换等核心环节,提供了一个完整的现代化Java对象模型实现案例。通过分层设计和对象转换,实现了业务逻辑与数据访问的解耦,提高了代码的可维护性和扩展性。
146 1
|
2月前
|
缓存 安全 Java
Spring 框架核心原理与实践解析
本文详解 Spring 框架核心知识,包括 IOC(容器管理对象)与 DI(容器注入依赖),以及通过注解(如 @Service、@Autowired)声明 Bean 和注入依赖的方式。阐述了 Bean 的线程安全(默认单例可能有安全问题,需业务避免共享状态或设为 prototype)、作用域(@Scope 注解,常用 singleton、prototype 等)及完整生命周期(实例化、依赖注入、初始化、销毁等步骤)。 解析了循环依赖的解决机制(三级缓存)、AOP 的概念(公共逻辑抽为切面)、底层动态代理(JDK 与 Cglib 的区别)及项目应用(如日志记录)。介绍了事务的实现(基于 AOP
100 0
|
2月前
|
SQL Java 数据库连接
Spring、SpringMVC 与 MyBatis 核心知识点解析
我梳理的这些内容,涵盖了 Spring、SpringMVC 和 MyBatis 的核心知识点。 在 Spring 中,我了解到 IOC 是控制反转,把对象控制权交容器;DI 是依赖注入,有三种实现方式。Bean 有五种作用域,单例 bean 的线程安全问题及自动装配方式也清晰了。事务基于数据库和 AOP,有失效场景和七种传播行为。AOP 是面向切面编程,动态代理有 JDK 和 CGLIB 两种。 SpringMVC 的 11 步执行流程我烂熟于心,还有那些常用注解的用法。 MyBatis 里,#{} 和 ${} 的区别很关键,获取主键、处理字段与属性名不匹配的方法也掌握了。多表查询、动态
108 0
|
3月前
|
Java 测试技术 数据库
说一说 SpringBoot 整合 Junit5 常用注解
我是小假 期待与你的下一次相遇 ~
|
4月前
|
安全 Java API
Spring Boot 功能模块全解析:构建现代Java应用的技术图谱
Spring Boot不是一个单一的工具,而是一个由众多功能模块组成的生态系统。这些模块可以根据应用需求灵活组合,构建从简单的REST API到复杂的微服务系统,再到现代的AI驱动应用。
|
3月前
|
Java 数据库 开发者
Spring Boot 框架超级详细总结及长尾关键词应用解析
本文深入讲解Spring Boot框架的核心概念、功能特性及实际应用,涵盖自动配置、独立运行、starter依赖等优势。通过Web开发、微服务架构、批处理等适用场景分析,结合在线书店实战案例,演示项目初始化、数据库设计、分层架构实现全流程。同时探讨热部署、多环境配置、缓存机制与事务管理等高级特性,助你高效掌握Spring Boot开发技巧。代码示例详尽,适合从入门到进阶的学习者。
1076 0

推荐镜像

更多
  • DNS