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

本文涉及的产品
容器服务 Serverless 版 ACK Serverless,952元额度 多规格
容器服务 Serverless 版 ACK Serverless,317元额度 多规格
容器镜像服务 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为何还要对此接口继续提升

相关文章
|
2天前
|
负载均衡 大数据 测试技术
docker容器技术有哪些应用场景?
docker容器技术有哪些应用场景?
17 5
|
8天前
|
Cloud Native 持续交付 Docker
云原生入门指南:构建你的首个容器化应用
【8月更文挑战第30天】云原生技术,作为现代软件开发的风向标,正在改变我们构建、部署和管理应用程序的方式。本篇文章将引导你了解云原生的核心概念,并通过一个简单的代码示例,展示如何将传统应用转变为容器化的云原生应用。无论你是新手开发者还是希望扩展知识的IT专业人士,这篇文章都将是你探索云原生世界的起点。
|
11天前
|
运维 开发者 Docker
Docker容器化技术在运维中的应用实践
【8月更文挑战第27天】本文旨在探讨Docker容器化技术如何在现代运维工作中发挥核心作用,通过深入浅出的方式介绍Docker的基本概念、优势以及实际应用场景。文章将结合具体案例,展示如何利用Docker简化部署流程、提高资源利用率和加强应用的可移植性。读者将获得对Docker容器技术在实际运维中应用的全面认识,并能够理解其在提升运维效率与质量方面的重要性。
|
16天前
|
存储 监控 Linux
在Linux中,如何进行容器技术的应用?
在Linux中,如何进行容器技术的应用?
|
7天前
|
容器 C# Docker
WPF与容器技术的碰撞:手把手教你Docker化WPF应用,实现跨环境一致性的开发与部署
【8月更文挑战第31天】容器技术简化了软件开发、测试和部署流程,尤其对Windows Presentation Foundation(WPF)应用程序而言,利用Docker能显著提升其可移植性和可维护性。本文通过具体示例代码,详细介绍了如何将WPF应用Docker化的过程,包括创建Dockerfile及构建和运行Docker镜像的步骤。借助容器技术,WPF应用能在任何支持Docker的环境下一致运行,极大地提升了开发效率和部署灵活性。
20 0
|
7天前
|
应用服务中间件 Java Maven
掌控视图的力量!深入解析 JSF 视图管理,揭秘视图生命周期的秘密,让你的应用更高效!
【8月更文挑战第31天】JavaServer Faces (JSF) 是一种强大的框架,用于管理 Web 应用程序的视图。本文通过具体案例介绍 JSF 视图管理的基础知识,包括创建、管理和销毁视图的过程。首先,在 Eclipse 中创建一个新 JSF 项目,并配置 Maven 依赖。接着,在 `WEB-INF` 目录下配置 `web.xml` 文件,设置 JSF servlet。
19 0
|
7天前
|
JavaScript 应用服务中间件 nginx
玩转现代化部署:Angular与Docker的完美邂逅——细说如何通过容器化技术让您的Angular应用飞速上线,实现一键部署的高效与便捷,彻底告别复杂流程
【8月更文挑战第31天】容器化技术已成现代软件部署标配,为应用提供一致的运行环境。本文通过具体示例详细介绍了如何使用 Docker 容器化 Angular 应用,包括创建 Angular 项目、编写 Dockerfile 以及构建和运行 Docker 镜像的过程,显著提升了部署效率与可靠性。无论在本地调试还是生产部署,Docker 均提供了高效解决方案。
18 0
|
7天前
|
Kubernetes Cloud Native 云计算
云原生之旅:Docker容器化应用的简易指南
【8月更文挑战第31天】在云计算的浪潮中,云原生技术正成为企业数字化转型的重要推手。本文将通过深入浅出的方式,带领读者快速入门Docker容器技术,探索其在云原生生态中的关键作用。我们将从Docker的基础概念讲起,逐步过渡到实际操作,最后通过代码示例展示如何部署一个简单的应用。无论你是云原生新手还是希望深化理解,这篇文章都将为你提供一次丰富的学习之旅。
|
10天前
|
Kubernetes Linux 开发者
【实战秘籍】从零开始:用.NET与Docker打造现代化容器化应用之旅
【8月更文挑战第28天】本文详细介绍如何使用 .NET 框架构建并部署 Docker 容器化应用程序,涵盖环境搭建、项目创建、Dockerfile 编写等关键步骤。首先安装必要软件,如 Visual Studio 2022 及 Docker Desktop。接着创建 .NET Core 控制台应用,并在项目根目录编写 Dockerfile 文件。使用 .NET 运行时基础镜像,复制二进制文件,指定入口点。运行命令构建镜像并测试容器。为实现通信,映射端口。最后,标签化镜像并推送到 Docker Hub,为生产环境部署做好准备。掌握这些步骤,即可轻松应对从小型项目到大规模应用的各种需求。
31 0
|
13天前
|
存储 大数据 索引
【Azure Contianer Apps】在云上使用容器应用时收集日志遇见延迟问题
【Azure Contianer Apps】在云上使用容器应用时收集日志遇见延迟问题
下一篇
DDNS