【SpringBoot】配置加載


title: 【SpringBoot】配置加載
date: 2017-09-18 22:14:15
tags:

  • Java
  • Spring
    categories: Spring

前面的文章已經(jīng)基本講清楚從 Spring Boot 應(yīng)用完整的啟動過程了,不過中間過程缺少自動配置相關(guān)的功能實(shí)現(xiàn)說明。

我把功能配置分為兩個部分:

  • 自動化配置
  • 條件注解

以上兩個功能極大的簡化了 Spring Boot 應(yīng)用的配置

自動化配置

要弄清自動化配置的起源就得從入口類的 SpringBootApplication 注解說起:

@EnableAutoConfiguration
public @interface SpringBootApplication {
  // 省略注解的字段,別名
}

SpringBootApplication 作為一個組合注解,配置的功能是其核心功能,這部分功能由 EnableAutoConfiguration 注解實(shí)現(xiàn)。 EnableAutoConfiguration 也是個符合注解:

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import({EnableAutoConfigurationImportSelector.class})
public @interface EnableAutoConfiguration {
    String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";
    Class<?>[] exclude() default {};
    String[] excludeName() default {};
}

EnableAutoConfiguration 的關(guān)鍵是導(dǎo)入了 EnableAutoConfigurationImportSelector ,這類繼承自 AutoConfigurationImportSelector。在 Spring 容器刷新的過程中的 invokeBeanFactoryPostProcessors 方法會調(diào)用到 EnableAutoConfigurationImportSelector 的邏輯:

// EnableAutoConfigurationImportSelector
@Override
public String[] selectImports(AnnotationMetadata annotationMetadata) {
   if (!isEnabled(annotationMetadata)) {
      return NO_IMPORTS;
   }
   try {
      // 獲取注解屬性
      AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader
            .loadMetadata(this.beanClassLoader);
      AnnotationAttributes attributes = getAttributes(annotationMetadata);
      // 讀取spring.factories屬性文件中的數(shù)據(jù)
      List<String> configurations = getCandidateConfigurations(annotationMetadata,
            attributes);
      // 刪除重復(fù)的配置類
      configurations = removeDuplicates(configurations);
      configurations = sort(configurations, autoConfigurationMetadata);
      // 找到@EnableAutoConfiguration注解中定義的需要被過濾的配置類
      Set<String> exclusions = getExclusions(annotationMetadata, attributes);
      checkExcludedClasses(configurations, exclusions);
      // 刪除這些需要被過濾的配置類
      configurations.removeAll(exclusions);
      configurations = filter(configurations, autoConfigurationMetadata);
      fireAutoConfigurationImportListeners(configurations, exclusions);
      // 返回最終得到的自動化配置類
      return configurations.toArray(new String[configurations.size()]);
   }
   catch (IOException ex) {
      throw new IllegalStateException(ex);
   }
}

protected List<String> getCandidateConfigurations(AnnotationMetadata metadata,
      AnnotationAttributes attributes) {
   // 調(diào)用SpringFactoriesLoader的loadFactoryNames靜態(tài)方法
   // getSpringFactoriesLoaderFactoryClass方法返回的是EnableAutoConfiguration類對象
   List<String> configurations = SpringFactoriesLoader.loadFactoryNames(
         getSpringFactoriesLoaderFactoryClass(), getBeanClassLoader());
   Assert.notEmpty(configurations,
         "No auto configuration classes found in META-INF/spring.factories. If you "
               + "are using a custom packaging, make sure that file is correct.");
   return configurations;
}

EnableAutoConfigurationImportSelector 會使用 SpringFactoriesLoader 加載相應(yīng)的配置

// SpringFactoriesLoader 
public static List<String> loadFactoryNames(Class<?> factoryClass, ClassLoader classLoader) {
   // 解析出properties文件中需要的key值
   String factoryClassName = factoryClass.getName();
   try {
      // 常量FACTORIES_RESOURCE_LOCATION的值為META-INF/spring.factories
      // 使用類加載器找META-INF/spring.factories資源
      Enumeration<URL> urls = (classLoader != null ? classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
            ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
      List<String> result = new ArrayList<String>();
      while (urls.hasMoreElements()) {
         URL url = urls.nextElement();
         Properties properties = PropertiesLoaderUtils.loadProperties(new UrlResource(url));
         String factoryClassNames = properties.getProperty(factoryClassName);
         result.addAll(Arrays.asList(StringUtils.commaDelimitedListToStringArray(factoryClassNames)));
      }
      return result;
   }
   catch (IOException ex) {
      throw new IllegalArgumentException("Unable to load [" + factoryClass.getName() +
            "] factories from location [" + FACTORIES_RESOURCE_LOCATION + "]", ex);
   }
}

條件注解

條件注解實(shí)現(xiàn)

以常見的 ConditionalOnClass 注解為例:

@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Conditional(OnClassCondition.class)
public @interface ConditionalOnClass {
   Class<?>[] value() default {};  // 要匹配的類
   String[] name() default {};  // 要匹配的類名
}

必要重要的是 Conditional 注解:

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD})
public @interface Conditional {
   Class<? extends Condition>[] value();
}

從命名可以看出 Conditional 注解的 value 屬性是具體的 Condition 實(shí)現(xiàn),Spring Boot 提供了通用的 Condition 抽象類: SpringBootCondition,其完成了具體匹配邏輯之外的其他模板:

// SpringBootCondition

@Override
public final boolean matches(ConditionContext context,
      AnnotatedTypeMetadata metadata) {
   // 獲得類名或方法名
   String classOrMethodName = getClassOrMethodName(metadata);
   try {
      // 抽象方法由子類實(shí)現(xiàn)匹配邏輯
      // 返回結(jié)構(gòu)包裝了匹配結(jié)果和log信息
      ConditionOutcome outcome = getMatchOutcome(context, metadata);
      // log
      logOutcome(classOrMethodName, outcome);
      // 報告一下
      recordEvaluation(context, classOrMethodName, outcome);
      // 返回匹配結(jié)果
      return outcome.isMatch();
   }
   catch // 省略異常處理
}

具體到 ConditionalOnClass 使用的是 OnClassCondition:

@Override
public ConditionOutcome getMatchOutcome(ConditionContext context,
      AnnotatedTypeMetadata metadata) {
   ClassLoader classLoader = context.getClassLoader();
   ConditionMessage matchMessage = ConditionMessage.empty();
   // 獲得 ConditionalOnClass 注解的屬性
   List<String> onClasses = getCandidates(metadata, ConditionalOnClass.class);
   if (onClasses != null) {
      // 屬性不為空
      // 獲取具體缺失的類
      List<String> missing = getMatches(onClasses, MatchType.MISSING, classLoader);
      if (!missing.isEmpty()) {
         return ConditionOutcome
               .noMatch(ConditionMessage.forCondition(ConditionalOnClass.class)
                     .didNotFind("required class", "required classes")
                     .items(Style.QUOTE, missing));
      }
      matchMessage = matchMessage.andCondition(ConditionalOnClass.class)
            .found("required class", "required classes").items(Style.QUOTE,
                  getMatches(onClasses, MatchType.PRESENT, classLoader));
   }
   // 獲得 ConditionalOnMissingClass 注解的屬性
   List<String> onMissingClasses = getCandidates(metadata,
         ConditionalOnMissingClass.class);
   if (onMissingClasses != null) {
      List<String> present = getMatches(onMissingClasses, MatchType.PRESENT,
            classLoader);
      if (!present.isEmpty()) {
         return ConditionOutcome.noMatch(
               ConditionMessage.forCondition(ConditionalOnMissingClass.class)
                     .found("unwanted class", "unwanted classes")
                     .items(Style.QUOTE, present));
      }
      matchMessage = matchMessage.andCondition(ConditionalOnMissingClass.class)
            .didNotFind("unwanted class", "unwanted classes").items(Style.QUOTE,
                  getMatches(onMissingClasses, MatchType.MISSING, classLoader));
   }
   // 返回全部匹配成功的ConditionalOutcome
   return ConditionOutcome.match(matchMessage);
}

條件注解激活

這里又要從 Spring 容器的 refresh 方法說起了,這個方法太重要了。其中的 invokeBeanFactoryPostProcessors 方法調(diào)用 BeanFactoryPostProcessor 和 BeanDefinitionRegistryPostProcessor 的處理邏輯,在 bean 實(shí)例化階段開始之前,對注冊到容器的 BeanDefinition 保存的原始數(shù)據(jù)做出修改。與條件注解激活有關(guān)的 posstProcessor 是 ConfigurationClassPostProcessor,其實(shí)現(xiàn)自 BeanFactoryPostProcessor 。

ConfigurationClassPostProcessor 會從 BeanFactory 中找出所有被 @Configuration 直接或間接注解的類(包括 @Component, @ComponentScan, @Import, @ImportResource 修飾的類)進(jìn)行解析。對解析的結(jié)果使用 ConditionEvaluator 進(jìn)行過濾判斷。判斷邏輯:

public boolean shouldSkip(AnnotatedTypeMetadata metadata, ConfigurationPhase phase) {
   // 
   if (metadata == null || !metadata.isAnnotated(Conditional.class.getName())) {
      return false;
   }

   if (phase == null) {
      if (metadata instanceof AnnotationMetadata &&
            ConfigurationClassUtils.isConfigurationCandidate((AnnotationMetadata) metadata)) {
         return shouldSkip(metadata, ConfigurationPhase.PARSE_CONFIGURATION);
      }
      return shouldSkip(metadata, ConfigurationPhase.REGISTER_BEAN);
   }

   List<Condition> conditions = new ArrayList<Condition>();
   for (String[] conditionClasses : getConditionClasses(metadata)) {
      for (String conditionClass : conditionClasses) {
         Condition condition = getCondition(conditionClass, this.context.getClassLoader());
         conditions.add(condition);
      }
   }

   AnnotationAwareOrderComparator.sort(conditions);

   for (Condition condition : conditions) {
      ConfigurationPhase requiredPhase = null;
      if (condition instanceof ConfigurationCondition) {
         requiredPhase = ((ConfigurationCondition) condition).getConfigurationPhase();
      }
      if (requiredPhase == null || requiredPhase == phase) {
         if (!condition.matches(this.context, metadata)) {
            return true;
         }
      }
   }

   return false;
}

其中 ConfigurationCondition 是 Condition 的子接口,內(nèi)部主要定義了枚舉類型表示條件注解生效的階段:

public interface ConfigurationCondition extends Condition {
   ConfigurationPhase getConfigurationPhase();
   enum ConfigurationPhase {
      PARSE_CONFIGURATION,
      REGISTER_BEAN
   }
}

完整的 ConfigurationClassPostProcessor 處理邏輯:

configuration-annotation-process.png

參考:SpringBoot源碼分析之Spring容器的refresh過程

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容