SpringBoot自动装配机制分析

  上篇分析了SpringBoot应用的启动流程,说到过自动装配发生在加载配置类并解析出对应的BeanDefinition这一阶段,同时我们也简要说明了@EnableAutoConfiguration是通过@Import元注解引入的AutoConfigurationImportSelector来实现的,今天就来分析下吧(基于spring-boot-2.1.6.RELEASE)。

Preface

package

  典型的SpringBoot应用大多有着类似上图的包结构,我们知道@SpringBootApplication是一个复合注解,它包括:

  1. @Configuration
  2. @ComponentScan
  3. @EnableAutoConfiguration

这就意味着左侧Application这个类可以作为配置类来定义组件,比如添加@Bean标注的方法;同时它所在的包com.anyoptional作为组件扫描的根包,所有定义在其下及其子包下的组件都会被Spring IoC容器管理;最后是通过自动装配机制导入的外部配置。

  再提一嘴@Import元注解,它支持的导入类型有三种:

  1. @Configuration配置类,直接向容器导入组件
  2. ImportSelector实现类,返回@Configuration配置类,间接向容器导入组件
  3. ImportBeanDefinitionRegistrar实现类,携带BeanDefinitionRegistry,支持直接向容器中注册组件

How

  AutoConfigurationImportSelector实现了DeferredImportSelector接口——ImportSelector的一个变种,它的运行时机在所有@Configuration配置类被处理完毕之后。注意这个时间点,此时所有用户自定义的组件已经得到解析,这不正是条件注解@Conditional生效的好时机吗?大家比较熟悉的@ConditionalOnMissingBean@ConditionalOnProperty均是@Conditional元注解衍生出来的。spring-boot-autoconfigure虽然大量依赖@Conditional来实现条件注入,不过这部分逻辑却是在spring-context中实现的,有兴趣的话大家可以翻翻ConfigurationClassPostProcessor的源码。

selectImports(...)

@Override
public String[] selectImports(AnnotationMetadata annotationMetadata) {
    // 查看配置项 spring.boot.enableautoconfiguration
    // 看看是否允许自动装配
   if (!isEnabled(annotationMetadata)) {
       // 不允许的话返回空数组了事
      return NO_IMPORTS;
   }
    // 解析由 auto-configure annotation processor 生成的元数据
    // 默认位于 META-INF/spring-autoconfigure-metadata.properties
    // 里面的信息主要有两部分:
    //  1. 自动配置类的导入条件,用来做过滤,优点是不用实例化配置类再做判断
    //  2. 自动配置类的相对顺序,用来调整加载顺序
   AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader
         .loadMetadata(this.beanClassLoader);
    // AutoConfigurationEntry 包含需要导入的自动配置类和需要排除的自动配置类
    // 这一步便是通过 Spring SPI 机制加载所有的自动配置类,当然这里面还包含
    // 过滤、排序等一系列操作
   AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(autoConfigurationMetadata,
         annotationMetadata);
    // 返回需要由容器加载的自动配置类
   return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
}
AutoConfigurationMetadataLoader

  AutoConfigurationMetadataLoader的源码并不复杂,默认情况下它读取并解析位于META-INF下的spring-autoconfigure-metadata.properties文件。这个文件由AutoConfigureAnnotationProcessor解析class file生成(位于包spring-boot-autoconfigure-processor),它主要包含以下两个方面的内容:

  1. 自动配置类的导入条件
  2. 自动配置类的相对顺序
# 截取自 spring-boot-autoconfifure 下的 META-INF/spring-autoconfigure-metadata.properties
org.springframework.boot.autoconfigure.jdbc.JdbcTemplateAutoConfiguration.AutoConfigureAfter=org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration
org.springframework.boot.autoconfigure.jdbc.JdbcTemplateAutoConfiguration.ConditionalOnSingleCandidate=javax.sql.DataSource

对于上面这个例子,说明:

  1. JdbcTemplateAutoConfiguration需要在DataSourceAutoConfiguration之后加载
  2. JdbcTemplateAutoConfiguration是否生效取决于容器中是否存在唯一的类型为javax.sql.DataSourceBean

这其实很好理解,因为JdbcTemplate操作数据库的能力来源于标准的javax.sql.DataSource,而javax.sql.DataSource默认情况下由DataSourceAutoConfiguration提供。

// AutoConfigureAnnotationProcessor 的声明
@SupportedAnnotationTypes({ "org.springframework.context.annotation.Configuration",
        "org.springframework.boot.autoconfigure.condition.ConditionalOnClass",
        "org.springframework.boot.autoconfigure.condition.ConditionalOnBean",
        "org.springframework.boot.autoconfigure.condition.ConditionalOnSingleCandidate",
        "org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication",
        "org.springframework.boot.autoconfigure.AutoConfigureBefore",
        "org.springframework.boot.autoconfigure.AutoConfigureAfter",
        "org.springframework.boot.autoconfigure.AutoConfigureOrder" })
public class AutoConfigureAnnotationProcessor extends AbstractProcessor { 
    // omitted...
}

翻一下AutoConfigureAnnotationProcessor的源码,可以清楚地看到它支持的注解类型。因为AutoConfigureAnnotationProcessor解析的是class file,所以可以实现在不实例化自动配置类(比如上例的JdbcTemplateAutoConfiguration)的情况下判断它是否可以被容器加载,算是对内存的一点节约吧。

AutoConfigurationEntry
protected static class AutoConfigurationEntry {
    // 需要被加载的自动配置类
    private final List<String> configurations;
    // 不需要被加载的自动配置类
    private final Set<String> exclusions;
    
    // rest are omitted...
}

protected AutoConfigurationEntry getAutoConfigurationEntry(
    AutoConfigurationMetadata autoConfigurationMetadata, AnnotationMetadata annotationMetadata) {
   if (!isEnabled(annotationMetadata)) {
      return EMPTY_ENTRY;
   }
    // 获取 @EnableAutoConfiguration 注解 属性 -> 属性值 的映射
    // 其实就是声明的 exclude/excludeName
   AnnotationAttributes attributes = getAttributes(annotationMetadata);
    // 通过 Spring SPI 机制,获取 spring.factories 文件中所有以
    // @EnableAutoConfiguration 注解的全限定名为 key 的值,这些值对应着自动配置类的名称
   List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
    // 通过 Set 去重
   configurations = removeDuplicates(configurations);
    // 提取由 @EnableAutoConfiguration#exclude/excludeName 和 spring.autoconfigure.exclude
    // 指定的自动配置类的名称,这些配置类不需要被容器加载
   Set<String> exclusions = getExclusions(annotationMetadata, attributes);
    // 检查这些待排除的自动配置类是否存在,并且它们是否真的属于自动配置类的一员
   checkExcludedClasses(configurations, exclusions);
    // 从自动配置类集合中排除这些不需要加载的
   configurations.removeAll(exclusions);
    // 根据 AutoConfigurationMetadata 进行过滤
   configurations = filter(configurations, autoConfigurationMetadata);
    // 通过 SPI 机制加载 AutoConfigurationImportListener(s) 进行回调
   fireAutoConfigurationImportEvents(configurations, exclusions);
    // 返回结果
   return new AutoConfigurationEntry(configurations, exclusions);
}

  getAutoConfigurationEntry(...)主要做了下面几件事:

  1. 汇总需要排除的自动配置类
  2. 通过Spring SPI机制提取所有待加载的自动配置类
  3. 过滤第2步得到的集合,剔除不符合要求或不需要进行加载的
  4. 回调AutoConfigurationImportListener,监听器同样由Spring SPI机制加载

其中第12和第4步都比较简单,大家自己看看就可以了,我们重点来说说第3步——过滤。

private List<String> filter(List<String> configurations, 
                            AutoConfigurationMetadata autoConfigurationMetadata) {
   long startTime = System.nanoTime();
   String[] candidates = StringUtils.toStringArray(configurations);
    // 对应 configurations 中自动配置类的下标,标记其是否需要被排除
   boolean[] skip = new boolean[candidates.length];
   boolean skipped = false;
    // 通过 SPI 机制加载 AutoConfigurationImportFilter 来进行过滤
   for (AutoConfigurationImportFilter filter : getAutoConfigurationImportFilters()) {
      invokeAwareMethods(filter);
       // 判定
      boolean[] match = filter.match(candidates, autoConfigurationMetadata);
      for (int i = 0; i < match.length; i++) {
          // 未通过的话进行记录
         if (!match[i]) {
            skip[i] = true;
             // 清掉数据,如此下一个 Filter 就不用再判定了
            candidates[i] = null;
            skipped = true;
         }
      }
   }
    // 都有效直接返回即可,否则...
   if (!skipped) {
      return configurations;
   }
    // 就需要遍历一次,剔除不需要的
   List<String> result = new ArrayList<>(candidates.length);
   for (int i = 0; i < candidates.length; i++) {
      if (!skip[i]) {
         result.add(candidates[i]);
      }
   }
   if (logger.isTraceEnabled()) {
      int numberFiltered = configurations.size() - result.size();
      logger.trace("Filtered " + numberFiltered + " auto configuration class in "
            + TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startTime) + " ms");
   }
   return new ArrayList<>(result);
}

可以看到对自动配置类的过滤工作是通过代理给AutoConfigurationImportFilter来完成的,这些过滤器同样是通过Spring SPI机制进行加载的,默认注册的有:

# 截取自 spring-boot-autoconfigure 包下的 META-INF/spring.factories
# Auto Configuration Import Filters
org.springframework.boot.autoconfigure.AutoConfigurationImportFilter=\
org.springframework.boot.autoconfigure.condition.OnBeanCondition,\
org.springframework.boot.autoconfigure.condition.OnClassCondition,\
org.springframework.boot.autoconfigure.condition.OnWebApplicationCondition

都是好东西,挑一挑吧。那就挑OnClassCondition来看看吧。

OnClassCondition

  翻开OnClassCondition的源码,可以看到AutoConfigurationImportFilter#match(...)是其父类实现的,然后代理给了模板方法getOutcomes(...),我们直接看这个方法就行。

// 实际工作又代理给了 OutcomesResolver, 注意这里有个并行的优化
@Override
protected final ConditionOutcome[] getOutcomes(String[] autoConfigurationClasses, 
                                               AutoConfigurationMetadata autoConfigurationMetadata) {
    // 天下自动配置类千千万
    int split = autoConfigurationClasses.length / 2;
    // 你一半
    OutcomesResolver firstHalfResolver = createOutcomesResolver(autoConfigurationClasses, 0, split,
    autoConfigurationMetadata);
    // 我一半
    OutcomesResolver secondHalfResolver = new StandardOutcomesResolver(autoConfigurationClasses, split,
    autoConfigurationClasses.length, autoConfigurationMetadata, getBeanClassLoader());
    // 合并两个 OutcomesResolver 的处理结果
    ConditionOutcome[] secondHalf = secondHalfResolver.resolveOutcomes();
    ConditionOutcome[] firstHalf = firstHalfResolver.resolveOutcomes();
    ConditionOutcome[] outcomes = new ConditionOutcome[autoConfigurationClasses.length];
    System.arraycopy(firstHalf, 0, outcomes, 0, firstHalf.length);
    System.arraycopy(secondHalf, 0, outcomes, split, secondHalf.length);
    return outcomes;
}

只能说大牛写的代码都比较绕吧,getOutcomes(...)也是个代理商,实际的工作都交给OutcomesResolver去完成了。注意这里分而治之的思想,待处理的自动配置类被一分为二,由两个线程并行完成。那继续看看StandardOutcomesResolver是怎么处理的呗。

private final class StandardOutcomesResolver implements OutcomesResolver {

    // fields、ctor are omitted...

   @Override
   public ConditionOutcome[] resolveOutcomes() {
      return getOutcomes(this.autoConfigurationClasses, this.start, this.end, this.autoConfigurationMetadata);
   }

   private ConditionOutcome[] getOutcomes(String[] autoConfigurationClasses, int start, int end,
         AutoConfigurationMetadata autoConfigurationMetadata) {
      ConditionOutcome[] outcomes = new ConditionOutcome[end - start];
      for (int i = start; i < end; i++) {
         String autoConfigurationClass = autoConfigurationClasses[i];
         if (autoConfigurationClass != null) {
             // 获取当前 autoConfigurationClass 的 ConditionalOnClass
             // 比如 example.MyConfig.ConditionalOnClass=example.YourConfig
             // 这里就会返回 example.YourConfig,注意这个值是 CSV 格式的
            String candidates = autoConfigurationMetadata.get(autoConfigurationClass, "ConditionalOnClass");
            if (candidates != null) {
                // 判定 candidates 代表的类是否存在于 classpath 下
               outcomes[i - start] = getOutcome(candidates);
            }
         }
      }
      return outcomes;
   }
   // 对 candidates 的格式进行处理
   private ConditionOutcome getOutcome(String candidates) {
      try {
         if (!candidates.contains(",")) {
            return getOutcome(candidates, this.beanClassLoader);
         }
          // CSV 格式分解
         for (String candidate : StringUtils.commaDelimitedListToStringArray(candidates)) {
            ConditionOutcome outcome = getOutcome(candidate, this.beanClassLoader);
            if (outcome != null) {
               return outcome;
            }
         }
      }
      catch (Exception ex) {
         // We'll get another chance later
      }
      return null;
   }
    // 最终的判定逻辑
   private ConditionOutcome getOutcome(String className, ClassLoader classLoader) {
       // ClassNameFilter 无非是通过类加载机制来进行判定
       // 比如 classLoader.loadClass(className) 或者 classLoader 为空则是 Class.forName(className)
      if (ClassNameFilter.MISSING.matches(className, classLoader)) {
         return ConditionOutcome.noMatch(ConditionMessage.forCondition(ConditionalOnClass.class)
               .didNotFind("required class").items(Style.QUOTE, className));
      }
      return null;
   }
}

前面分析了AutoConfigurationMetadata的加载,这里就利用上了这部分元数据——由autoConfigurationMetadata.get(autoConfigurationClass, "ConditionalOnClass")体现,剩下的只需要通过类加载机制判定一下这些类是否存在就可以了。注意整个判定过程中自动配置类并没有实例化,仅仅使用了它的全限定名。

getImportGroup()

  前面对selectImports(...)的分析已经可以解释SpringBoot的自动装配机制了,不过这里还有一个问题,那就是没有处理自动配置类的加载顺序。我们知道自动配置类可以使用@AutoConfigureBefore@AutoConfigureAfter来指定相对顺序,使用AutoConfigureOrder来指定绝对顺序,不过这在selectImports(...)中一点儿也没有得到体现。答案就在getImportGroup(),它返回的AutoConfigurationGroup处理了这个遗留问题。

@Override
public Class<? extends Group> getImportGroup() {
    return AutoConfigurationGroup.class;
}
AutoConfigurationGroup
private static class AutoConfigurationGroup
      implements DeferredImportSelector.Group, BeanClassLoaderAware, BeanFactoryAware, ResourceLoaderAware {

    // fields、*Aware are omitted...

   @Override
   public void process(AnnotationMetadata annotationMetadata, DeferredImportSelector deferredImportSelector) {
      Assert.state(deferredImportSelector instanceof AutoConfigurationImportSelector,
            () -> String.format("Only %s implementations are supported, got %s",
                  AutoConfigurationImportSelector.class.getSimpleName(),
                  deferredImportSelector.getClass().getName()));
       // 上一节分析过了,这里就得到了待加载和被排除的自动配置类集合
      AutoConfigurationEntry autoConfigurationEntry = ((AutoConfigurationImportSelector) deferredImportSelector)
            .getAutoConfigurationEntry(getAutoConfigurationMetadata(), annotationMetadata);
      this.autoConfigurationEntries.add(autoConfigurationEntry);
       // 自动装配类的类名 -> 标注 @EnableAutoConfiguration 类的注解元信息
      for (String importClassName : autoConfigurationEntry.getConfigurations()) {
         this.entries.putIfAbsent(importClassName, annotationMetadata);
      }
   }

   @Override
   public Iterable<Entry> selectImports() {
      if (this.autoConfigurationEntries.isEmpty()) {
         return Collections.emptyList();
      }
      Set<String> allExclusions = this.autoConfigurationEntries.stream()
          // 待排除的自动配置类集合  
          .map(AutoConfigurationEntry::getExclusions)
          // 降维
          .flatMap(Collection::stream)
          // 收集成 Set 去重
          .collect(Collectors.toSet());
      Set<String> processedConfigurations = this.autoConfigurationEntries.stream()
            // 待加载的自动配置类集合 
            .map(AutoConfigurationEntry::getConfigurations)
            // 降维
            .flatMap(Collection::stream)
            // 收集成 Set 去重并保持顺序
            .collect(Collectors.toCollection(LinkedHashSet::new));
       // 剔除明确不需要加载的
      processedConfigurations.removeAll(allExclusions);
      // 对剩下的进行排序并转换成Entry
      return sortAutoConfigurations(processedConfigurations, getAutoConfigurationMetadata()).stream()
            .map((importClassName) -> new Entry(this.entries.get(importClassName), importClassName))
            .collect(Collectors.toList());
   }

  // getAutoConfigurationMetadata() omitted...

   private List<String> sortAutoConfigurations(Set<String> configurations,
         AutoConfigurationMetadata autoConfigurationMetadata) {
       // 使用 AutoConfigurationSorter 进行排序
      return new AutoConfigurationSorter(getMetadataReaderFactory(), autoConfigurationMetadata)
            .getInPriorityOrder(configurations);
   }

    // 如果 spring boot starter 未引入 spring-boot-autoconfigure-processor.jar
    // 那就不会生成 spring-autoconfigure-metadata.properties
    // 这样的话就需要 MetadataReaderFactory 创建 MetadataReader 来读取
    // 和自动配置顺序相关的注解了,比如 @AutoConfigureBefore
   private MetadataReaderFactory getMetadataReaderFactory() {
      try {
         return this.beanFactory.getBean(SharedMetadataReaderFactoryContextInitializer.BEAN_NAME,
               MetadataReaderFactory.class);
      } catch (NoSuchBeanDefinitionException ex) {
         return new CachingMetadataReaderFactory(this.resourceLoader);
      }
   }
}

AutoConfigurationGroup确实只加入了对自动配置类顺序的处理,其它逻辑是相同的,我们的重心自然也转到AutoConfigurationSorter

AutoConfigurationSorter
public List<String> getInPriorityOrder(Collection<String> classNames) {
   AutoConfigurationClasses classes = new AutoConfigurationClasses(this.metadataReaderFactory,
         this.autoConfigurationMetadata, classNames);
   List<String> orderedClassNames = new ArrayList<>(classNames);
   // 1. 按字母表顺序排序
   Collections.sort(orderedClassNames);
   // 2. 按 @AutoConfigureOrder 指定的优先级排序
   orderedClassNames.sort((o1, o2) -> {
      int i1 = classes.get(o1).getOrder();
      int i2 = classes.get(o2).getOrder();
      return Integer.compare(i1, i2);
   });
   // 3. 最后根据 @AutoConfigureBefore @AutoConfigureAfter 来进行微调
   orderedClassNames = sortByAnnotation(classes, orderedClassNames);
   return orderedClassNames;
}

  鉴于本篇的篇幅已经很长了,这里仅仅标注了一下AutoConfigurationSorter进行排序的大体步骤,具体细节各位看官自己去扣一扣吧。

End

  今天和大家分享了SpringBoot自动装配机制的原理,相信大家看完后可以开心地编排自定义的starter了,完。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,839评论 6 482
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,543评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 153,116评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,371评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,384评论 5 374
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,111评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,416评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,053评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,558评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,007评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,117评论 1 334
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,756评论 4 324
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,324评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,315评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,539评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,578评论 2 355
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,877评论 2 345

推荐阅读更多精彩内容