SpringBoot @ConditionalOnBean、@ConditionalOnMissingBean註解原始碼分析與示例

OkidoGreen發表於2020-04-05

https://blog.csdn.net/xcy1193068639/article/details/81517456

前言:
Spring4推出了@Conditional註解,方便程式根據當前環境或者容器情況來動態注入bean,對@Conditional註解不熟悉的朋友可移步至 Spring @Conditional註解 詳細講解及示例 這篇部落格進行學習。

繼@Conditional註解後,又基於此註解推出了很多派生註解,比如@ConditionalOnBean、@ConditionalOnMissingBean、@ConditionalOnExpression、@ConditionalOnClass......動態注入bean變得更方便了。本篇將講解@ConditionalOnBean註解。

配置類中有兩個Computer類的bean,一個是膝上型電腦,一個是備用電腦。如果當前容器中已經有電腦bean了,就不注入備用電腦,如果沒有,則注入備用電腦,這裡需要使用到@ConditionalOnMissingBean。

@Configuration
public class BeanConfig {
 
    @Bean(name = "notebookPC")
    public Computer computer1(){
        return new Computer("膝上型電腦");
    }
 
    @ConditionalOnMissingBean(Computer.class)
    @Bean("reservePC")
    public Computer computer2(){
        return new Computer("備用電腦");
    }
}


這個註解就實現了功能,這個@ConditionalOnMissingBean為我們做了什麼呢?我們來一探究竟.。

一探究竟:
首先,來看@ConditionalOnMissingBean的宣告:

//可以標註在類和方法上
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
//使用了@Conditional註解,條件類是OnBeanCondition
@Conditional({OnBeanCondition.class})
public @interface ConditionalOnMissingBean {
    Class<?>[] value() default {};
 
    String[] type() default {};
 
    Class<?>[] ignored() default {};
 
    String[] ignoredType() default {};
 
    Class<? extends Annotation>[] annotation() default {};
 
    String[] name() default {};
 
    SearchStrategy search() default SearchStrategy.ALL;
}


這時候,我們就看到了我們熟悉的@Conditional註解,OnBeanCondition作為條件類。

OnBeanCondition類的宣告:

//定義帶註釋的元件的排序順序,2147483647即為預設值

@Order(2147483647)
class OnBeanCondition extends SpringBootCondition implements ConfigurationCondition {


它繼承了SpringBootCondition類,OnBeanCondition類中沒有matches方法,而SpringBootCondition類中有實現matches方法。OnBeanCondition還實現了ConfigurationCondition,ConfigurationCondition介面不熟悉的讀者可以到Spring ConfigurationCondition介面詳解 瞭解介面。OnBeanCondition類重寫了getConfigurationPhase()方法,表示在註冊bean的時候註解生效:

public ConfigurationPhase getConfigurationPhase() {
        return ConfigurationPhase.REGISTER_BEAN;
    }


就從matches方法開始:

   

//SpringBootCondition類中的matches方法
    public final boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
        //獲取當前的類名或者方法名(由標註的位置決定)
        String classOrMethodName = getClassOrMethodName(metadata);
 
        try {
            //關鍵程式碼:這裡就會判斷出結果
            ConditionOutcome outcome = this.getMatchOutcome(context, metadata);
            //存入日誌
            this.logOutcome(classOrMethodName, outcome);
            //存入記錄
            this.recordEvaluation(context, classOrMethodName, outcome);
            //最後返回ConditionOutcome的isMatch就是返回boolean型別結果
            return outcome.isMatch();
        } catch (NoClassDefFoundError var5) {
            throw new IllegalStateException("Could not evaluate condition on " + classOrMethodName + " due to " + var5.getMessage() + " not found. Make sure your own configuration does not rely on that class. This can also happen if you are @ComponentScanning a springframework package (e.g. if you put a @ComponentScan in the default package by mistake)", var5);
        } catch (RuntimeException var6) {
            throw new IllegalStateException("Error processing condition on " + this.getName(metadata), var6);
        }
    }


關鍵程式碼在OnBeanCondition的getMatchOutcome方法上:

 

  /**
     * 獲得判斷結果的方法,ConditionOutcome類中存著boolean型別的結果
     */
    public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) {
    //返回一個新的ConditionMessage
        ConditionMessage matchMessage = ConditionMessage.empty();
        OnBeanCondition.BeanSearchSpec spec;
        List matching;
        //這是metadata會呼叫isAnnotated方法判斷當前標註的註解是不是ConditionalOnMissingBean
    //其實@ConditionalOnBean、@ConditionalOnMissingBean和@ConditionalOnSingleCandidate都是使用這個條件類,所以這裡做判斷
        if (metadata.isAnnotated(ConditionalOnBean.class.getName())) {
            spec = new OnBeanCondition.BeanSearchSpec(context, metadata, ConditionalOnBean.class);
            matching = this.getMatchingBeans(context, spec);
            if (matching.isEmpty()) {
                return ConditionOutcome.noMatch(ConditionMessage.forCondition(ConditionalOnBean.class, new Object[]{spec}).didNotFind("any beans").atAll());
            }
 
            matchMessage = matchMessage.andCondition(ConditionalOnBean.class, new Object[]{spec}).found("bean", "beans").items(Style.QUOTE, matching);
        }
 
        if (metadata.isAnnotated(ConditionalOnSingleCandidate.class.getName())) {
            OnBeanCondition.BeanSearchSpec spec = new OnBeanCondition.SingleCandidateBeanSearchSpec(context, metadata, ConditionalOnSingleCandidate.class);
            matching = this.getMatchingBeans(context, spec);
            if (matching.isEmpty()) {
                return ConditionOutcome.noMatch(ConditionMessage.forCondition(ConditionalOnSingleCandidate.class, new Object[]{spec}).didNotFind("any beans").atAll());
            }
 
            if (!this.hasSingleAutowireCandidate(context.getBeanFactory(), matching, spec.getStrategy() == SearchStrategy.ALL)) {
                return ConditionOutcome.noMatch(ConditionMessage.forCondition(ConditionalOnSingleCandidate.class, new Object[]{spec}).didNotFind("a primary bean from beans").items(Style.QUOTE, matching));
            }
 
            matchMessage = matchMessage.andCondition(ConditionalOnSingleCandidate.class, new Object[]{spec}).found("a primary bean from beans").items(Style.QUOTE, matching);
        }
 
        //如果當前注入的bean是@ConditionalOnMissingBean
        if (metadata.isAnnotated(ConditionalOnMissingBean.class.getName())) {
            //返回一個spec(說明),這裡的spec規定了搜尋的內容,比如搜尋策略、需要搜尋的類名......
            spec = new OnBeanCondition.BeanSearchSpec(context, metadata, ConditionalOnMissingBean.class);
            //主要的搜尋實現在這個方法裡,最後返回一個list
            matching = this.getMatchingBeans(context, spec);
            //判斷搜尋出來的結果
            if (!matching.isEmpty()) {
                return ConditionOutcome.noMatch(ConditionMessage.forCondition(ConditionalOnMissingBean.class, new Object[]{spec}).found("bean", "beans").items(Style.QUOTE, matching));
            }
 
            matchMessage = matchMessage.andCondition(ConditionalOnMissingBean.class, new Object[]{spec}).didNotFind("any beans").atAll();
        }
 
        return ConditionOutcome.match(matchMessage);
    }


spec = new OnBeanCondition.BeanSearchSpec(context, metadata, ConditionalOnBean.class);

這句中,相當於從內部類中將標註@ConditionalOnMissingBean註解時的屬性都取出來:

       

 BeanSearchSpec(ConditionContext context, AnnotatedTypeMetadata metadata, Class<?> annotationType) {
            this.annotationType = annotationType;
            MultiValueMap<String, Object> attributes = metadata.getAllAnnotationAttributes(annotationType.getName(), true);
            //將attributes這個map中的資料放到對應的list成員變數中
            this.collect(attributes, "name", this.names);
            this.collect(attributes, "value", this.types);
            this.collect(attributes, "type", this.types);
            this.collect(attributes, "annotation", this.annotations);
            this.collect(attributes, "ignored", this.ignoredTypes);
            this.collect(attributes, "ignoredType", this.ignoredTypes);
            this.strategy = (SearchStrategy)metadata.getAnnotationAttributes(annotationType.getName()).get("search");
            OnBeanCondition.BeanTypeDeductionException deductionException = null;
 
            try {
                if (this.types.isEmpty() && this.names.isEmpty()) {
                    this.addDeducedBeanType(context, metadata, this.types);
                }
            } catch (OnBeanCondition.BeanTypeDeductionException var7) {
                deductionException = var7;
            }
 
            this.validate(deductionException);
        }
 
        //驗證的方法
        protected void validate(OnBeanCondition.BeanTypeDeductionException ex) {
            if (!this.hasAtLeastOne(this.types, this.names, this.annotations)) {
                String message = this.annotationName() + " did not specify a bean using type, name or annotation";
                if (ex == null) {
                    throw new IllegalStateException(message);
                } else {
                    throw new IllegalStateException(message + " and the attempt to deduce the bean's type failed", ex);
                }
            }
        }


看一下OnBeanCondition類中的getMatchingBeans方法,裡面有用到搜尋策略,詳見搜尋策略介紹

   

private List<String> getMatchingBeans(ConditionContext context, OnBeanCondition.BeanSearchSpec beans) {
        //獲得當前bean工廠
        ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
        //判斷當前的搜尋策略是否是PARENTS或者ANCESTORS,預設是ALL
        if (beans.getStrategy() == SearchStrategy.PARENTS || beans.getStrategy() == SearchStrategy.ANCESTORS) {
            BeanFactory parent = beanFactory.getParentBeanFactory();
            Assert.isInstanceOf(ConfigurableListableBeanFactory.class, parent, "Unable to use SearchStrategy.PARENTS");
            //如果是PARENTS或者ANCESTORS,當前bean工廠就用父工廠
            beanFactory = (ConfigurableListableBeanFactory)parent;
        }
 
        if (beanFactory == null) {
            return Collections.emptyList();
        } else {
            List<String> beanNames = new ArrayList();
            //如果當前搜尋策略等於CURRENT,為true
            boolean considerHierarchy = beans.getStrategy() != SearchStrategy.CURRENT;
            //這裡的type就是需要查詢的bean的型別
            //下面,會從屬性中找bean
            Iterator var6 = beans.getTypes().iterator();
 
            String beanName;
            while(var6.hasNext()) {
                beanName = (String)var6.next();
                //如果找到了型別,接下來就是根據型別找bean的例項名,找示例名的方法在下方,實際上就是一個getNamesForType
                beanNames.addAll(this.getBeanNamesForType(beanFactory, beanName, context.getClassLoader(), considerHierarchy));
            }
 
            var6 = beans.getIgnoredTypes().iterator();
 
            while(var6.hasNext()) {
                beanName = (String)var6.next();
                beanNames.removeAll(this.getBeanNamesForType(beanFactory, beanName, context.getClassLoader(), considerHierarchy));
            }
 
            var6 = beans.getAnnotations().iterator();
 
            while(var6.hasNext()) {
                beanName = (String)var6.next();
                beanNames.addAll(Arrays.asList(this.getBeanNamesForAnnotation(beanFactory, beanName, context.getClassLoader(), considerHierarchy)));
            }
 
            var6 = beans.getNames().iterator();
 
            while(var6.hasNext()) {
                beanName = (String)var6.next();
                if (this.containsBean(beanFactory, beanName, considerHierarchy)) {
                    beanNames.add(beanName);
                }
            }
            //將存放bean例項名的list返回
            return beanNames;
        }
    }
 
 
 
    //根據型別獲取bean的name
    private Collection<String> getBeanNamesForType(ListableBeanFactory beanFactory, String type, ClassLoader classLoader, boolean considerHierarchy) throws LinkageError {
        try {
            Set<String> result = new LinkedHashSet();
            this.collectBeanNamesForType(result, beanFactory, ClassUtils.forName(type, classLoader), considerHierarchy);
            return result;
        } catch (ClassNotFoundException var6) {
            return Collections.emptySet();
        } catch (NoClassDefFoundError var7) {
            return Collections.emptySet();
        }
    }
 
    private void collectBeanNamesForType(Set<String> result, ListableBeanFactory beanFactory, Class<?> type, boolean considerHierarchy) {
        result.addAll(BeanTypeRegistry.get(beanFactory).getNamesForType(type));
        if (considerHierarchy && beanFactory instanceof HierarchicalBeanFactory) {
            BeanFactory parent = ((HierarchicalBeanFactory)beanFactory).getParentBeanFactory();
            if (parent instanceof ListableBeanFactory) {
                this.collectBeanNamesForType(result, (ListableBeanFactory)parent, type, considerHierarchy);
            }
        }
 
    }


找完bean了之後,回到剛才的程式碼裡:

       

 //如果當前注入的bean是@ConditionalOnMissingBean
        if (metadata.isAnnotated(ConditionalOnMissingBean.class.getName())) {
            //返回一個spec(說明),這裡的spec規定了搜尋的內容,比如搜尋策略、需要搜尋的類名......
            spec = new OnBeanCondition.BeanSearchSpec(context, metadata, ConditionalOnMissingBean.class);
            matching = this.getMatchingBeans(context, spec);
            if (!matching.isEmpty()) {
                return ConditionOutcome.noMatch(ConditionMessage.forCondition(ConditionalOnMissingBean.class, new Object[]{spec}).found("bean", "beans").items(Style.QUOTE, matching));
            }
 
            matchMessage = matchMessage.andCondition(ConditionalOnMissingBean.class, new Object[]{spec}).didNotFind("any beans").atAll();
        }


如果第5行返回的list不是空的,就會返回ConditionOutcome物件noMatch方法,表示不匹配。ConditionOutcome類用於存放過濾結果,只有兩個變數:

/**
 * 過濾結果類
 */
public class ConditionOutcome {
    /**
     * 匹配結果 true or false
     */
    private final boolean match;
    /**
     * 匹配結果資訊
     */
    private final ConditionMessage message;


兩者區別:
@ConditionOnBean在判斷list的時候,如果list沒有值,返回false,否則返回true

@ConditionOnMissingBean在判斷list的時候,如果list沒有值,返回true,否則返回false,其他邏輯都一樣

例子:
@ConditionalOnBean(javax.sql.DataSource.class)    
Spring容器或者所有父容器中需要存在至少一個javax.sql.DataSource類的例項

相關文章