Spring Boot源码解析(一)- @ConditionalOnProperty详解

1.前言

最近在学习Spring Boot的运作原理发现“自动配置”的功能都借助于@Conditional注解来完成,@Conditional根据满足某一特定条件创建一个特定的Bean。比如,当某一个类在一个路径下的时候,自动配置一个或多个Bean。即根据特定条件来控制Bean的创建行为。本文只讲解其中一个核心注解@ConditionalOnProperty

2.@ConditionalOnProperty说明

package org.springframework.boot.autoconfigure.condition;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

import org.springframework.context.annotation.Conditional;
import org.springframework.core.env.Environment;

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

	/**
	 * name的别名。
     *
	 * @return the names
	 */
	String[] value() default {};

	/**
	 * 属性前缀。
     *
	 * @return the prefix
	 */
	String prefix() default "";

	/**
	 * 属性名(字符串数组)。
     *   
	 * @return the names
	 */
	String[] name() default {};

	/**
	 * 预期值。
     *
	 * @return the expected value
	 */
	String havingValue() default "";

	/**
	 * 如果属性名不存在,条件是否成立。
     *
	 * @return if should match if the property is missing
	 */
	boolean matchIfMissing() default false;

	/**
	 * 属性名是否松散匹配。
     *
	 * @return if relaxed names are used
	 */
	boolean relaxedNames() default true;

}

3.使用方法

  1. name和value不能都为空,也不能都有值;
  2. 如果配置了prefix,在匹配时的属性名为prefix+name;
  3. 如果配置文件(eg: application.yml)中不包含属性名name,且matchIfMissing为false,返回false;
  4. 如果配置文件(eg: application.yml)中不包含属性名name,且matchIfMissing为true,返回true;
  5. 如果配置文件(eg: application.yml)中包含属性名name,且配置了havingValue,比较属性名name对应的属性值和havingValue是否相等,相等返回true,否则返回false;
  6. 如果配置文件(eg: application.yml)中包含属性名name,且没有配置havingValue,比较属性名name对应的属性值是否等于字符串false,相等返回false,否则返回true;
  7. 循环以上判断,有一项为false,则@Conditional不成立。

 

4.OnPropertyCondition条件说明

package org.springframework.boot.autoconfigure.condition;

import org.springframework.boot.autoconfigure.condition.ConditionMessage.Style;
import org.springframework.boot.bind.RelaxedPropertyResolver;
import org.springframework.context.annotation.Condition;
import org.springframework.context.annotation.ConditionContext;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.AnnotationAttributes;
import org.springframework.core.annotation.Order;
import org.springframework.core.env.PropertyResolver;
import org.springframework.core.type.AnnotatedTypeMetadata;
import org.springframework.util.Assert;
import org.springframework.util.MultiValueMap;
import org.springframework.util.StringUtils;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

/**
 * {@link Condition} that checks if properties are defined in environment.
 *
 * @author Maciej Walkowiak
 * @author Phillip Webb
 * @author Stephane Nicoll
 * @author Andy Wilkinson
 * @see ConditionalOnProperty
 * @since 1.1.0
 */
@Order(Ordered.HIGHEST_PRECEDENCE + 40)
class OnPropertyCondition extends SpringBootCondition {

    /**
     * 获取比对结果。
     *
     * <p>ConditionOutcome待翻译,看源码可以理解为成功或失败的结果。TODO
     * <p>ConditionMessage待翻译,看源码可以理解为ConditionOutcome含有的一种描述。TODO
     *
     * @param context
     * @param metadata
     * @return
     */
    @Override
    public ConditionOutcome getMatchOutcome(ConditionContext context,
                                            AnnotatedTypeMetadata metadata) {
        // metadata.getAllAnnotationAttributes(ConditionalOnProperty.class.getName())
        // 获取@ConditionalOnProperty注解的所有属性值,属性值是MultiValueMap类型
        List<AnnotationAttributes> allAnnotationAttributes = annotationAttributesFromMultiValueMap(
                metadata.getAllAnnotationAttributes(
                        ConditionalOnProperty.class.getName()));
        // 初始化【比对不成功】列表
        List<ConditionMessage> noMatch = new ArrayList<ConditionMessage>();
        // 初始化【比对成功】列表
        List<ConditionMessage> match = new ArrayList<ConditionMessage>();
        // 循环计算每一个属性的比对结果
        for (AnnotationAttributes annotationAttributes : allAnnotationAttributes) {
            // 计算比对结果
            ConditionOutcome outcome = determineOutcome(annotationAttributes,
                    context.getEnvironment());
            // 判断匹配结果是否成功,如果成功将结果的ConditionMessage加入【比对成功】列表,否则加入【比对不成功】列表
            (outcome.isMatch() ? match : noMatch).add(outcome.getConditionMessage());
        }
        // 如果【比对不成功】列表不为空,返回“匹配失败”的ConditionOutcome,条件不成立
        if (!noMatch.isEmpty()) {
            return ConditionOutcome.noMatch(ConditionMessage.of(noMatch));
        }
        // 返回“匹配成功”的ConditionOutcome,条件成立
        return ConditionOutcome.match(ConditionMessage.of(match));
    }

    /**
     * 将MultiValueMap中的属性数据转成AnnotationAttributes类型。
     *
     * <p>AnnotationAttributes待解析 TODO。
     *
     * @param multiValueMap @ConditionalOnProperty注解的所有属性值
     * @return
     */
    private List<AnnotationAttributes> annotationAttributesFromMultiValueMap(
            MultiValueMap<String, Object> multiValueMap) {
        List<Map<String, Object>> maps = new ArrayList<Map<String, Object>>();
        for (Entry<String, List<Object>> entry : multiValueMap.entrySet()) {
            for (int i = 0; i < entry.getValue().size(); i++) {
                Map<String, Object> map;
                if (i < maps.size()) {
                    map = maps.get(i);
                } else {
                    map = new HashMap<String, Object>();
                    maps.add(map);
                }
                map.put(entry.getKey(), entry.getValue().get(i));
            }
        }
        List<AnnotationAttributes> annotationAttributes = new ArrayList<AnnotationAttributes>(
                maps.size());
        for (Map<String, Object> map : maps) {
            annotationAttributes.add(AnnotationAttributes.fromMap(map));
        }
        return annotationAttributes;
    }

    /**
     * 计算比对结果。
     *
     * @param annotationAttributes 转换后的注解属性值
     * @param resolver
     * @return
     */
    private ConditionOutcome determineOutcome(AnnotationAttributes annotationAttributes,
                                              PropertyResolver resolver) {
        // 用属性值实例化一个内部类Spec
        Spec spec = new Spec(annotationAttributes);
        // 初始化【未配置属性】列表
        List<String> missingProperties = new ArrayList<String>();
        // 初始化【匹配失败属性】列表
        List<String> nonMatchingProperties = new ArrayList<String>();
        // 匹配属性(传入missingProperties和nonMatchingProperties)
        spec.collectProperties(resolver, missingProperties, nonMatchingProperties);
        // 如果【未配置属性】列表不为空,返回实例化匹配失败的ConditionOutcome
        if (!missingProperties.isEmpty()) {
            return ConditionOutcome.noMatch(
                    ConditionMessage.forCondition(ConditionalOnProperty.class, spec)
                            .didNotFind("property", "properties")
                            .items(Style.QUOTE, missingProperties));
        }
        // 如果【匹配失败属性】列表不为空,返回实例化匹配失败的ConditionOutcome
        if (!nonMatchingProperties.isEmpty()) {
            return ConditionOutcome.noMatch(
                    ConditionMessage.forCondition(ConditionalOnProperty.class, spec)
                            .found("different value in property",
                                    "different value in properties")
                            .items(Style.QUOTE, nonMatchingProperties));
        }
        // 返回实例化匹配成功的ConditionOutcome
        return ConditionOutcome.match(ConditionMessage
                .forCondition(ConditionalOnProperty.class, spec).because("matched"));
    }

    /**
     * 私有静态内部类。
     */
    private static class Spec {

        /**
         * 每个要匹配的属性前缀。如果没有指定前缀,前缀将自动以"."结尾。
         *
         * <p>对应{@link ConditionalOnProperty#prefix()}。
         */
        private final String prefix;

        /**
         * 属性的预期值。如果没有指定预期值,属性不能等于false字符串。
         *
         * <p>如果names中配置了多个属性要匹配,那么所有的name对应的预期值都是该值。
         *
         * <p>对应{@link ConditionalOnProperty#havingValue()}。
         */
        private final String havingValue;

        /**
         * 要匹配的属性的名称。如果定义了前缀prefix,那么将使用prefix+name的形式进行匹配。
         *
         * <p>names是字符串数组,可以设置多个匹配的条件。
         *
         * <p>对应{@link ConditionalOnProperty#name()}
         */
        private final String[] names;

        /**
         * names是否松散的,默认为true。
         *
         * <p>对应{@link ConditionalOnProperty#relaxedNames()}
         */
        private final boolean relaxedNames;

        /**
         * 如果没有设置要匹配的属性,那么指定条件是否应该匹配成功,默认为false。
         *
         * <p>对应{@link ConditionalOnProperty#matchIfMissing()}
         */
        private final boolean matchIfMissing;

        /**
         * 构造函数。
         *
         * @param annotationAttributes
         */
        Spec(AnnotationAttributes annotationAttributes) {
            // 前缀作trim处理。
            String prefix = annotationAttributes.getString("prefix").trim();
            // 如果前缀有值,且不是以"."结尾,在结尾加"."
            if (StringUtils.hasText(prefix) && !prefix.endsWith(".")) {
                prefix = prefix + ".";
            }
            // 初始化前缀
            this.prefix = prefix;
            // 初始化预期值
            this.havingValue = annotationAttributes.getString("havingValue");
            // 初始化属性名数组
            this.names = getNames(annotationAttributes);
            // 初始化松散names
            this.relaxedNames = annotationAttributes.getBoolean("relaxedNames");
            // 初始化如果没有配置,条件是否成功
            this.matchIfMissing = annotationAttributes.getBoolean("matchIfMissing");
        }

        /**
         * 初始化属性名数组。
         *
         * @param annotationAttributes
         * @return
         */
        private String[] getNames(Map<String, Object> annotationAttributes) {
            // 获取value数组(name数组的别名)
            String[] value = (String[]) annotationAttributes.get("value");
            // 获取name数组
            String[] name = (String[]) annotationAttributes.get("name");
            // name和value必须指定,不能同时为空。
            Assert.state(value.length > 0 || name.length > 0,
                    "The name or value attribute of @ConditionalOnProperty must be specified");
            // name和value是互斥的,不能同时有值。
            Assert.state(value.length == 0 || name.length == 0,
                    "The name and value attributes of @ConditionalOnProperty are exclusive");
            // value和name,哪个有值就返回哪个
            return (value.length > 0) ? value : name;
        }

        /**
         * 判断属性是否匹配
         *
         * @param resolver
         * @param missing     【未配置属性】列表
         * @param nonMatching 【匹配失败属性】列表
         */
        private void collectProperties(PropertyResolver resolver, List<String> missing,
                                       List<String> nonMatching) {
            // 如果是松散names
            if (this.relaxedNames) {
                // 实例化一个属性处理器,并传入前缀
                resolver = new RelaxedPropertyResolver(resolver, this.prefix);
            }
            // 循环配置属性是否匹配
            for (String name : this.names) {
                String key = (this.relaxedNames ? name : this.prefix + name);
                if (resolver.containsProperty(key)) {
                    // 如果处理器(理解为配置文件application.yml)中包含这个属性名,判断属性名对应的值是否和预期值匹配
                    if (!isMatch(resolver.getProperty(key), this.havingValue)) {
                        // 如果不匹配,将属性名加入【匹配失败属性】列表
                        nonMatching.add(name);
                    }
                } else {
                    // 如果处理器中(理解为配置文件application.yml)中未包含这个属性,且matchIfMissing为false,将属性名加入【未配置属性】列表
                    if (!this.matchIfMissing) {
                        missing.add(name);
                    }
                }
            }
        }

        /**
         * 判断属性对应的值是否和预期值匹配。
         *
         * @param value         属性值
         * @param requiredValue 预期值
         * @return 是否匹配成功 true-成功 false-失败
         */
        private boolean isMatch(String value, String requiredValue) {
            // 如果预期值不为空,忽略大小写判断两者是否相等
            if (StringUtils.hasLength(requiredValue)) {
                return requiredValue.equalsIgnoreCase(value);
            }
            // 预期值为空,忽略大小写判断属性值是否等于false
            return !"false".equalsIgnoreCase(value);
        }

        @Override
        public String toString() {
            StringBuilder result = new StringBuilder();
            result.append("(");
            result.append(this.prefix);
            if (this.names.length == 1) {
                result.append(this.names[0]);
            } else {
                result.append("[");
                result.append(StringUtils.arrayToCommaDelimitedString(this.names));
                result.append("]");
            }
            if (StringUtils.hasLength(this.havingValue)) {
                result.append("=").append(this.havingValue);
            }
            result.append(")");
            return result.toString();
        }

    }

}

 

  • 3
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值