spring-boot-2.0.3源碼篇 - @Configuration、Condition與@Conditional

前言

  開心一刻html

   一名劫匪慌忙中竄上了一輛車的後座,上車後發現主駕和副駕的一男一女疑惑地回頭看着他,他當即拔出槍威脅到:「趕快開車,甩掉後面的警車,不然老子一槍崩了你!」,因而副駕上的男人轉過臉對那女的說:「大姐,別慌,聽我口令把剛纔的動做再練習一遍,掛一檔,輕鬆離合,輕踩油門,走...走,哎 走...哎,哎,對,走走... 最後,三人都躺到了醫院,劫匪的手上還戴上了銬子...java

劫匪的心裏

  路漫漫其修遠兮,吾將上下而求索!git

前情回顧

  估摸着你們已經忘記了createApplicationContext的內容,本文不作過多的回顧,只是提醒你們:在AnnotationConfigServletWebServerApplicationContext的實例化過程當中,實例化了AnnotatedBeanDefinitionReader,另外也將ConfigurationClassPostProcessor定義註冊到了beanFactory中,以下圖所示github

  看着AnnotatedBeanDefinitionReader、ConfigurationClassPostProcessor是否是隱約感受到了什麼? ConfigurationClassPostProcessor實現了BeanDefinitionRegistryPostProcessor,BeanDefinitionRegistryPostProcessor又實現了BeanFactoryPostProcessor,關於BeanFactoryPostProcessor,你們能夠看看這篇文章:Spring拓展接口之BeanFactoryPostProcessor,佔位符與敏感信息解密原理web

概念介紹與應用

  @Configuration

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Configuration {

    /**
     * Explicitly specify the name of the Spring bean definition associated
     * with this Configuration class. If left unspecified (the common case),
     * a bean name will be automatically generated.
     * <p>The custom name applies only if the Configuration class is picked up via
     * component scanning or supplied directly to a {@link AnnotationConfigApplicationContext}.
     * If the Configuration class is registered as a traditional XML bean definition,
     * the name/id of the bean element will take precedence.
     * @return the suggested component name, if any (or empty String otherwise)
     * @see org.springframework.beans.factory.support.DefaultBeanNameGenerator
     */
    @AliasFor(annotation = Component.class)
    String value() default "";

}
View Code

    @Configuration可以修飾Class、interface和enum,用的最多的仍是標註在類上,至關於把該類做爲spring的xml配置文件中的<beans>,用於配置spring容器;@Configuration每每會結合@Bean來使用,@Bean等價於spring的xml配置文件中的<bean>,用於註冊bean對象。@Configuration和@Bean組成了基於java類的配置,是spring的推薦配置方式。最簡單的使用以下spring

@Configuration
public class MyConfiguration {

    @Bean
    public Cat mycat() {
        return new Cat();
    }
}

    如上代碼就會在spring容器中註冊一個名叫mycat的Cat類型的Bean數組

  Condition

@FunctionalInterface
public interface Condition {

    /**
     * Determine if the condition matches.
     * @param context the condition context
     * @param metadata metadata of the {@link org.springframework.core.type.AnnotationMetadata class}
     * or {@link org.springframework.core.type.MethodMetadata method} being checked
     * @return {@code true} if the condition matches and the component can be registered,
     * or {@code false} to veto the annotated component's registration
     */
    boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata);

}
View Code

    Spring的條件化配置,當咱們向spring註冊bean時,能夠對這個bean添加必定的自定義條件,當知足這個條件時註冊這個bean,不然不註冊。springboot中部分實現子類以下springboot

    springboot更多實現請查看org.springframework.boot.autoconfigure.condition包。Condition通常配合@Conditional使用,更多信息往下看app

  @Conditional

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Conditional {

    /**
     * All {@link Condition}s that must {@linkplain Condition#matches match}
     * in order for the component to be registered.
     */
    Class<? extends Condition>[] value();

}
View Code

    Spring的條件註解,其value是一個Class<? extends Condition>[],只有數組中的所有Condition所有匹配成功時,被@Conditional修飾的組件纔會被註冊到Spring容器中。@Conditional只是一個標誌,標示須要進行條件判斷,而具體的判斷規則則由具體的Condition來實現。ide

    在SpringBoot源碼中很容易看到被@Conditional註解的組合註解,例如:@ConditionalOnBean、@ConditionalOnMissingBean、@ConditionalOnClass、@ConditionalOnMissingClass等,具體以下

    springboot還提供了AutoConfigureAfter、AutoConfigureBefore、AutoConfigureOrder,看名字基本知道其做用,具體細節須要你們本身去跟了。

  完整應用案例

    接口都能訪問通,數據返回也都正確,很是完美

    完整工程代碼:spring-boot-condition

    當咱們把MyConfiguration中的myCat方法註釋掉(ConditionWeb中的cat相關也註釋掉),再啓動應用的時候,應用報錯啓動不起來,提示以下信息:

Description:

Field dog in com.lee.condition.web.ConditionWeb required a bean of type 'com.lee.condition.model.Dog' that could not be found.
    - Bean method 'myDog' in 'MyConfiguration' not loaded because @ConditionalOnBean (types: com.lee.condition.model.Cat; SearchStrategy: all) did not find any beans of type com.lee.condition.model.Cat


Action:

Consider revisiting the conditions above or defining a bean of type 'com.lee.condition.model.Dog' in your configuration.
View Code

    ConditionWeb中須要Dog類型的bean,而Dog實例化又依賴Cat實例,而咱們沒有實例化Cat,因此應用啓動報錯,提示如上信息 

源碼探究

  咱們要探究什麼了?不探究太細,就探究@Configuration修飾的配置類是什麼時候解析的,@Conditional是什麼時候生效、如何生效的

  @Configuration修飾的配置類是什麼時候解析的

    ConfigurationClassPostProcessor是一個BeanFactoryPostProcessor(能夠查看ConfigurationClassPostProcessor的類繼承結構圖),那麼咱們從AbstractApplicationContext的refresh方法調用的invokeBeanFactoryPostProcessors(beanFactory)方法開始

    來到了processConfigurationClass方法,其詳細代碼以下

protected void processConfigurationClass(ConfigurationClass configClass) throws IOException {
    // ConfigurationClass是否應該被skip
    if (this.conditionEvaluator.shouldSkip(configClass.getMetadata(), ConfigurationPhase.PARSE_CONFIGURATION)) {
        return;
    }

    ConfigurationClass existingClass = this.configurationClasses.get(configClass);
    if (existingClass != null) {
        if (configClass.isImported()) {
            if (existingClass.isImported()) {
                existingClass.mergeImportedBy(configClass);
            }
            // Otherwise ignore new imported config class; existing non-imported class overrides it.
            return;
        }
        else {
            // Explicit bean definition found, probably replacing an import.
            // Let's remove the old one and go with the new one.
            this.configurationClasses.remove(configClass);
            this.knownSuperclasses.values().removeIf(configClass::equals);
        }
    }

    // Recursively process the configuration class and its superclass hierarchy. 遞歸處理configuration class和它的父級類
    // 也就說會遞歸處理咱們的應用入口類:ConditionApplication.class,以及ConditionApplication.class的父級類
    SourceClass sourceClass = asSourceClass(configClass);
    do {
        sourceClass = doProcessConfigurationClass(configClass, sourceClass);
    }
    while (sourceClass != null);

    // 將知足條件的ConfigurationClass都放入configurationClasses集合中
    // 後續會加載configurationClasses集合中全部的ConfigurationClass中配置的bean定義
    this.configurationClasses.put(configClass, configClass);
}
View Code

    其中shouldSkip方法以下

/**
 * Determine if an item should be skipped based on {@code @Conditional} annotations.
 * @param metadata the meta data
 * @param phase the phase of the call
 * @return if the item should be skipped
 */
public boolean shouldSkip(@Nullable AnnotatedTypeMetadata metadata, @Nullable ConfigurationPhase phase) {
    // 若是這個類沒有註解修飾,或者沒有被@Conditional註解(包括Conditional系列)所修飾,不會skip
    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);
    }

    // 要解析的配置類的條件集合,即@Conditional的value
    List<Condition> conditions = new ArrayList<>();
    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();
        }
        // 條件註解的生效階段知足,一旦有條件匹配不成功,則返回true,skip此類
        if ((requiredPhase == null || requiredPhase == phase) && !condition.matches(this.context, metadata)) {
            return true;
        }
    }

    return false;
}
View Code

    咱們再回過頭去看processConfigBeanDefinitions方法

/**
 * Build and validate a configuration model based on the registry of
 * {@link Configuration} classes.
 *  驗證@Configuration修飾的類,知足條件則構建成configuration model
 */
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)) {
            if (logger.isDebugEnabled()) {
                logger.debug("Bean definition has already been processed as a configuration class: " + 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;
    }

    // Sort by previously determined @Order value, if applicable
    configCandidates.sort((bd1, bd2) -> {
        int i1 = ConfigurationClassUtils.getOrder(bd1.getBeanDefinition());
        int i2 = ConfigurationClassUtils.getOrder(bd2.getBeanDefinition());
        return Integer.compare(i1, i2);
    });

    // Detect any custom bean name generation strategy supplied through the enclosing application context
    // 檢測自定義的bean生成策略
    SingletonBeanRegistry sbr = null;
    if (registry instanceof SingletonBeanRegistry) {
        sbr = (SingletonBeanRegistry) registry;
        if (!this.localBeanNameGeneratorSet) {
            BeanNameGenerator generator = (BeanNameGenerator) sbr.getSingleton(CONFIGURATION_BEAN_NAME_GENERATOR);
            if (generator != null) {
                this.componentScanBeanNameGenerator = generator;
                this.importBeanNameGenerator = generator;
            }
        }
    }

    if (this.environment == null) {
        this.environment = new StandardEnvironment();
    }

    // Parse each @Configuration class
    // 解析每個被@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);    // 解析過程當中會將知足條件的@Configuration class存放到configurationClasses中
        parser.validate();

        // 知足條件的@Configuration class 都存放在了parser的configurationClasses中
        Set<ConfigurationClass> configClasses = new LinkedHashSet<>(parser.getConfigurationClasses());
        configClasses.removeAll(alreadyParsed);

        // Read the model and create bean definitions based on its content
        // 讀取@Configuration class中的配置(各個@Bean),並建立對應的bean definition(後續建立bean實例會用到bean定義)
        if (this.reader == null) {
            this.reader = new ConfigurationClassBeanDefinitionReader(
                    registry, this.sourceExtractor, this.resourceLoader, this.environment,
                    this.importBeanNameGenerator, parser.getImportRegistry());
        }
        this.reader.loadBeanDefinitions(configClasses);    // 加載所有@Configuration class中的配置
        alreadyParsed.addAll(configClasses);

        candidates.clear();
        if (registry.getBeanDefinitionCount() > candidateNames.length) {
            String[] newCandidateNames = registry.getBeanDefinitionNames();
            Set<String> oldCandidateNames = new HashSet<>(Arrays.asList(candidateNames));
            Set<String> alreadyParsedClasses = new HashSet<>();
            for (ConfigurationClass configurationClass : alreadyParsed) {
                alreadyParsedClasses.add(configurationClass.getMetadata().getClassName());
            }
            for (String candidateName : newCandidateNames) {
                if (!oldCandidateNames.contains(candidateName)) {
                    BeanDefinition bd = registry.getBeanDefinition(candidateName);
                    if (ConfigurationClassUtils.checkConfigurationClassCandidate(bd, this.metadataReaderFactory) &&
                            !alreadyParsedClasses.contains(bd.getBeanClassName())) {
                        candidates.add(new BeanDefinitionHolder(bd, candidateName));
                    }
                }
            }
            candidateNames = newCandidateNames;
        }
    }
    while (!candidates.isEmpty());

    // Register the ImportRegistry as a bean in order to support ImportAware @Configuration classes
    if (sbr != null && !sbr.containsSingleton(IMPORT_REGISTRY_BEAN_NAME)) {
        sbr.registerSingleton(IMPORT_REGISTRY_BEAN_NAME, parser.getImportRegistry());
    }

    if (this.metadataReaderFactory instanceof CachingMetadataReaderFactory) {
        // Clear cache in externally provided MetadataReaderFactory; this is a no-op
        // for a shared cache since it'll be cleared by the ApplicationContext.
        ((CachingMetadataReaderFactory) this.metadataReaderFactory).clearCache();
    }
}
View Code

  @Conditional是什麼時候生效、如何生效的

    這個問題再上面已經所有獲得體現,Spring不會無腦的加載全部的@Configuration class,只會加載知足條件的@Configuration class,而@Conditional就是條件標誌,至於條件匹配規則這由Condition提供;shouldSkip方法中用到Conditional和Condition,完成條件的匹配處理。

總結

  一、@Configuration和@Bean組成了基於java類的配置,與xml中的<Beans>、<Bean>功能一致,Spring推薦java類的配置;

  二、Condition與@Conditional實現了條件配置,只有知足了條件的@Configuration class和@Bean纔會被註冊到Spring容器;

  三、Spring以咱們的應用啓動類爲基礎來遞歸掃描配置類,包括咱們應用中的配置類、Spring本身的以及第三方的配置類(springboot集成的各類配置類(spring-boot-autoconfigure-xxx.RELEASE.jar下的spring.factories文件中的Auto Configure),還有pageHelper的自動配置,等等);前提是須要開啓自動配置(@EnableAutoConfiguration)。

參考

  SpringBoot源碼分析之條件註解的底層實現

  Spring 工具類 ConfigurationClassParser 分析得獲得配置類

相關文章
相關標籤/搜索