Spring Boot源碼 - @ConditionalOnBean實現分析

目錄

1、OnBeanCondition的繼承結構

2、Condition的matches方法

1)、創建Spec對象

2)、獲取匹配的Bean

3)、判斷,組裝結果返回

3、AutoConfigurationImportFilter的match方法


1、OnBeanCondition的繼承結構

    在上一篇@Conditional之後,知道了動態判斷註冊bean的是怎麼實現的。執行的時機,回調時傳入了什麼對象,只是在Spring BootCondition下面會有很多更復雜的情況,所以就使用比較常見的@ConditionalOnBean爲例,分析其結構和回調的邏輯。

@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Conditional(OnBeanCondition.class)
public @interface ConditionalOnBean {
    
    // 指定需要存在的Bean的Class
    Class<?>[] value() default {};

    // 指定需要存在的Bean的名稱數組
    String[] type() default {};

    Class<? extends Annotation>[] annotation() default {};

    String[] name() default {};

    SearchStrategy search() default SearchStrategy.ALL;

    Class<?>[] parameterizedContainer() default {};
}

    當前的Condition類型爲OnBeanCondition,先看看其繼承關係:

1)、實現了ConfigurationCondition接口,則需要實現getConfigurationPhase,返回REGISTER_BEAN

2)、OnBeanConditional(與OnWebApplicationCondition、OnClassCondition)共同繼承自類FilteringSpringBootCondition

2-1)、FilteringSpringBootCondition其實現了BeanFactoryAware,BeanClassLoaderAware,但是其本身不是Bean,生命週期不會進行回調,而是通過自動裝配時回調了invokeAwareMethods。

2-2)、FilteringSpringBootCondition繼承自SpringBootCondition,並且SpringBootCondition繼承Condition接口,實現了matches方法。

2-3)、FilteringSpringBootCondition實現了AutoConfigurationImportFilter接口,只有一個未實現的方法match。

 

    稍微有點亂,總結起來就是OnBeanConditional(OnWebApplicationCondition、OnClassCondition)作爲Spring Boot的實現類,完成了兩條線的任務(後面也根據這兩條線進行分析,按照Spring Boot的啓動過程,會先調用第二部,再調用第一步)。

    第一:判斷@ComponentScan、ImportSelector等注入的Bean時,會調用Condition的match方法。但是會先調用的SpringBootCondition的matches方法,matches又會調用到自定義的getMatchOutcome方法,最終由OnBeanConditional實現。

    第二:作爲Spring Boot的實現(並非Spring),需要配合完成自動裝配。不僅僅是spring.factories中的自動裝配EnableAutoConfiguration配置什麼就註冊什麼。而是需要使用OnBeanConditional(OnWebApplicationCondition、OnClassCondition),配合spring-autoconfigure-metadata.properties中的條件過濾。

    

2、Conditionmatches方法

SpringBootCondition實現了matches方法:

@Override
public final boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
    String classOrMethodName = getClassOrMethodName(metadata);
    try {
        ConditionOutcome outcome = getMatchOutcome(context, metadata);
        logOutcome(classOrMethodName, outcome);
        recordEvaluation(context, classOrMethodName, outcome);
        return outcome.isMatch();
    } // 省略異常代碼
}

// 定義抽象方法,讓子類實現(相當於鉤子)
public abstract ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata);

1)、根據當前的註解類型,獲取處理的類或者方法名(比較簡單就不看了)

2)、然後會調用定義好的abstract方法,讓子類實現,相當於一個鉤子。

3)、再打印日誌

4)、處理autoConfigurationReport類型的Bean注入。

    這些都是Spring Boot關心的步驟,或者模板方法。但是最重要的是子類的不同實現。根據Class是否存在,根據是否爲Web項目判斷,根據Bean是否存在判斷。當然下面還是值分析根據Bean是否存在進行判斷:

@Override
public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) {
    ConditionMessage matchMessage = ConditionMessage.empty();
    MergedAnnotations annotations = metadata.getAnnotations();
    if (annotations.isPresent(ConditionalOnBean.class)) {
        Spec<ConditionalOnBean> spec = new Spec<>(context, metadata, annotations, ConditionalOnBean.class);
        MatchResult matchResult = getMatchingBeans(context, spec);
        if (!matchResult.isAllMatched()) {
            String reason = createOnBeanNoMatchReason(matchResult);
            return ConditionOutcome.noMatch(spec.message().because(reason));
        }
        matchMessage = spec.message(matchMessage).found("bean", "beans").items(Style.QUOTE,
                matchResult.getNamesOfAllMatches());
    }
    if (metadata.isAnnotated(ConditionalOnSingleCandidate.class.getName())) {
        Spec<ConditionalOnSingleCandidate> spec = new SingleCandidateSpec(context, metadata, annotations);
        MatchResult matchResult = getMatchingBeans(context, spec);
        if (!matchResult.isAllMatched()) {
            return ConditionOutcome.noMatch(spec.message().didNotFind("any beans").atAll());
        }
        else if (!hasSingleAutowireCandidate(context.getBeanFactory(), matchResult.getNamesOfAllMatches(),
                spec.getStrategy() == SearchStrategy.ALL)) {
            return ConditionOutcome.noMatch(spec.message().didNotFind("a primary bean from beans")
                    .items(Style.QUOTE, matchResult.getNamesOfAllMatches()));
        }
        matchMessage = spec.message(matchMessage).found("a primary bean from beans").items(Style.QUOTE,
                matchResult.getNamesOfAllMatches());
    }
    if (metadata.isAnnotated(ConditionalOnMissingBean.class.getName())) {
        Spec<ConditionalOnMissingBean> spec = new Spec<>(context, metadata, annotations,
                ConditionalOnMissingBean.class);
        MatchResult matchResult = getMatchingBeans(context, spec);
        if (matchResult.isAnyMatched()) {
            String reason = createOnMissingBeanNoMatchReason(matchResult);
            return ConditionOutcome.noMatch(spec.message().because(reason));
        }
        matchMessage = spec.message(matchMessage).didNotFind("any beans").atAll();
    }
    return ConditionOutcome.match(matchMessage);
}

    由於@ConditionalOnBean、@ConditionalOnSingleCandidate、@ConditionalOnMissingBean三個註解都會回調OnBeanCondition類進行處理。在上一篇博客@Condition的回調時機我們知道,會根據每一個註解類進行回調。

1)、創建Spec<A extends Annotation>對象

private static class Spec<A extends Annotation> {

    private final ClassLoader classLoader;

    private final Class<?> annotationType;

    private final Set<String> names;

    private final Set<String> types;

    private final Set<String> annotations;

    private final Set<String> ignoredTypes;

    private final Set<Class<?>> parameterizedContainers;

    private final SearchStrategy strategy;
}

    將@ConditionalOnBean等註解上的屬性值,從傳入的MergedAnnotations上根據key、value獲取出來,並封裝成當前的Spec對象,爲後續進行準備。

2)、獲取匹配的Bean

    先獲取與傳入類型匹配的Bean,由於存在繼承關係,可能獲取到多個值。

protected final MatchResult getMatchingBeans(ConditionContext context, Spec<?> spec) {
    ClassLoader classLoader = context.getClassLoader();
    ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
    boolean considerHierarchy = spec.getStrategy() != SearchStrategy.CURRENT;
    Set<Class<?>> parameterizedContainers = spec.getParameterizedContainers();
    if (spec.getStrategy() == SearchStrategy.ANCESTORS) {
        BeanFactory parent = beanFactory.getParentBeanFactory();
        Assert.isInstanceOf(ConfigurableListableBeanFactory.class, parent,
                "Unable to use SearchStrategy.ANCESTORS");
        beanFactory = (ConfigurableListableBeanFactory) parent;
    }
    MatchResult result = new MatchResult();
    Set<String> beansIgnoredByType = getNamesOfBeansIgnoredByType(classLoader, beanFactory, considerHierarchy,
            spec.getIgnoredTypes(), parameterizedContainers);
    for (String type : spec.getTypes()) {
        Collection<String> typeMatches = getBeanNamesForType(classLoader, considerHierarchy, beanFactory, type,
                parameterizedContainers);
        typeMatches.removeAll(beansIgnoredByType);
        if (typeMatches.isEmpty()) {
            result.recordUnmatchedType(type);
        }
        else {
            result.recordMatchedType(type, typeMatches);
        }
    }
    for (String annotation : spec.getAnnotations()) {
        Set<String> annotationMatches = getBeanNamesForAnnotation(classLoader, beanFactory, annotation,
                considerHierarchy);
        annotationMatches.removeAll(beansIgnoredByType);
        if (annotationMatches.isEmpty()) {
            result.recordUnmatchedAnnotation(annotation);
        }
        else {
            result.recordMatchedAnnotation(annotation, annotationMatches);
        }
    }
    for (String beanName : spec.getNames()) {
        if (!beansIgnoredByType.contains(beanName) && containsBean(beanFactory, beanName, considerHierarchy)) {
            result.recordMatchedName(beanName);
        }
        else {
            result.recordUnmatchedName(beanName);
        }
    }
    return result;
}

    雖然方法比較長,但是根據在註解上可配置名稱,Class,Annotation等方式進行判斷。主要還是依賴於BeanFactory的getBeanNamesForType、getBeanNamesForAnnotation、containsBean返回的Bean封裝成MatchResult對象。

private static final class MatchResult {

    private final Map<String, Collection<String>> matchedAnnotations = new HashMap<>();

    private final List<String> matchedNames = new ArrayList<>();

    private final Map<String, Collection<String>> matchedTypes = new HashMap<>();

    private final List<String> unmatchedAnnotations = new ArrayList<>();

    private final List<String> unmatchedNames = new ArrayList<>();

    private final List<String> unmatchedTypes = new ArrayList<>();

    private final Set<String> namesOfAllMatches = new HashSet<>();
}

3)、判斷,組裝結果返回

ConditionalOnBean或者ConditionalOnSingleCandidate:只要不是一個都不匹配,則返回true。!isAllMatch

boolean isAllMatched() {
    return this.unmatchedAnnotations.isEmpty() && this.unmatchedNames.isEmpty()
					&& this.unmatchedTypes.isEmpty();
}

ConditionalOnMissBean:只有一個都不存在的情況下才返回true。 !isAnyMatched

boolean isAnyMatched() {
    return (!this.matchedAnnotations.isEmpty()) || (!this.matchedNames.isEmpty())
					|| (!this.matchedTypes.isEmpty());
}

 

3、AutoConfigurationImportFiltermatch方法

    AutoConfigurationImportFilter的match方法由父類FilteringSpringBootCondition實現。

@Override
public boolean[] match(String[] autoConfigurationClasses, AutoConfigurationMetadata autoConfigurationMetadata) {
    ConditionEvaluationReport report = ConditionEvaluationReport.find(this.beanFactory);
    ConditionOutcome[] outcomes = getOutcomes(autoConfigurationClasses, autoConfigurationMetadata);
    boolean[] match = new boolean[outcomes.length];
    for (int i = 0; i < outcomes.length; i++) {
        match[i] = (outcomes[i] == null || outcomes[i].isMatch());
        if (!match[i] && outcomes[i] != null) {
            logOutcome(autoConfigurationClasses[i], outcomes[i]);
            if (report != null) {
                report.recordConditionEvaluation(autoConfigurationClasses[i], this, outcomes[i]);
            }
        }
    }
    return match;
}

    由於父類需要處理ConditionalOnBean,ConditionalOnWeb,ConditionalOnWebApplication、ConditionalOnNotWebApplication等類型,所以具體的判斷邏輯需要子類自己實現,父類只處理相同的模板方法部分。同樣是定義了抽象方法,然子類實現:

protected abstract ConditionOutcome[] getOutcomes(String[] autoConfigurationClasses,
			AutoConfigurationMetadata autoConfigurationMetadata);

    具體的邏輯比較複雜,但是就是進行filter方法,多層的輪訓過濾。

比如:CacheAutoConfiguration爲spring.factories中配置的自動裝配類,如下:

@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(CacheManager.class)
@ConditionalOnBean(CacheAspectSupport.class)
@ConditionalOnMissingBean(value = CacheManager.class, name = "cacheResolver")
@EnableConfigurationProperties(CacheProperties.class)
@AutoConfigureAfter({ CouchbaseAutoConfiguration.class, HazelcastAutoConfiguration.class,
        HibernateJpaAutoConfiguration.class, RedisAutoConfiguration.class })
@Import({ CacheConfigurationImportSelector.class, CacheManagerEntityManagerFactoryDependsOnPostProcessor.class })
public class CacheAutoConfiguration {

    @Bean
    @ConditionalOnMissingBean
    public CacheManagerCustomizers cacheManagerCustomizers(ObjectProvider<CacheManagerCustomizer<?>> customizers) {
        return new CacheManagerCustomizers(customizers.orderedStream().collect(Collectors.toList()));
    }

    @Bean
    public CacheManagerValidator cacheAutoConfigurationValidator(CacheProperties cacheProperties,
                                                                 ObjectProvider<CacheManager> cacheManager) {
        return new CacheManagerValidator(cacheProperties, cacheManager);
    }

    @ConditionalOnClass(LocalContainerEntityManagerFactoryBean.class)
    @ConditionalOnBean(AbstractEntityManagerFactoryBean.class)
    static class CacheManagerEntityManagerFactoryDependsOnPostProcessor
            extends EntityManagerFactoryDependsOnPostProcessor {

        CacheManagerEntityManagerFactoryDependsOnPostProcessor() {
            super("cacheManager");
        }

    }
}

    但是,在spring-autoconfigure-metadata.properties中配置了對應的OnBeanCondition等。

org.springframework.boot.autoconfigure.cache.CacheAutoConfiguration.ConditionalOnBean=\
org.springframework.cache.interceptor.CacheAspectSupport

org.springframework.boot.autoconfigure.cache.CacheAutoConfiguration.ConditionalOnClass=\
org.springframework.cache.CacheManager

org.springframework.boot.autoconfigure.cache.CacheAutoConfiguration.AutoConfigureAfter=\
org.springframework.boot.autoconfigure.couchbase.CouchbaseAutoConfiguration,\
org.springframework.boot.autoconfigure.hazelcast.HazelcastAutoConfiguration,\
org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration,\
org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration

    我自己認爲CacheAutoConfiguration其內部也是有條件的,但是這樣的話如果不滿足條件,就不用加載其內部那麼多註解,以提交效率。

 

 

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