1. 程式人生 > >springboot條件註解原始碼分析

springboot條件註解原始碼分析

上篇我們講到springboot註解之:@EnableAutoConfiguration,主要講到springboot如何自動載入配置,核心類是ConfigurationClassPostProcessor。在spring容器啟動的時候去掃描有@Component的類,然後對掃描到的類進行判斷是否有條件註解,有的話判斷是否通過能進行類的進一步解析和註冊。

這裡就提到了條件註解,來看看具體使用到的地方:

ConfigurationClassPostProcessor.postProcessBeanDefinitionRegistry-->
ConfigurationClassPostProcessor.processConfigBeanDefinitions-->
processConfigBeanDefinitions.parse-->
processConfigBeanDefinitions.processConfigurationClass


protected
void processConfigurationClass(ConfigurationClass configClass) throws IOException { if (this.conditionEvaluator.shouldSkip(configClass.getMetadata(), ConfigurationPhase.PARSE_CONFIGURATION)) { return; }

this.conditionEvaluator.shouldSkip(configClass.getMetadata(), ConfigurationPhase.PARSE_CONFIGURATION)這裡進行條件註解的判斷。

ConditionEvaluator類就是用來進行條件註解處理的類

public boolean shouldSkip(AnnotatedTypeMetadata metadata, ConfigurationPhase phase) {
        //如果註解為空或者註解中沒有使用Conditional則跳過
        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); } List<Condition> conditions = new ArrayList<Condition>(); //取出條件註解所使用的類 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(); } if (requiredPhase == null || requiredPhase == phase) { //使用註解使用的類進行匹配判斷,如果匹配成功則返回true跳過 if (!condition.matches(this.context, metadata)) { return true; } } } return false; }

if (metadata == null || !metadata.isAnnotated(Conditional.class.getName()))
這句話的意思就是判斷是有註解,並且註解是Conditional,才進行後面的處理,我們來看看幾個常用的註解

條件註解 對應的Condition處理類 處理邏輯
@ConditionalOnBean OnBeanCondition Spring容器中是否存在對應的例項。可以通過例項的型別、類名、註解、暱稱去容器中查詢(可以配置從當前容器中查詢或者父容器中查詢或者兩者一起查詢)這些屬性都是陣列,通過”與”的關係進行查詢
@ConditionalOnClass OnClassCondition 類載入器中是否存在對應的類。可以通過Class指定(value屬性)或者Class的全名指定(name屬性)。如果是多個類或者多個類名的話,關係是”與”關係,也就是說這些類或者類名都必須同時在類載入器中存在
@ConditionalOnExpression OnExpressionCondition 判斷SpEL 表示式是否成立
@ConditionalOnJava OnJavaCondition 指定Java版本是否符合要求。內部有2個屬性value和range。value表示一個列舉的Java版本,range表示比這個老或者新於等於指定的Java版本(預設是新於等於)。內部會基於某些jdk版本特有的類去類載入器中查詢,比如如果是jdk9,類載入器中需要存在java.security.cert.URICertStoreParameters;如果是jdk8,類載入器中需要存在java.util.function.Function;如果是jdk7,類載入器中需要存在java.nio.file.Files;如果是jdk6,類載入器中需要存在java.util.ServiceLoader
@ConditionalOnMissingBean OnBeanCondition Spring容器中是否缺少對應的例項。可以通過例項的型別、類名、註解、暱稱去容器中查詢(可以配置從當前容器中查詢或者父容器中查詢或者兩者一起查詢)這些屬性都是陣列,通過”與”的關係進行查詢。還多了2個屬性ignored(類名)和ignoredType(類名),匹配的過程中會忽略這些bean
@ConditionalOnMissingClass OnClassCondition 跟ConditionalOnClass的處理邏輯一樣,只是條件相反,在類載入器中不存在對應的類
@ConditionalOnNotWebApplication OnWebApplicationCondition 應用程式是否是非Web程式,沒有提供屬性,只是一個標識。會從判斷Web程式特有的類是否存在,環境是否是Servlet環境,容器是否是Web容器等
@ConditionalOnProperty OnPropertyCondition 應用環境中的屬性是否存在。提供prefix、name、havingValue以及matchIfMissing屬性。prefix表示屬性名的字首,name是屬性名,havingValue是具體的屬性值,matchIfMissing是個boolean值,如果屬性不存在,這個matchIfMissing為true的話,會繼續驗證下去,否則屬性不存在的話直接就相當於匹配不成功
@ConditionalOnResource OnResourceCondition 是否存在指定的資原始檔。只有一個屬性resources,是個String陣列。會從類載入器中去查詢對應的資原始檔是否存在
@ConditionalOnSingleCandidate OnBeanCondition Spring容器中是否存在且只存在一個對應的例項。只有3個屬性value、type、search。跟ConditionalOnBean中的這3種屬性值意義一樣
@ConditionalOnWebApplication OnWebApplicationCondition 應用程式是否是Web程式,沒有提供屬性,只是一個標識。會從判斷Web程式特有的類是否存在,環境是否是Servlet環境,容器是否是Web容器等

所有的條件註解都是使用了@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();
}

所有的對應的處理類都是實現了Condition介面。

針對介面程式設計,關閉開放原則,spring設計的非常巧妙

到這裡如果要你實現一個條件註解,你該怎麼做?

1.自己寫一個註解使用@Condition註解。
2.實現一個對應的處理類實現Condition介面。

針對介面程式設計的好處體現出來了,spring設計的非常精妙處之一它的關閉開放原則,擴充套件起來非常容易

繼續看shouldSkip方法,在使用condition.matches,我們的springboot中條件註解使用的類繼承了SpringBootCondition,該類實現了Condition介面,來看看SpringBootCondition

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();
        }
        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);
        }
    }

ConditionOutcome outcome = getMatchOutcome(context, metadata);
其中getMatchOutcome是一個抽象類,將它的實現放在子類,這樣可以讓子類擁有自己的實現。

這裡用到了模板模式,getMatchOutcome是一個模板方法,具體的實現延遲到子類,然後matches使用這個模板方法。

以ConditionalOnBean為例來進行分析
如果spring容器中有該類的時候就會進行註冊,看它的處理類OnBeanCondition

@Override
    public ConditionOutcome getMatchOutcome(ConditionContext context,
            AnnotatedTypeMetadata metadata) {
        ConditionMessage matchMessage = ConditionMessage.empty();
        //如果註解為ConditionalOnBean處理邏輯
        if (metadata.isAnnotated(ConditionalOnBean.class.getName())) {
            BeanSearchSpec spec = new BeanSearchSpec(context, metadata,
                    ConditionalOnBean.class);
            List<String> matching = getMatchingBeans(context, spec);
            if (matching.isEmpty()) {
                return ConditionOutcome.noMatch(
                        ConditionMessage.forCondition(ConditionalOnBean.class, spec)
                                .didNotFind("any beans").atAll());
            }
            matchMessage = matchMessage.andCondition(ConditionalOnBean.class, spec)
                    .found("bean", "beans").items(Style.QUOTE, matching);
        }
        if (metadata.isAnnotated(ConditionalOnSingleCandidate.class.getName())) {
            BeanSearchSpec spec = new SingleCandidateBeanSearchSpec(context, metadata,
                    ConditionalOnSingleCandidate.class);
            List<String> matching = getMatchingBeans(context, spec);
            if (matching.isEmpty()) {
                return ConditionOutcome.noMatch(ConditionMessage
                        .forCondition(ConditionalOnSingleCandidate.class, spec)
                        .didNotFind("any beans").atAll());
            }
            else if (!hasSingleAutowireCandidate(context.getBeanFactory(), matching,
                    spec.getStrategy() == SearchStrategy.ALL)) {
                return ConditionOutcome.noMatch(ConditionMessage
                        .forCondition(ConditionalOnSingleCandidate.class, spec)
                        .didNotFind("a primary bean from beans")
                        .items(Style.QUOTE, matching));
            }
            matchMessage = matchMessage
                    .andCondition(ConditionalOnSingleCandidate.class, spec)
                    .found("a primary bean from beans").items(Style.QUOTE, matching);
        }
        if (metadata.isAnnotated(ConditionalOnMissingBean.class.getName())) {
            BeanSearchSpec spec = new BeanSearchSpec(context, metadata,
                    ConditionalOnMissingBean.class);
            List<String> matching = getMatchingBeans(context, spec);
            if (!matching.isEmpty()) {
                return ConditionOutcome.noMatch(ConditionMessage
                        .forCondition(ConditionalOnMissingBean.class, spec)
                        .found("bean", "beans").items(Style.QUOTE, matching));
            }
            matchMessage = matchMessage.andCondition(ConditionalOnMissingBean.class, spec)
                    .didNotFind("any beans").atAll();
        }
        return ConditionOutcome.match(matchMessage);
    }

這裡有很多註解類都使用了OnBeanCondition

private List<String> getMatchingBeans(ConditionContext context,
            BeanSearchSpec beans) {
        //獲得spring容器
        ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
        if (beans.getStrategy() == SearchStrategy.PARENTS
                || beans.getStrategy() == SearchStrategy.ANCESTORS) {
            BeanFactory parent = beanFactory.getParentBeanFactory();
            Assert.isInstanceOf(ConfigurableListableBeanFactory.class, parent,
                    "Unable to use SearchStrategy.PARENTS");
            beanFactory = (ConfigurableListableBeanFactory) parent;
        }
        //容器都為空肯定是沒有該類的實現的
        if (beanFactory == null) {
            return Collections.emptyList();
        }
        List<String> beanNames = new ArrayList<String>();
        boolean considerHierarchy = beans.getStrategy() != SearchStrategy.CURRENT;
        for (String type : beans.getTypes()) {
            beanNames.addAll(getBeanNamesForType(beanFactory, type,
                    context.getClassLoader(), considerHierarchy));
        }
        for (String ignoredType : beans.getIgnoredTypes()) {
            beanNames.removeAll(getBeanNamesForType(beanFactory, ignoredType,
                    context.getClassLoader(), considerHierarchy));
        }
        for (String annotation : beans.getAnnotations()) {
            beanNames.addAll(Arrays.asList(getBeanNamesForAnnotation(beanFactory,
                    annotation, context.getClassLoader(), considerHierarchy)));
        }
        //下面就判斷,spring容器當中是否有該類的實現,有的話就加入
        for (String beanName : beans.getNames()) {
            if (containsBean(beanFactory, beanName, considerHierarchy)) {
                beanNames.add(beanName);
            }
        }
        return beanNames;
    }

如果beanNames存在則返回

matchMessage = matchMessage.andCondition(ConditionalOnBean.class, spec)
                    .found("bean", "beans").items(Style.QUOTE, matching);

否則返回

return ConditionOutcome.noMatch(
                        ConditionMessage.forCondition(ConditionalOnBean.class, spec)
                                .didNotFind("any beans").atAll());
public static ConditionOutcome match(ConditionMessage message) {
        return new ConditionOutcome(true, message);
    }

    /**
     * Create a new {@link ConditionOutcome} instance for 'no match'. For more consistent
     * messages consider using {@link #noMatch(ConditionMessage)}.
     * @param message the message
     * @return the {@link ConditionOutcome}
     */
    public static ConditionOutcome noMatch(String message) {
        return new ConditionOutcome(false, message);
    }

public ConditionOutcome(boolean match, ConditionMessage message) {
        Assert.notNull(message, "ConditionMessage must not be null");
        this.match = match;
        this.message = message;
    }

在SpringBootCondition.matches中最終返回
return outcome.isMatch();
也就是匹配成不成功。
成功就能該類能進行解析註冊。
其他的看下相應的處理類即可。

總結下,在掃描類時,先要判斷該類是否能通過條件註解。
條件註解都使用了@Conditional,相應的處理類實現了Condition介面。在該介面matches方法中進行對應的邏輯處理。

上一講是講解自動配置,這一講講解的是條件註解。
下一講講解一個例項,手動實現一個類,使用springboot自動配置,並且結合條件註解。

需求:構建一個redis配置工具,如果存在jedis,redistemplate,redis.propterties才能實現自動配置。以後只需要匯入jar,配置redis.propterties即可直接使用redisTemplate。


菜鳥不易,望有問題指出,共同進步