【小家Spring】Spring向容器注册Bean的高级应用:@Import、DeferredImportSelector、ImportBeanDefinitionRegistrar的使用(中)

本文涉及的产品
容器服务 Serverless 版 ACK Serverless,317元额度 多规格
容器服务 Serverless 版 ACK Serverless,952元额度 多规格
容器镜像服务 ACR,镜像仓库100个 不限时长
简介: 【小家Spring】Spring向容器注册Bean的高级应用:@Import、DeferredImportSelector、ImportBeanDefinitionRegistrar的使用(中)
  @Nullable
  protected final SourceClass doProcessConfigurationClass(ConfigurationClass configClass, SourceClass sourceClass)
      throws IOException {
    // Recursively process any member (nested) classes first
    // 递归循环的解析内部类的配置类(因此,即使是内部类的配置类,我们Spring也是支持的,很强大有木有)
    processMemberClasses(configClass, sourceClass);
    // Process any @PropertySource annotations
    // 处理@PropertySources注解和@PropertySource注解,交给processPropertySource去解析
    // 显然必须是ConfigurableEnvironment的环境采取解析,否则发出警告:会忽略这个不进行解析
    for (AnnotationAttributes propertySource : AnnotationConfigUtils.attributesForRepeatable(
        sourceClass.getMetadata(), PropertySources.class,
        org.springframework.context.annotation.PropertySource.class)) {
      if (this.environment instanceof ConfigurableEnvironment) {
        processPropertySource(propertySource);
      }
      else {
        logger.warn("Ignoring @PropertySource annotation on [" + sourceClass.getMetadata().getClassName() +
            "]. Reason: Environment must implement ConfigurableEnvironment");
      }
    }
    // Process any @ComponentScan annotations
    // 解析@ComponentScans和@ComponentScan注解,进行包扫描。最终交给ComponentScanAnnotationParser#parse方法进行处理
    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
        // 这一步非常重要:如果被扫描的Bean定义信息,还是属于@Configuration的配置组件,那就继续调用本类的parse方法,进行递归解析==============
        // 所以我们在进行包扫描的时候,也是会扫描到@Configuration并且进行解析的。。。
        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方法
    processImports(configClass, sourceClass, getImports(sourceClass), true);
    // Process any @ImportResource annotations
    // 显然,先是处理了@Import,才过来解析@ImportResource的====最终交给environment.resolveRequiredPlaceholders(resource)去处理了
    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注解的方法们
    // 遍历@Bean注释的方法,添加到configClass中的BeanMethod
    // 这里需要注意的是:最终会实例化的时候是执行此工厂方法来获取到对应实例的
    // if (mbd.getFactoryMethodName() != null) { ... }  这里会是true,从而执行此方法内部逻辑。   原理同XML中的FactoryMethod方式创建Bean
    Set<MethodMetadata> beanMethods = retrieveBeanMethodMetadata(sourceClass);
    for (MethodMetadata methodMetadata : beanMethods) {
      configClass.addBeanMethod(new BeanMethod(methodMetadata, configClass));
    }
    // Process default methods on interfaces
    // 这个特别有意思:处理接口中被@Bean注解默认方法,代码如下
    // 因为JDK8以后接口可以写default方法了,所以接口竟然也能给容器里注册Bean了
    // 但是需要注意:这里的意思并不是说你写个接口然后标注上@Configuration注解,然后@Bean注入就可以了
    // 这个解析的意思是我们的配置类可以实现接口,然后在所实现的接口里面若有@Bean的注解默认方法,是会加入到容器的
    processInterfaces(configClass, sourceClass);
    // Process superclass, if any
    // 如果有父类的话,则返回父类进行进一步的解析,否则返回null
    // 这个也是很厉害的,如果有父类,也是能够继续解析的。@EnableWebMvc中的DelegatingWebMvcConfiguration就是这么玩的
    // 它自己标注了@Configuration注解,但是真正@Bean注入,都是它父类去干的
    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;
  }


因此本文,就重点来看看processImports这个方法,如下:


  private void processImports(ConfigurationClass configClass, SourceClass currentSourceClass,
      Collection<SourceClass> importCandidates, boolean checkForCircularImports) {
    // 相当于没有找到@Import注解,那就不处理了
    // 说明:获取@Import是递归获取,任意子类父类上标注有都行的
    if (importCandidates.isEmpty()) {
      return;
    }
    //循环依赖检查:如果存在循环依赖的话,则直接抛出异常(比如你@Import我,我@Import你这种情况)
    if (checkForCircularImports && isChainedImportOnStack(configClass)) {
      this.problemReporter.error(new CircularImportProblem(configClass, this.importStack));
    }
    else {
      this.importStack.push(configClass);
      try {
        // 依次处理每个@Import里面候选的Bean们
        for (SourceClass candidate : importCandidates) {
          // 分之一:如果实现了ImportSelector接口(又分为两种,因为有子接口DeferredImportSelector呢)
          if (candidate.isAssignable(ImportSelector.class)) {
            // Candidate class is an ImportSelector -> delegate to it to determine imports
            Class<?> candidateClass = candidate.loadClass();
            // 根据空的构造函数,把这个Bean实例化出来,
            ImportSelector selector = BeanUtils.instantiateClass(candidateClass, ImportSelector.class);
            // 这里面注入了一下感知接口的元素,包括environment、resourceLoader、registry等等(实现了DeferredImportSelector也在此处注入了哦)
            ParserStrategyUtils.invokeAwareMethods(selector, this.environment, this.resourceLoader, this.registry);
            // 判断是否是DeferredImportSelectorHolder的子类,是的话先加入进入  不处理先
            if (this.deferredImportSelectors != null && selector instanceof DeferredImportSelector) {
              this.deferredImportSelectors.add(
                  new DeferredImportSelectorHolder(configClass, (DeferredImportSelector) selector));
            }
            // 否则立马调用它的`selectImports`方法,拿到一个BeanName的数组
            else {
              String[] importClassNames = selector.selectImports(currentSourceClass.getMetadata());
              Collection<SourceClass> importSourceClasses = asSourceClasses(importClassNames);
              // 这里面高级了:因为我们这里放进去的Bean,有可能是普通Bean,当然也还有可能是实现了ImportSelector等等接口的,因此此处继续调用processImports进行处理,递归的效果~~~~
              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);
            // 完成了实例化后和Aware方法后,添加进configClass类的属性importBeanDefinitionRegistrars里先缓存着(至于执行时机,留给下面讲吧)
            configClass.addImportBeanDefinitionRegistrar(registrar, currentSourceClass.getMetadata());
          }
          else {
            // Candidate class not an ImportSelector or ImportBeanDefinitionRegistrar ->
            // process it as an @Configuration class
            // 什么都接口都没有实现,那就是普通的配置类嘛,那就直接交给processConfigurationClass()去处理了
            // 备注:这个方法的处理流程,请参照上面哦
            // 这里面有个特别重要的地方:是candidate.asConfigClass(configClass)这一句,给包装陈一个ConfigurationClass去处理
            // 因为传入了configClass属于它的importedBy属性,这样一来ConfigurationClass#isImported()就返回true了,表面这个Bean是被单纯的、单纯的、单纯的的导入进来的
            this.importStack.registerImport(
                currentSourceClass.getMetadata(), candidate.getMetadata().getClassName());
            processConfigurationClass(candidate.asConfigClass(configClass));
          }
        }
      }
      catch (BeanDefinitionStoreException ex) {
        throw ex;
      }
      catch (Throwable ex) {
        throw new BeanDefinitionStoreException(
            "Failed to process import candidates for configuration class [" +
            configClass.getMetadata().getClassName() + "]", ex);
      }
      finally {
        // 上面push,下面pop出来
        this.importStack.pop();
      }
    }
  }


从上面的源码处理过程,我们可以很清楚的知道了ImportSelector#selectImports执行时机,然后并且把DeferredImportSelector和ImportBeanDefinitionRegistrar都先装起来了。


doProcessConfigurationClass执行完成之后,processConfigurationClass也就执行完了,接下来就开始执行顶层parse方法内部的:processDeferredImportSelectors():


附录上DeferredImportSelector的源码:(Spring4和Spring4差异很大) 本文都是基于Spring5进行讲解的


// Spring4的源码,啥都木有
public interface DeferredImportSelector extends ImportSelector {
}
// Sparing5的源码,加了不少东西
public interface DeferredImportSelector extends ImportSelector {
  @Nullable
  default Class<? extends Group> getImportGroup() {
    return null;
  }
  // 内部接口
  interface Group {
    void process(AnnotationMetadata metadata, DeferredImportSelector selector);
    Iterable<Entry> selectImports();
    // 内部的内部类
    class Entry {
      private final AnnotationMetadata metadata;
      private final String importClassName;
      public Entry(AnnotationMetadata metadata, String importClassName) {
        this.metadata = metadata;
        this.importClassName = importClassName;
      }
      public AnnotationMetadata getMetadata() {
        return this.metadata;
      }
      public String getImportClassName() {
        return this.importClassName;
      }
      @Override
      public boolean equals(Object o) {
        if (this == o) {
          return true;
        }
        if (o == null || getClass() != o.getClass()) {
          return false;
        }
        Entry entry = (Entry) o;
        return Objects.equals(this.metadata, entry.metadata) &&
            Objects.equals(this.importClassName, entry.importClassName);
      }
      @Override
      public int hashCode() {
        return Objects.hash(this.metadata, this.importClassName);
      }
    }
  }
}


源码的差异很大,就就造成了processDeferredImportSelectors的处理方式不尽相同。同样的,本文就以Spring5的源码进行讲解了:


  private void processDeferredImportSelectors() {
    List<DeferredImportSelectorHolder> deferredImports = this.deferredImportSelectors;
    this.deferredImportSelectors = null;
    if (deferredImports == null) {
      return;
    }
    // 排序:注意这个比较器。它是按照PriorityOrdered、Ordered等进行优先级排序的
    // 因此我们可以看到一大特性:DeferredImportSelector是支持Order排序的
    deferredImports.sort(DEFERRED_IMPORT_COMPARATOR);
    // 这个Map厉害了,key竟然是Object。。。
    Map<Object, DeferredImportSelectorGrouping> groupings = new LinkedHashMap<>();
    Map<AnnotationMetadata, ConfigurationClass> configurationClasses = new HashMap<>();
    // 对这些个DeferredImportSelector一个个处理吧
    //遍历DeferredImportSelector接口集合,获取Group集合类,默认为DefaultDeferredImportSelectorGroup
    for (DeferredImportSelectorHolder deferredImport : deferredImports) {
      // getImportGroup()方法是DeferredImportSelector接口的default方法,若不复写,默认return null
      // 该接口的作用是:子类可以对一些Import的类进行分类 
      //Group 为DeferredImportSelector的一个内部接口~~~~~~~~~~~
      Class<? extends Group> group = deferredImport.getImportSelector().getImportGroup();
      // 按照group 或者 deferredImport 进行分组
      DeferredImportSelectorGrouping grouping = groupings.computeIfAbsent((group == null ? deferredImport : group), (key) -> new DeferredImportSelectorGrouping(createGroup(group)));
      grouping.add(deferredImport);
      configurationClasses.put(deferredImport.getConfigurationClass().getMetadata(),
          deferredImport.getConfigurationClass());
    }
    //遍历Group集合,作用也是调用processImport()方法用于解析@Import
    for (DeferredImportSelectorGrouping grouping : groupings.values()) {
      grouping.getImports().forEach((entry) -> {
        ConfigurationClass configurationClass = configurationClasses.get(
            entry.getMetadata());
        try {
          processImports(configurationClass, asSourceClass(configurationClass),
              asSourceClasses(entry.getImportClassName()), false);
        }
        catch (BeanDefinitionStoreException ex) {
          throw ex;
        }
        catch (Throwable ex) {
          throw new BeanDefinitionStoreException(
              "Failed to process import candidates for configuration class [" +
                  configurationClass.getMetadata().getClassName() + "]", ex);
        }
      });
    }


DeferredImportSelector接口在Spring-core/Context中没有实现类。但是在Spring Boot的自动配置中有大量的实现。那么我们将在讲解Spring Boot源码分析的时候,会再回来重点讲解。也看看Spring5为何还要对此接口继续提升

相关文章
|
9天前
|
弹性计算 运维 负载均衡
容器化管理云上应用体验评测
从解读方案开始,带你领略容器化管理云上应用的奇妙之旅。
65 5
容器化管理云上应用体验评测
|
7天前
|
运维 Kubernetes 安全
容器化技术在现代运维中的应用与挑战
【7月更文挑战第19天】随着云计算和微服务架构的兴起,容器化技术已经成为现代运维工作的重要组成部分。本文将探讨容器技术如何简化运维流程、提高效率,并分析在实际应用中遇到的挑战及解决方案。我们将深入讨论Docker和Kubernetes等工具的使用场景,以及如何在保障系统安全的同时,实现快速部署和扩展。
|
3天前
|
安全 Java Spring
在Spring中,如何配置Bean的初始化方法和销毁方法
在Spring中,如何配置Bean的初始化方法和销毁方法
|
2天前
|
持续交付 云计算 开发者
Docker容器技术在软件开发中的应用
【7月更文挑战第25天】Docker容器技术凭借其轻量级、可移植和高效的特点,在软件开发中发挥着越来越重要的作用。通过容器化技术,开发者可以更加方便地构建、部署和管理应用程序,提高开发效率和应用程序的可靠性。随着云计算和现代应用开发的不断发展,Docker容器技术将在更多领域得到广泛应用,为企业的数字化转型提供有力支持。
|
2天前
|
XML Java 数据格式
Spring中的bean相关问题
Spring Bean是构建Spring应用的核心元素。熟悉Bean的声明方式、作用域、生命周期以及自动装配机制对于开发高效、健壮的Spring应用至关重要。希望以上内容能够为您在使用Spring框架时提供有效的指南和帮助。
4 1
|
6天前
|
运维 监控 Kubernetes
容器化技术在现代运维中的应用与挑战
【7月更文挑战第21天】随着微服务架构的兴起,容器化技术成为现代运维不可或缺的工具。Docker和Kubernetes等技术的广泛应用,不仅提升了部署效率和应用的可移植性,还带来了新的运维模式。然而,容器安全、性能监控和自动化管理等问题也随之凸显。本文将深入探讨容器化技术的应用实践和面临的主要挑战,为运维人员提供策略和建议。
25 3
|
6天前
|
存储 运维 安全
容器化技术在现代运维中的应用与挑战
【7月更文挑战第21天】随着云原生技术的飞速发展,容器化已成为现代运维不可或缺的一环。本文将深入探讨容器技术的核心优势,分析其在运维自动化、微服务架构及持续集成/持续部署(CI/CD)流程中的关键作用,同时识别并讨论容器化实施过程中遇到的主要挑战,包括安全性问题、存储与网络配置的复杂性以及状态管理等难题。通过案例分析,我们旨在为读者提供容器化技术在运维领域应用的全面视角,并指出未来发展趋势。
|
14天前
|
弹性计算 运维 负载均衡
解决方案评测|容器化管理云上应用
解决方案评测|容器化管理云上应用
|
6天前
|
运维 Kubernetes Docker
|
6天前
|
XML Java 数据格式
如果在创建Bean的过程中发生异常,Spring会如何处理
如果在创建Bean的过程中发生异常,Spring会如何处理