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

本文涉及的产品
容器镜像服务 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为何还要对此接口继续提升

相关文章
|
21小时前
|
Java 开发者 Spring
Spring 中 Bean 的生命周期
Spring 中 Bean 的生命周期
8 2
|
21小时前
|
Java 开发者 Spring
解析Spring中Bean的生命周期
解析Spring中Bean的生命周期
7 2
|
1天前
|
XML Java 数据格式
Spring框架第三章(基于注解管理bean)
Spring框架第三章(基于注解管理bean)
|
1天前
|
XML Java 数据格式
Spring框架第二章(基于XML管理bean)
Spring框架第二章(基于XML管理bean)
|
1天前
|
存储 Java 数据安全/隐私保护
Spring Boot中实现邮箱登录/注册接口
Spring Boot中实现邮箱登录/注册接口
|
2天前
|
人工智能 前端开发 Java
基于Spring框架的GPT应用
基于Spring框架的GPT应用
11 0
|
4天前
|
Java Linux Spring
在 Linux 系统中将 Spring Boot 应用作为系统服务运行
【6月更文挑战第11天】最近由于一些原因,服务器经常会重启,每次重启后需要手动启动 Spring Boot 的工程,因此我需要将其配置成开启自启动的服务。
23 1
|
5天前
|
Java Nacos 数据格式
Spring Cloud Nacos 详解:服务注册与发现及配置管理平台
Spring Cloud Nacos 详解:服务注册与发现及配置管理平台
25 3
|
3天前
|
NoSQL 关系型数据库 Redis
Docker的通俗理解和通过宿主机端口访问Redis容器的实例
本文目标:引导初学者入门Docker,理解镜像、容器和宿主机概念,学习常用Docker命令,特别是如何创建并从Redis容器通过宿主机端口访问。 关键点: - Docker核心:镜像(类)、容器(实例)、宿主机(运行环境)。 - `docker pull` 拉取镜像,如 `redis:3.0`。 - `docker run -d --name` 后台运行容器,如 `my-redis`。 - `-p` 参数做端口映射,如 `6379:6379`。 - `docker exec -it` 交互式进入容器,如 `bash` 或执行命令。
|
1天前
|
Linux Docker 容器
蓝易云 - net.ipv4.ip_forward=0导致docker容器无法与外部通信
完成以上步骤后,Docker容器应该能够正常与外部通信了。
8 2