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.使用方法
- name和value不能都为空,也不能都有值;
- 如果配置了prefix,在匹配时的属性名为prefix+name;
- 如果配置文件(eg: application.yml)中不包含属性名name,且matchIfMissing为false,返回false;
- 如果配置文件(eg: application.yml)中不包含属性名name,且matchIfMissing为true,返回true;
- 如果配置文件(eg: application.yml)中包含属性名name,且配置了havingValue,比较属性名name对应的属性值和havingValue是否相等,相等返回true,否则返回false;
- 如果配置文件(eg: application.yml)中包含属性名name,且没有配置havingValue,比较属性名name对应的属性值是否等于字符串false,相等返回false,否则返回true;
- 循环以上判断,有一项为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();
}
}
}