助力SpringBoot自動配置的條件註解ConditionalOnXXX分析--SpringBoo

注:該源碼分析對應SpringBoot版本爲2.1.0.RELEASEhtml

1 前言

本篇接
如何分析SpringBoot源碼模塊及結構?--SpringBoot源碼(二)java

上一篇分析了SpringBoot源碼結構及各個模塊pom之間的關係後,那麼此篇開始就開始解開SpringBoot新特性之一--自動配置的神祕面紗了。由於SpringBoot自動配置原理是基於其大量的條件註解ConditionalOnXXX,所以,本節咱們先來擼下Spring的條件註解的相關源碼。linux

2 SpringBoot的派生條件註解

咱們都知道,SpringBoot自動配置是須要知足相應的條件纔會自動配置,所以SpringBoot的自動配置大量應用了條件註解ConditionalOnXXX。以下圖:web

助力SpringBoot自動配置的條件註解ConditionalOnXXX分析--SpringBoo

那麼上圖的條件註解如何使用呢?spring

舉個栗子,咱們來看下如何使用@ConditionalOnClass@ConditionalOnProperty這兩個註解,先看下圖代碼:
助力SpringBoot自動配置的條件註解ConditionalOnXXX分析--SpringBoo
HelloWorldEnableAutoConfiguration這個自動配置類應用了@ConditionalOnClassConditionalOnProperty兩個條件註解,那麼只有在知足:classpath中存在HelloWorldComponent.class和配置了hello.world.namehello.world.age屬性這兩個條件的狀況下才會建立HelloWorldComponent這個beanspringboot

其實SpringBoot的@ConditionalOnXXX等條件註解都是派生註解,那麼什麼是派生註解呢?
就拿上面的栗子來講,以@ConditionalOnClass(HelloWorldComponent.class)爲例,咱們打開ConditionalOnClass註解源碼,以下:session

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

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

    String[] name() default {};

}

能夠看到@ConditionalOnClass註解上面又標註了@Conditional(OnClassCondition.class)註解,所以@ConditionalOnClass@Conditional的派生註解,@Conditional(OnClassCondition.class)@ConditionalOnClass註解是等價的,即這兩個註解標註在某個配置類上的效果是等價的。app

而SpringBoot的自動配置原理正是創建在這些大量的派生條件註解@ConditionalOnXXX之上,而這些條件註解的原理跟Spring的Condition接口有關。所以咱們先來研究下Condition接口的相關源碼。ide

3 Condition接口

3.1 Condition接口源碼分析

分析Condition接口源碼前先看下如何自定義ConditionalOnXXX註解,舉個栗子,好比自定義一個@ConditionalOnLinux註解,該註解只有在其屬性environment是"linux"纔會建立相關的bean。定義瞭如下代碼:源碼分析

/**
 * 實現spring 的Condition接口,而且重寫matches()方法,若是@ConditionalOnLinux的註解屬性environment是linux就返回true
 *
 */
public class LinuxCondition implements Condition {

    @Override
    public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
        // 得到註解@ConditionalOnLinux的全部屬性
        List<AnnotationAttributes> allAnnotationAttributes = annotationAttributesFromMultiValueMap(
                metadata.getAllAnnotationAttributes(
                        ConditionalOnLinux.class.getName()));
        for (AnnotationAttributes annotationAttributes : allAnnotationAttributes) {
            // 得到註解@ConditionalOnLinux的environment屬性
            String environment = annotationAttributes.getString("environment");
            // 若environment等於linux,則返回true
            if ("linux".equals(environment)) {
                return true;
            }
        }
        return false;
    }
}
@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Conditional(LinuxCondition.class)
public @interface ConditionalOnLinux {
    // 標註是哪一個環境
    String environment() default "";

}
@Configuration
public class ConditionConfig {
        // 只有`@ConditionalOnLinux`的註解屬性`environment`是"linux"時纔會建立bean
    @Bean
    @ConditionalOnLinux(environment = "linux")
    public Environment linuxEnvironment() {
        return new LinuxEnvironment();
    }
}

上面的代碼咱們捋一下:

  1. LinuxCondition實現了Condition接口並實現了matches方法,而matches方法則判斷@ConditionalOnLinux的註解屬性environment是否"linux",是則返回true,不然false。
  2. 而後咱們再定義了一個註解@ConditionalOnLinux,這個註解是@Conditional的派生註解,與@Conditional(LinuxCondition.class)等價,注意@ConditionalOnLinux註解定義了一個屬性environment。而咱們最終能夠利用LinuxConditionmatches方法中的參數AnnotatedTypeMetadata來獲取@ConditionalOnLinux的註解屬性environment的值,從而用來判斷值是否爲linux"。
  3. 最後咱們又定義了一個配置類ConditionConfig,在linuxEnvironment方法上標註了@ConditionalOnLinux(environment = "linux")。所以,這裏只有 LinuxConditionmatches方法返回true纔會建立bean

學會了如何自定義@ConditionalOnXXX註解後,咱們如今再來看下Condition接口的源碼:

@FunctionalInterface
public interface Condition {
    boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata);
}

Condition接口主要有一個matches方法,該方法決定了是否要註冊相應的bean對象。其中matches方法中有兩個參數,參數類型分別是ConditionContextAnnotatedTypeMetadata,這兩個參數很是重要。它們分別用來獲取一些環境信息和註解元數據從而用在matches方法中判斷是否符合條件。

ConditionContext,顧名思義,主要是跟Condition的上下文有關,主要用來獲取Registry,BeanFactory,Environment,ResourceLoaderClassLoader等。那麼獲取這些用來幹什麼呢?舉個栗子,好比OnResourceCondition須要靠ConditionContext來獲取ResourceLoader來加載指定資源,OnClassCondition須要靠ConditionContext來獲取ClassLoader來加載指定類等,下面看下其源碼:

public interface ConditionContext {
BeanDefinitionRegistry getRegistry();
@Nullable
ConfigurableListableBeanFactory getBeanFactory();
Environment getEnvironment();
ResourceLoader getResourceLoader();
@Nullable
ClassLoader getClassLoader();
}

AnnotatedTypeMetadata,這個跟註解元數據有關,利用AnnotatedTypeMetadata能夠拿到某個註解的一些元數據,而這些元數據就包含了某個註解裏面的屬性,好比前面的栗子,利用AnnotatedTypeMetadata能夠拿到@ConditionalOnLinux的註解屬性environment的值。下面看下其源碼:

public interface AnnotatedTypeMetadata {
boolean isAnnotated(String annotationName);
Map&lt;String, Object&gt; getAnnotationAttributes(String annotationName);
Map&lt;String, Object&gt; getAnnotationAttributes(String annotationName, boolean classValuesAsString);
MultiValueMap&lt;String, Object&gt; getAllAnnotationAttributes(String annotationName);
MultiValueMap&lt;String, Object&gt; getAllAnnotationAttributes(String annotationName, boolean classValuesAsString);
}

回到剛纔的栗子,咱們知道@ConditionalOnLinux註解真正起做用的是Condition接口的具體實現類LinuxConditionmatches方法,那麼這個matches方法是在什麼時候被調用的呢?

經過idea調試看調用的棧幀,以下圖:

助力SpringBoot自動配置的條件註解ConditionalOnXXX分析--SpringBoo
發現是在ConditionEvaluatorshouldSkip方法中調用了LinuxConditionmatches方法,天然咱們再去看看ConditionEvaluatorshouldSkip的方法執行了什麼邏輯。

// 這個方法主要是若是是解析階段則跳過,若是是註冊階段則不跳過
public boolean shouldSkip(@Nullable AnnotatedTypeMetadata metadata, @Nullable ConfigurationPhase phase) {
    // 若沒有被@Conditional或其派生註解所標註,則不會跳過
    if (metadata == null || !metadata.isAnnotated(Conditional.class.getName())) {
        return false;
    }
    // 沒有指定phase,注意phase能夠分爲PARSE_CONFIGURATION或REGISTER_BEAN類型
    if (phase == null) {
        // 若標有@Component,@Import,@Bean或@Configuration等註解的話,則說明是PARSE_CONFIGURATION類型
        if (metadata instanceof AnnotationMetadata &&
                ConfigurationClassUtils.isConfigurationCandidate((AnnotationMetadata) metadata)) {
            return shouldSkip(metadata, ConfigurationPhase.PARSE_CONFIGURATION);
        }
        // 不然是REGISTER_BEAN類型
        return shouldSkip(metadata, ConfigurationPhase.REGISTER_BEAN);
    }

    List<Condition> conditions = new ArrayList<>();
    // TODO 得到全部標有@Conditional註解或其派生註解裏面的Condition接口實現類並實例化成對象。
    // 好比@Conditional(OnBeanCondition.class)則得到OnBeanCondition.class,OnBeanCondition.class每每實現了Condition接口
    for (String[] conditionClasses : getConditionClasses(metadata)) {
        // 將類實例化成對象
        for (String conditionClass : conditionClasses) {
            Condition condition = getCondition(conditionClass, this.context.getClassLoader());
            conditions.add(condition);
        }
    }
    // 排序,即按照Condition的優先級進行排序
    AnnotationAwareOrderComparator.sort(conditions);

    for (Condition condition : conditions) {
        ConfigurationPhase requiredPhase = null;
        if (condition instanceof ConfigurationCondition) {
            // 從condition中得到對bean是解析仍是註冊
            requiredPhase = ((ConfigurationCondition) condition).getConfigurationPhase();
        }
        // 若requiredPhase爲null或獲取的階段類型正是當前階段類型且不符合condition的matches條件,則跳過
        if ((requiredPhase == null || requiredPhase == phase) && !condition.matches(this.context, metadata)) {
            return true;
        }
    }

    return false;
}

shouldSkip這個方法執行的邏輯主要是若是是解析階段則跳過,若是是註冊階段則不跳過;若是是在註冊階段即REGISTER_BEAN階段的話,此時會獲得全部的Condition接口的具體實現類並實例化這些實現類,而後再執行下面關鍵的代碼進行判斷是否須要跳過。

if ((requiredPhase == null || requiredPhase == phase) && !condition.matches(this.context, metadata)) {
    return true;
}

上面代碼最重要的邏輯是調用了Condition接口的具體實現類的matches方法,若matches返回false,則跳過,不進行註冊bean的操做;若matches返回true,則不跳過,進行註冊bean的操做;

好了,Condition的源碼分析就到此爲止,再往上翻調用方法的話應該就是Spring加載bean定義的相關源碼了,不屬於這裏的分析範圍。

3.2 Spring的內置Condition接口實現類

前面咱們學會了如何自定義條件註解及Condition的源碼分析,那麼咱們不由好奇,Spring究竟內置了哪些Condition接口的實現類呢?

那麼看下Spring的Condition接口的具體實現類的類圖:

助力SpringBoot自動配置的條件註解ConditionalOnXXX分析--SpringBoo
發現Spring內置的Condition接口的具體實現類雖然有多個,但只有ProfileCondition不是測試相關的,所以能夠說真正的內置的Condition接口的具體實現類只有ProfileCondition一個,很是很是少,這跟SpringBoot的大量派生條件註解造成了鮮明的對比。ProfileCondition你們都知道,是跟環境有關,好比咱們平時通常有dev,testprod環境,而ProfileCondition就是判斷咱們項目配置了哪一個環境的。下面是ProfileCondition的源碼,很簡單,這裏就不分析了。

class ProfileCondition implements Condition {
    @Override
    public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
        MultiValueMap<String, Object> attrs = metadata.getAllAnnotationAttributes(Profile.class.getName());
        if (attrs != null) {
            for (Object value : attrs.get("value")) {
                if (context.getEnvironment().acceptsProfiles(Profiles.of((String[]) value))) {
                    return true;
                }
            }
            return false;
        }
        return true;
    }
}

4 SpringBootCondition源碼解析

前面看到Spring對Condition的內置註解能夠說只有ProfileCondition一個,可是咱們都知道,SpringBoot則內置了大量的條件註解ConditionalOnXXX。在分析前,咱們先來看一下SpringBootCondition的總體類圖來個總體的理解,以下圖:

助力SpringBoot自動配置的條件註解ConditionalOnXXX分析--SpringBoo
能夠看到SpringBootCondition做爲SpringBoot條件註解的基類,處於整個類圖的中心,它實現了Condition接口,而後又有不少具體的子類OnXXXCondition,這些OnXXXCondition其實就是@ConditionalOnXXX的條件類。

咱們先來看下SpringBootCondition這個父類是主要作了哪些事情,抽象了哪些共有的邏輯?

SpringBootConditon實現了Condition接口,做爲SpringBoot衆多條件註解OnXXXCondtion的父類,它的做用主要就是打印一些條件註解評估報告的日誌,好比打印哪些配置類是符合條件註解的,哪些是不符合的。打印的日誌形式以下圖:

助力SpringBoot自動配置的條件註解ConditionalOnXXX分析--SpringBoo

助力SpringBoot自動配置的條件註解ConditionalOnXXX分析--SpringBoo
由於SpringBootConditon實現了Condition接口,也實現了matches方法,所以該方法一樣也是被ConditionEvaluatorshouldSkip方法中調用,所以咱們就以SpringBootConditonmatches方法爲入口去進行分析。直接上代碼:

// SpringBootCondition.java

public final boolean matches(ConditionContext context,
            AnnotatedTypeMetadata metadata) {
        // 獲得metadata的類名或方法名
        String classOrMethodName = getClassOrMethodName(metadata);
        try {
            // 判斷每一個配置類的每一個條件註解@ConditionalOnXXX是否知足條件,而後記錄到ConditionOutcome結果中
            // 注意getMatchOutcome是一個抽象模板方法,交給OnXXXCondition子類去實現
            ConditionOutcome outcome = getMatchOutcome(context, metadata);
            // 打印condition評估的日誌,哪些條件註解@ConditionalOnXXX是知足條件的,哪些是不知足條件的,這些日誌都打印出來
            logOutcome(classOrMethodName, outcome);
            // 除了打印日誌外,這些是否匹配的信息還要記錄到ConditionEvaluationReport中
            recordEvaluation(context, classOrMethodName, outcome);
            // 最後返回@ConditionalOnXXX是否知足條件
            return outcome.isMatch();
        }
        catch (NoClassDefFoundError ex) {
            throw new IllegalStateException(
                    "Could not evaluate condition on " + classOrMethodName + " due to "
                            + ex.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)",
                    ex);
        }
        catch (RuntimeException ex) {
            throw new IllegalStateException(
                    "Error processing condition on " + getName(metadata), ex);
        }
    }

上面代碼的註釋已經很是詳細,咱們知道了SpringBootCondition抽象了全部其具體實現類OnXXXCondition的共有邏輯--condition評估信息打印,最重要的是封裝了一個模板方法getMatchOutcome(context, metadata),留給各個OnXXXCondition具體子類去覆蓋實現屬於本身的判斷邏輯,而後再返回相應的匹配結果給SpringBootCondition用於日誌打印。

所以咱們知道了SpringBootCondition其實就是用來打印condition評估信息的,對於其餘枝節方法咱們沒必要追究過深,省得丟了主線。咱們如今的重點是放在交給OnXXXCondition子類實現的模板方法上getMatchOutcome(context, metadata);,由於這個方法將會由不少OnXXXCondition覆蓋重寫判斷邏輯,這裏是咱們接下來分析的重點。

由於SpringBootCondition有衆多具體實現類,下面只挑OnResourceConditionOnBeanConditionOnWebApplicationCondition進行講解,而AutoConfigurationImportFilter跟自動配置有關,則留到自動配置源碼解析的時候再進行分析。

4.1 OnResourceCondition源碼分析

如今先來看下一個邏輯及其簡單的註解條件類OnResourceConditionOnResourceCondition繼承了SpringBootCondition父類,覆蓋了其getMatchOutcome方法,用於@ConditionalOnResource註解指定的資源存在與否。OnResourceCondition的判斷邏輯很是簡單,主要拿到@ConditionalOnResource註解指定的資源路徑後,而後用ResourceLoader根據指定路徑去加載看資源存不存在。下面直接看代碼:

先來看下@ConditionalOnResource的代碼,

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

    /**
     * The resources that must be present.
     * @return the resource paths that must be present.
     */
    String[] resources() default {};

}

再來看OnResourceCondition的代碼:

@Order(Ordered.HIGHEST_PRECEDENCE + 20)
class OnResourceCondition extends SpringBootCondition {

    private final ResourceLoader defaultResourceLoader = new DefaultResourceLoader();

    @Override
    public ConditionOutcome getMatchOutcome(ConditionContext context,
            AnnotatedTypeMetadata metadata) {
        // 得到@ConditionalOnResource註解的屬性元數據
        MultiValueMap<String, Object> attributes = metadata
                .getAllAnnotationAttributes(ConditionalOnResource.class.getName(), true);
        // 得到資源加載器,若ConditionContext中有ResourceLoader則用ConditionContext中的,沒有則用默認的
        ResourceLoader loader = (context.getResourceLoader() != null)
                ? context.getResourceLoader() : this.defaultResourceLoader;
        List<String> locations = new ArrayList<>();
        // 將@ConditionalOnResource中定義的resources屬性值取出來裝進locations集合
        collectValues(locations, attributes.get("resources"));
        Assert.isTrue(!locations.isEmpty(),
                "@ConditionalOnResource annotations must specify at "
                        + "least one resource location");
        // missing集合是裝不存在指定資源的資源路徑的
        List<String> missing = new ArrayList<>();
        // 遍歷全部的資源路徑,若指定的路徑的資源不存在則將其資源路徑存進missing集合中
        for (String location : locations) {
            // 這裏針對有些資源路徑是Placeholders的狀況,即處理${}
            String resource = context.getEnvironment().resolvePlaceholders(location);
            if (!loader.getResource(resource).exists()) {
                missing.add(location);
            }
        }
        // 若是存在某個資源不存在,那麼則報錯
        if (!missing.isEmpty()) {
            return ConditionOutcome.noMatch(ConditionMessage
                    .forCondition(ConditionalOnResource.class)
                    .didNotFind("resource", "resources").items(Style.QUOTE, missing));
        }
        // 全部資源都存在,那麼則返回能找到就提的資源
        return ConditionOutcome
                .match(ConditionMessage.forCondition(ConditionalOnResource.class)
                        .found("location", "locations").items(locations));
    }

    // 將@ConditionalOnResource中定義的resources屬性值取出來裝進locations集合
    private void collectValues(List<String> names, List<Object> values) {
        for (Object value : values) {
            for (Object item : (Object[]) value) {
                names.add((String) item);
            }
        }
    }
}

能夠看到OnResourceConditiongetMatchOutcome方法很是簡單,這裏再也不詳述。

4.2 OnBeanCondition源碼分析

OnBeanCondition一樣繼承了FilteringSpringBootCondition父類,覆蓋了父類FilteringSpringBootConditiongetOutcomes方法。而FilteringSpringBootCondition又是SpringBootCondition的子類,FilteringSpringBootCondition跟自動配置類過濾有關,這裏先不分析。值得注意的是OnBeanCondition一樣重寫了SpringBootConditiongetMatchOutcome方法,用來判斷Spring容器中是否存在指定條件的bean。同時是OnBeanCondition@ConditionalOnBean,@ConditionalOnSingleCandidateConditionalOnMissingBean的條件類。

一樣,先來看OnBeanCondition複寫父類SpringBootConditiongetMatchOutcome方法的代碼:

@Override
    public ConditionOutcome getMatchOutcome(ConditionContext context,
            AnnotatedTypeMetadata metadata) {
        ConditionMessage matchMessage = ConditionMessage.empty();
        // (1),配置類(metadata)標註@ConditionalOnBean註解的狀況
        if (metadata.isAnnotated(ConditionalOnBean.class.getName())) {
            // 將@ConditionalOnBean註解屬性封裝進BeanSearchSpec對象中
            // 注意BeanSearchSpec是一個靜態內部類,用來存儲@ConditionalOnBean和@ConditionalOnMissingBean註解的屬性值
            BeanSearchSpec spec = new BeanSearchSpec(context, metadata,
                    ConditionalOnBean.class);
            // 調用getMatchingBeans獲得符合條件的bean
            MatchResult matchResult = getMatchingBeans(context, spec);
            // 若是不匹配
            if (!matchResult.isAllMatched()) {
                String reason = createOnBeanNoMatchReason(matchResult);
                return ConditionOutcome.noMatch(ConditionMessage
                        .forCondition(ConditionalOnBean.class, spec).because(reason));
            }
            // 若是匹配
            matchMessage = matchMessage.andCondition(ConditionalOnBean.class, spec)
                    .found("bean", "beans")
                    .items(Style.QUOTE, matchResult.getNamesOfAllMatches());
        }
        // (2),配置類(metadata)標註@ConditionalOnSingleCandidate註解的狀況
        if (metadata.isAnnotated(ConditionalOnSingleCandidate.class.getName())) {
            BeanSearchSpec spec = new SingleCandidateBeanSearchSpec(context, metadata,
                    ConditionalOnSingleCandidate.class);
            MatchResult matchResult = getMatchingBeans(context, spec);
            if (!matchResult.isAllMatched()) {
                return ConditionOutcome.noMatch(ConditionMessage
                        .forCondition(ConditionalOnSingleCandidate.class, spec)
                        .didNotFind("any beans").atAll());
            }
            else if (!hasSingleAutowireCandidate(context.getBeanFactory(),
                    matchResult.getNamesOfAllMatches(),
                    spec.getStrategy() == SearchStrategy.ALL)) {
                return ConditionOutcome.noMatch(ConditionMessage
                        .forCondition(ConditionalOnSingleCandidate.class, spec)
                        .didNotFind("a primary bean from beans")
                        .items(Style.QUOTE, matchResult.getNamesOfAllMatches()));
            }
            matchMessage = matchMessage
                    .andCondition(ConditionalOnSingleCandidate.class, spec)
                    .found("a primary bean from beans")
                    .items(Style.QUOTE, matchResult.getNamesOfAllMatches());
        }
        // (3),配置類(metadata)標註@ConditionalOnMissingBean註解的狀況
        if (metadata.isAnnotated(ConditionalOnMissingBean.class.getName())) {
            BeanSearchSpec spec = new BeanSearchSpec(context, metadata,
                    ConditionalOnMissingBean.class);
            MatchResult matchResult = getMatchingBeans(context, spec);
            if (matchResult.isAnyMatched()) {
                String reason = createOnMissingBeanNoMatchReason(matchResult);
                return ConditionOutcome.noMatch(ConditionMessage
                        .forCondition(ConditionalOnMissingBean.class, spec)
                        .because(reason));
            }
            matchMessage = matchMessage.andCondition(ConditionalOnMissingBean.class, spec)
                    .didNotFind("any beans").atAll();
        }
        // 最終返回matchMessage
        return ConditionOutcome.match(matchMessage);
    }

咱們能夠看到OnBeanCondition類覆蓋的getMatchOutcome方法分別處理了標註@ConditionalOnBean,@ConditionalOnSingleCandidate@ConditionalOnMissingBean註解的狀況,分別對應上面代碼註釋的(1),(2)(3)處。

如今咱們只看針對@ConditionalOnBean註解的處理邏輯,從上面代碼中能夠看到若配置類(metadata)標註@ConditionalOnBean註解的話,主要作了如下事情:

  1. 將該註解屬性提取出來封裝進BeanSearchSpec對象中;
  2. 而後調用getMatchingBeans(context, spec)方法來獲取是否有匹配的bean
  3. 最後返回bean的匹配狀況;

能夠看到最重要的邏輯是第2步,那麼咱們再來看下getMatchingBeans方法,直接上代碼:

protected final MatchResult getMatchingBeans(ConditionContext context,
            BeanSearchSpec beans) {
        // 得到Spring容器的beanFactory
        ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
        // 判斷bean的搜索策略是不是SearchStrategy.ANCESTORS策略
        if (beans.getStrategy() == SearchStrategy.ANCESTORS) {
            BeanFactory parent = beanFactory.getParentBeanFactory();
            Assert.isInstanceOf(ConfigurableListableBeanFactory.class, parent,
                    "Unable to use SearchStrategy.PARENTS");
            beanFactory = (ConfigurableListableBeanFactory) parent;
        }
        // MatchResult用來存儲bean的匹配結果
        MatchResult matchResult = new MatchResult();
        // 若是bean的搜索策略不是SearchStrategy.CURRENT的話,則置considerHierarchy爲true
        boolean considerHierarchy = beans.getStrategy() != SearchStrategy.CURRENT;
        // 獲取TypeExtractor,TypeExtractor是用來判斷bean的類型的
        TypeExtractor typeExtractor = beans.getTypeExtractor(context.getClassLoader());
        // 獲取是否有被忽略bean類型,如有的話將該bean類型的名稱裝進beansIgnoredByType集合
        // 這裏主要是針對@ConditionalOnMissingBean的ignored屬性
        List<String> beansIgnoredByType = getNamesOfBeansIgnoredByType(
                beans.getIgnoredTypes(), typeExtractor, beanFactory, context,
                considerHierarchy);
        // 遍歷bean的全部類型
        for (String type : beans.getTypes()) {
            // 調用getBeanNamesForType方法根據bean類型獲得全部符合條件的bean類型,並放到typeMatches集合
            Collection<String> typeMatches = getBeanNamesForType(beanFactory, type,
                    typeExtractor, context.getClassLoader(), considerHierarchy);
            // 移除掉Ignored的類型
            typeMatches.removeAll(beansIgnoredByType);
            // 若typeMatches爲空,那麼則說明正在遍歷的這個type類型不符合匹配條件,此時用matchResult記錄一下這個不符合條件的類型
            if (typeMatches.isEmpty()) {
                matchResult.recordUnmatchedType(type);
            }
            // 若typeMatches不爲空,那麼則說明正在遍歷的這個type類型符合匹配條件,此時用matchResult記錄一下這個符合條件的類型
            else {
                matchResult.recordMatchedType(type, typeMatches);
            }
        }
        // 這裏針對@ConditionalOnBean等註解的annotation屬性的處理
        for (String annotation : beans.getAnnotations()) {
            List<String> annotationMatches = Arrays
                    .asList(getBeanNamesForAnnotation(beanFactory, annotation,
                            context.getClassLoader(), considerHierarchy));
            annotationMatches.removeAll(beansIgnoredByType);
            if (annotationMatches.isEmpty()) {
                matchResult.recordUnmatchedAnnotation(annotation);
            }
            else {
                matchResult.recordMatchedAnnotation(annotation, annotationMatches);
            }
        }
        // 這裏針對@ConditionalOnBean等註解的name屬性的處理
        for (String beanName : beans.getNames()) {
            // beansIgnoredByType集合不包含beanName且beanFactory包含這個bean,則匹配
            if (!beansIgnoredByType.contains(beanName)
                    && containsBean(beanFactory, beanName, considerHierarchy)) {
                matchResult.recordMatchedName(beanName);
            }
            // 不然,不匹配
            else {
                matchResult.recordUnmatchedName(beanName);
            }
        }
        // 最後返回匹配結果
        return matchResult;
    }

上面的邏輯主要是從spring容器中搜索有無指定條件的bean,搜索Spring容器搜索bean的話有三種搜索策略,分別是CURRENT,ANCESTORSALL,分表表示只從當前的context中搜索bean,只從父context中搜索bean和從整個context中搜索bean;定義了搜索策略後,而後再根據BeanSearchSpec對象封裝的註解屬性分別取指定的容器中查找有無符合條件的bean,而後再進行一些過濾。好比@ConditionalOnMissingBean註解有定義ignored屬性值,那麼從容器中搜索到有符合條件的bean時,此時還要移除掉ignored指定的bean

好了,上面就已經分析了OnBeanCondition這個條件類了,咱們堅持主線優先的原則,具體的細節代碼不會深究。

4.3 OnWebApplicationCondition

OnWebApplicationCondition一樣繼承了FilteringSpringBootCondition父類,覆蓋了父類FilteringSpringBootConditiongetOutcomes方法。而FilteringSpringBootCondition又是SpringBootCondition的子類,FilteringSpringBootCondition跟自動配置類過濾有關,這裏先不分析。值得注意的是OnWebApplicationCondition一樣重寫了SpringBootConditiongetMatchOutcome方法,用來判斷當前應用是否web應用。同時是OnWebApplicationCondition@ConditionalOnWebApplication的條件類。

一樣,先來看OnWebApplicationCondition重寫SpringBootConditiongetMatchOutcome方法:

public ConditionOutcome getMatchOutcome(ConditionContext context,
            AnnotatedTypeMetadata metadata) {
        // 配置類是否標註有@ConditionalOnWebApplication註解
        boolean required = metadata
                .isAnnotated(ConditionalOnWebApplication.class.getName());
        // 調用isWebApplication方法返回匹配結果
        ConditionOutcome outcome = isWebApplication(context, metadata, required);
        // 如有標註@ConditionalOnWebApplication但不符合條件,則返回不匹配
        if (required && !outcome.isMatch()) {
            return ConditionOutcome.noMatch(outcome.getConditionMessage());
        }
        // 若沒有標註@ConditionalOnWebApplication但符合條件,則返回不匹配
        if (!required && outcome.isMatch()) {
            return ConditionOutcome.noMatch(outcome.getConditionMessage());
        }
        // 這裏返回匹配的狀況,TODO 不過有個疑問:若是沒有標註@ConditionalOnWebApplication註解,又不符合條件的話,也會執行到這裏,返回匹配?
        return ConditionOutcome.match(outcome.getConditionMessage());
    }

上面代碼的邏輯很簡單,主要是調用isWebApplication方法來判斷當前應用是不是web應用。所以,咱們再來看下isWebApplication方法:

private ConditionOutcome isWebApplication(ConditionContext context,
            AnnotatedTypeMetadata metadata, boolean required) {
        // 調用deduceType方法判斷是哪一種類型,其中有SERVLET,REACTIVE和ANY類型,其中ANY表示了SERVLET或REACTIVE類型
        switch (deduceType(metadata)) {
        // SERVLET類型
        case SERVLET:
            return isServletWebApplication(context);
        // REACTIVE類型
        case REACTIVE:
            return isReactiveWebApplication(context);
        default:
            return isAnyWebApplication(context, required);
        }
    }

isWebApplication方法中,首先從@ConditionalOnWebApplication註解中獲取其定義了什麼類型,而後根據不一樣的類型進入不一樣的判斷邏輯。這裏咱們只看下SERVLET的狀況判斷處理,看代碼:

private ConditionOutcome isServletWebApplication(ConditionContext context) {
        ConditionMessage.Builder message = ConditionMessage.forCondition("");
        // 若classpath中不存在org.springframework.web.context.support.GenericWebApplicationContext.class,則返回不匹配
        if (!ClassNameFilter.isPresent(SERVLET_WEB_APPLICATION_CLASS,
                context.getClassLoader())) {
            return ConditionOutcome.noMatch(
                    message.didNotFind("servlet web application classes").atAll());
        }
        // 若classpath中存在org.springframework.web.context.support.GenericWebApplicationContext.class,那麼又分爲如下幾種匹配的狀況
        // session
        if (context.getBeanFactory() != null) {
            String[] scopes = context.getBeanFactory().getRegisteredScopeNames();
            if (ObjectUtils.containsElement(scopes, "session")) {
                return ConditionOutcome.match(message.foundExactly("'session' scope"));
            }
        }
        // ConfigurableWebEnvironment
        if (context.getEnvironment() instanceof ConfigurableWebEnvironment) {
            return ConditionOutcome
                    .match(message.foundExactly("ConfigurableWebEnvironment"));
        }
        // WebApplicationContext
        if (context.getResourceLoader() instanceof WebApplicationContext) {
            return ConditionOutcome.match(message.foundExactly("WebApplicationContext"));
        }
        // 若以上三種都不匹配的話,則說明不是一個servlet web application
        return ConditionOutcome.noMatch(message.because("not a servlet web application"));
    }

對因而SERVLET的狀況,首先根據classpath中是否存在org.springframework.web.context.support.GenericWebApplicationContext.class,若是不存在該類,則直接返回不匹配;若存在的話那麼又分爲如下幾種匹配的狀況:

  • session
  • ConfigurableWebEnvironment
  • WebApplicationContext

若上面三種狀況都不匹配,則說明不是一個servlet web application。

4.4 其餘

因爲springboot的OnXXXCondition類實現太多,不可能每一個條件類都分析一遍,所以上面只分析了OnResourceCondition,OnBeanConditiononWebApplicationCondition的源碼。咱們分析源碼不可能把全部代碼都通讀一遍的,閱讀源碼的話,只要理解了某個模塊的類之間的關係及挑幾個有表明性的類分析下就行,不可能一網打盡。

如有時間的話,推薦看下幾個咱們經常使用的條件類的源碼:OnPropertyCondition,OnClassConditionOnExpressionCondition等。

5 如何擴展SpringBootCondition

前文咱們知道了如何擴展Spring的Condition接口,那麼咱們該如何擴展SpringBoot的SpringBootCondition類呢?

推薦閱讀springboot之使用SpringBootCondition得到答案

好了,本篇文章是SpringBoot自動配置源碼分析的前置文章,這裏分析了條件註解源碼,那麼下篇文章咱們就來看看SpringBoot自動配置的源碼了。

下節預告:

<font color=Blue>SpringBoot新特性:SpringBoot是如何自動配置的?--SpringBoot源碼(四)</font>

原創不易,幫忙點個讚唄!

參考:

1,spring 自動配置(上) 配置文件和插件解讀

2,SpringBoot內置條件註解

3,spring boot 系列之六:深刻理解spring boot的自動配置


助力SpringBoot自動配置的條件註解ConditionalOnXXX分析--SpringBoo

相關文章
相關標籤/搜索