面試官:Spring 創建Bean 時是怎樣判斷條件的?

我們在 Spring/ Spring Boot Starter 或者一些框架的源碼裏經常能看到類似如下的註解聲明,可能作用在類上,也可能在某個方法上:

@ConditionalOnProperty(name = "spring.cloud.refresh.enabled", matchIfMissing = true) 
 
@ConditionalOnProperty(prefix = "management.metrics.export.atlas", name = "enabled", havingValue = "true", 
    matchIfMissing = true) 

我們一眼都能看出來,這是來「談條件」的。需要滿足某個屬性存在,或者屬性值是xx這一類的。

對於屬性的匹配,是會在 Environment 裏查找是否包含當前需要的屬性,如果沒指定 havingValue 的話,那需要同時屬性的值不爲「false」這個字符串,其它的東西都視爲true。

今天的這篇做爲鋪墊,先來描述一下註解的工作原理,後面一篇我會寫寫與此有關的一個有趣的案例。

工作原理

濃縮版

在SpringBoot 啓動過程中,會掃描當前依賴裏的 @Configuration,然後遍歷的過程中會判斷其中哪些是要講條件的。對於講條件的這些,會判斷

shouldSkip ,這裏的是否跳過,會根據註解作用在類上,方法上,轉向不同的Metadata,提取對應的實現類,但本質上還是通過 resolver 去Environment 裏找找這個屬性在不在,不在跳過,在的話是否值匹配。從而決定 Confirutaion 是否生效。

源碼版

我們知道 Spring 啓動的過程,也是創建和初始化Bean 的過程,在這個過程中,會先拿到BeanNames,並一個個的去創建和初始化。

此時,對於Configuration,是通過BeanPostProcessor的方式來處理的.

public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) { 
    int registryId = System.identityHashCode(registry); 
    this.registriesPostProcessed.add(registryId); 
    processConfigBeanDefinitions(registry);// 對,是這裏 
  } 

部分調用棧如下:

java.lang.Thread.State: RUNNABLE 
    at org.springframework.boot.autoconfigure.condition.OnPropertyCondition.getMatchOutcome(OnPropertyCondition.java:65) 
    at org.springframework.boot.autoconfigure.condition.SpringBootCondition.matches(SpringBootCondition.java:47) 
    at org.springframework.context.annotation.ConditionEvaluator.shouldSkip(ConditionEvaluator.java:108) 
    at org.springframework.context.annotation.ConfigurationClassBeanDefinitionReader.loadBeanDefinitionsForBeanMethod(ConfigurationClassBeanDefinitionReader.java:181) 
    at org.springframework.context.annotation.ConfigurationClassBeanDefinitionReader.loadBeanDefinitionsForConfigurationClass(ConfigurationClassBeanDefinitionReader.java:142) 
    at org.springframework.context.annotation.ConfigurationClassBeanDefinitionReader.loadBeanDefinitions(ConfigurationClassBeanDefinitionReader.java:118) 
    at org.springframework.context.annotation.ConfigurationClassPostProcessor.processConfigBeanDefinitions(ConfigurationClassPostProcessor.java:328) 
    at org.springframework.context.annotation.ConfigurationClassPostProcessor.postProcessBeanDefinitionRegistry(ConfigurationClassPostProcessor.java:233) 

這裏對於 Class 和 Method,都在該方法中,處理入口不一樣,傳入的Meta也有所區別

/** 
   * Build and validate a configuration model based on the registry of 
   * {@link Configuration} classes. 
   */ 
  public void processConfigBeanDefinitions(BeanDefinitionRegistry registry) { 
    List<BeanDefinitionHolder> configCandidates = new ArrayList<>(); 
    String[] candidateNames = registry.getBeanDefinitionNames(); 
 
    for (String beanName : candidateNames) { 
      BeanDefinition beanDef = registry.getBeanDefinition(beanName); 
      if (ConfigurationClassUtils.isFullConfigurationClass(beanDef) || 
          ConfigurationClassUtils.isLiteConfigurationClass(beanDef)) { 
      } 
      else if (ConfigurationClassUtils.checkConfigurationClassCandidate(beanDef, this.metadataReaderFactory)) { 
        configCandidates.add(new BeanDefinitionHolder(beanDef, beanName)); 
      } 
    } 
    // Return immediately if no @Configuration classes were found 
    if (configCandidates.isEmpty()) { 
      return; 
    } 
 
    // Parse each @Configuration class 
    ConfigurationClassParser parser = new ConfigurationClassParser( 
        this.metadataReaderFactory, this.problemReporter, this.environment, 
        this.resourceLoader, this.componentScanBeanNameGenerator, registry); 
 
    Set<BeanDefinitionHolder> candidates = new LinkedHashSet<>(configCandidates); 
    Set<ConfigurationClass> alreadyParsed = new HashSet<>(configCandidates.size()); 
    do { 
      parser.parse(candidates); // 這裏處理class 
      parser.validate(); 
 
      Set<ConfigurationClass> configClasses = new LinkedHashSet<>(parser.getConfigurationClasses()); 
      configClasses.removeAll(alreadyParsed); 
 
      // Read the model and create bean definitions based on its content 
      if (this.reader == null) { 
        this.reader = new ConfigurationClassBeanDefinitionReader( 
            registry, this.sourceExtractor, this.resourceLoader, this.environment, 
            this.importBeanNameGenerator, parser.getImportRegistry()); 
      } 
      this.reader.loadBeanDefinitions(configClasses); // 這裏處理Method 
      alreadyParsed.addAll(configClasses); 
    while (!candidates.isEmpty()); 
  } 

裏面的邏輯,則都是在判斷這些Condition 是否match,重點看這一行

condition.matches(this.context, metadata)

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

通過觀察 Condition 這個接口你也能發現,和我們上面說的一樣,這裏不同的處理metadata是不同的。

在 SpringBoot 裏,ConditionalOnProperty 的 Condition 實現,運用了一個模板方法模式, SpringBootCondition 做爲模板,再調用各子類的實現方法。

public final boolean matches(ConditionContext context, 
      AnnotatedTypeMetadata metadata) { 
    String classOrMethodName = getClassOrMethodName(metadata); 
      ConditionOutcome outcome = getMatchOutcome(context, metadata);// 這裏交給了抽象方法 
      recordEvaluation(context, classOrMethodName, outcome); 
      return outcome.isMatch(); 
  } 

來看子類的實現

private ConditionOutcome determineOutcome(AnnotationAttributes annotationAttributes,       PropertyResolver resolver) {     Spec spec = new Spec(annotationAttributes);     List<String> missingProperties = new ArrayList<>();     List<String> nonMatchingProperties = new ArrayList<>();     spec.collectProperties(resolver, missingProperties, nonMatchingProperties);     if (!missingProperties.isEmpty()) {       return ConditionOutcome.noMatch(           ConditionMessage.forCondition(ConditionalOnProperty.class, spec)               .didNotFind("property", "properties")               .items(Style.QUOTE, missingProperties));     }     if (!nonMatchingProperties.isEmpty()) {       return ConditionOutcome.noMatch(           ConditionMessage.forCondition(ConditionalOnProperty.class, spec)               .found("different value in property",                   "different value in properties")               .items(Style.QUOTE, nonMatchingProperties));     }     return ConditionOutcome.match(ConditionMessage         .forCondition(ConditionalOnProperty.class, spec).because("matched"));   } 

有了這個判斷,對於 OnClass 之類的,你也能猜個八九不離十。

同樣會有一個子類的實現

只不過判斷的從屬性,換成了在classloader裏查找已加載的類。

來源:https://www.tuicool.com/articles/E32iMj6

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章