Spring提供了一个基于条件的bean创建(@Conditional)。@Conditional
根据满足的某一特定条件创建一个特定的bean。条件注解的注解太多了,比如@ConditionalOnBean
,@ConditionalOnMissingBean
,@ConditionalOnProperty
等等都在包org.springframework.boot.autoconfigure.condition
下面。
@ConditionalOnProperty
我就以这个@ConditionalOnProperty
为例子来讲解,只要这个注解原理弄清楚了,一通百通。
1. 使用
创建一个类如下,如果applicaiton.properties
里面包含study.enable=true,那么该类会被创建。
@Configuration
@ConditionalOnProperty(prefix = "study", name = "enable", havingValue = "true")
public class HelloServiceAutoConfiguration {
}
2. 创建一个测试类ConditionalController
验证
@RestController
public class ConditionalController {
@Autowired
private HelloServiceAutoConfiguration autoConfiguration;
}
现在我们的applicaiton.properties
配置文件里面是没有study.enable=true这行配置,我们启动Spring Boot项目,会发现报错。我们在配置文件中加入study.enable=true这行配置,项目正常运行。
原理解析
话不多说,先看看@ConditionalOnProperty
源码。
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.TYPE, ElementType.METHOD })
@Documented
@Conditional(OnPropertyCondition.class)
public @interface ConditionalOnProperty {
String[] value() default {};
String prefix() default "";
String[] name() default {};
String havingValue() default "";
//缺省值
boolean matchIfMissing() default false;
}
比如我们看看Eureka的自动配置类EurekaClientAutoConfiguration
图片中标记的这行注解意思是,当application.properties中或者环境变量中配置了eureka.client.enabled=true或者不配置(不配置的话,缺省值matchIfMissing也是true),都会初始化这个类。只有当你显示的配置eureka.client.enabled=false的时候,才不会初始化这个类。所以在Eureka Client2.2.3中,不添加注解@EnableDiscoveryClient
也是可以向Eureka Server注册的。我们会发现,在Cloud中会有很多这种条件注解的使用,所以这些基础东西必须熟练掌握,才能玩转Cloud体系。
其实我们会发现,条件注解都是基于@Conditional
实现的,那么看看@Conditional
源码
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Conditional {
/**
* All {@link Condition Conditions} that must {@linkplain Condition#matches match}
* in order for the component to be registered.
*/
Class<? extends Condition>[] value();
}
注释说的很清楚,Condition必须满足match方法才能使component注册,查看Condition
源码
@FunctionalInterface
public interface Condition {
/**
* Determine if the condition matches.
* @param context the condition context
* @param metadata metadata of the {@link org.springframework.core.type.AnnotationMetadata class}
* or {@link org.springframework.core.type.MethodMetadata method} being checked
* @return {@code true} if the condition matches and the component can be registered,
* or {@code false} to veto the annotated component's registration
*/
boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata);
}
如果Condition匹配返回true,这个组件就会被注册,否则就不会注册。也就是需要继承这个Condition
,实现该matches方法,来自定义条件匹配。那么ConditionalOnProperty
是如何跟Condition
关联上的呢?我们可以看到ConditionalOnProperty
上面的注解@Conditional(OnPropertyCondition.class)
,看看类OnPropertyCondition
的源码。
@Order(Ordered.HIGHEST_PRECEDENCE + 40)
class OnPropertyCondition extends SpringBootCondition {
@Override
public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) {
List<AnnotationAttributes> allAnnotationAttributes = annotationAttributesFromMultiValueMap(
metadata.getAllAnnotationAttributes(ConditionalOnProperty.class.getName()));
List<ConditionMessage> noMatch = new ArrayList<>();
List<ConditionMessage> match = new ArrayList<>();
for (AnnotationAttributes annotationAttributes : allAnnotationAttributes) {
ConditionOutcome outcome = determineOutcome(annotationAttributes, context.getEnvironment());
(outcome.isMatch() ? match : noMatch).add(outcome.getConditionMessage());
}
if (!noMatch.isEmpty()) {
return ConditionOutcome.noMatch(ConditionMessage.of(noMatch));
}
return ConditionOutcome.match(ConditionMessage.of(match));
}
···先省略其他代码
}
发现这个类中并没有matches方法,利用快捷键搜索方法matches,发现这个方法在类SpringBootCondition
中
利用idea工具,查看OnPropertyCondition
的结构
OnPropertyCondition
继承了SpringBootCondition
,而类是一个抽象类,并且是一个模板类,源码如下:
public abstract class SpringBootCondition implements Condition {
@Override
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);
}
}
···
现在逐步分析源码:
①得到类名或者方法名(源码名字取得很好,不需要直接看源码就可以看出意思)
private static String getClassOrMethodName(AnnotatedTypeMetadata metadata) {
if (metadata instanceof ClassMetadata) {
ClassMetadata classMetadata = (ClassMetadata) metadata;
return classMetadata.getClassName();
}
MethodMetadata methodMetadata = (MethodMetadata) metadata;
return methodMetadata.getDeclaringClassName() + "#" + methodMetadata.getMethodName();
}
②获取ConditionOutcome实例,该实例记录了条件匹配的结果和日志信息,从源码中可以看出来。该类唯一的抽象类,留给子类实现。
public abstract ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata);
③打印日志
protected final void logOutcome(String classOrMethodName, ConditionOutcome outcome) {
if (this.logger.isTraceEnabled()) {
this.logger.trace(getLogMessage(classOrMethodName, outcome));
}
}
④记录结果
private void recordEvaluation(ConditionContext context, String classOrMethodName, ConditionOutcome outcome) {
if (context.getBeanFactory() != null) {
ConditionEvaluationReport.get(context.getBeanFactory()).recordConditionEvaluation(classOrMethodName, this,
outcome);
}
}
⑤返回条件匹配结果。
我们来看一下类ConditionOutcome
信息。条件匹配结果里面包含了macth,和log信息,所以返回outcome.isMatch()就是返回ConditionOutcome
中的match值,只有这步和第②步很关键,前面几步都是记录日志等。
public class ConditionOutcome {
private final boolean match;
private final ConditionMessage message;
···
}
看到这里,估计大部分人都有些迷糊了,我们在理理思路。SpringBootCondition
是一个模板类,只有第②步getMatchOutcome(ConditionContext, AnnotatedTypeMetadata)
是个抽象类留个子类实现,其他的都已经实现好了,那么只需要看下类OnPropertyCondition
中的getMatchOutcome(ConditionContext, AnnotatedTypeMetadata)
即可。
class OnPropertyCondition extends SpringBootCondition {
@Override
public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) {
// 获取ConditionalOnProperty注解里面的所有属性值
List<AnnotationAttributes> allAnnotationAttributes = annotationAttributesFromMultiValueMap(
metadata.getAllAnnotationAttributes(ConditionalOnProperty.class.getName()));
List<ConditionMessage> noMatch = new ArrayList<>();
List<ConditionMessage> match = new ArrayList<>();
for (AnnotationAttributes annotationAttributes : allAnnotationAttributes) {
// 核心代码: 确定ConditionOutcome
ConditionOutcome outcome = determineOutcome(annotationAttributes, context.getEnvironment());
(outcome.isMatch() ? match : noMatch).add(outcome.getConditionMessage());
}
if (!noMatch.isEmpty()) {
return ConditionOutcome.noMatch(ConditionMessage.of(noMatch));
}
return ConditionOutcome.match(ConditionMessage.of(match));
}
确定ConditionOutcome
/* annotationAttributes这是是从注解里面读出来的值
* resolver 这个理解为从配置文件中取值,也就是从application.properties中取值
*/
private ConditionOutcome determineOutcome(AnnotationAttributes annotationAttributes, PropertyResolver resolver) {
//将annotationAttributes包装成Spec类
Spec spec = new Spec(annotationAttributes);
//
List<String> missingProperties = new ArrayList<>();
List<String> nonMatchingProperties = new ArrayList<>();
// 将spec与resolver进行匹配,看属性值是否匹配
spec.collectProperties(resolver, missingProperties, nonMatchingProperties);
if (!missingProperties.isEmpty()) {
return ConditionOutcome.noMatch(ConditionMessage.forCondition(ConditionalOnProperty.class, spec)
.didNotFind("property", "properties").items(Style.QUOTE, missingProperties));
}
if (!nonMatchingProperties.isEmpty()) {
return ConditionOutcome.noMatch(ConditionMessage.forCondition(ConditionalOnProperty.class, spec)
.found("different value in property", "different value in properties")
.items(Style.QUOTE, nonMatchingProperties));
}
return ConditionOutcome
.match(ConditionMessage.forCondition(ConditionalOnProperty.class, spec).because("matched"));
}
匹配属性
private void collectProperties(PropertyResolver resolver, List<String> missing, List<String> nonMatching) {
for (String name : this.names) {
// key在这里等于study.enable,因为之前将属性包装成Spec类,这个类将prefix = prefix + "."
String key = this.prefix + name;
// 判断环境属性中是否包含这个key,如果application.properties配置了study.enable=true,
// 那么这里返回true,那么列表nonMatching与missing都为null
if (resolver.containsProperty(key)) {
if (!isMatch(resolver.getProperty(key), this.havingValue)) {
nonMatching.add(name);
}
}
// 如果返回false
else {
// 继续判断matchIfMissing值,默认为false,加入missing列表。
if (!this.matchIfMissing) {
missing.add(name);
}
}
}
}
最后来看看ConditionOutcome
的match方法与noMatch方法。原来match方法就是返回true,nomatch方法就是返回false,
public static ConditionOutcome match(ConditionMessage message) {
return new ConditionOutcome(true, message);
}
public static ConditionOutcome noMatch(ConditionMessage message) {
return new ConditionOutcome(false, message);
}
那么现在已经解析完毕了。最后看一下Spring Boot是在哪里调用的?
定位到整个判断逻辑的切入点是在ConfigurationClassParser#processConfigurationClass
protected void processConfigurationClass(ConfigurationClass configClass, Predicate<String> filter) throws IOException {
if (this.conditionEvaluator.shouldSkip(configClass.getMetadata(), ConfigurationPhase.PARSE_CONFIGURATION)) {
return;
}
ConfigurationClass existingClass = this.configurationClasses.get(configClass);
if (existingClass != null) {
if (configClass.isImported()) {
if (existingClass.isImported()) {
existingClass.mergeImportedBy(configClass);
}
// Otherwise ignore new imported config class; existing non-imported class overrides it.
return;
}
else {
this.configurationClasses.remove(configClass);
this.knownSuperclasses.values().removeIf(configClass::equals);
}
}
// Recursively process the configuration class and its superclass hierarchy.
SourceClass sourceClass = asSourceClass(configClass, filter);
do {
sourceClass = doProcessConfigurationClass(configClass, sourceClass, filter);
}
while (sourceClass != null);
this.configurationClasses.put(configClass, configClass);
}
这里太熟悉了,因为前几天@ComponentSacn的解析就是在这行代码里。
sourceClass = doProcessConfigurationClass(configClass, sourceClass, filter);
代码的第一行就是整个@Conditional
的切入点,如果验证不通过,那么会忽略后面的解析逻辑,那么这个类的其他属性以及@ComponentSacn之类的配置都不会得到解析。这个方法会获取类上的@Conditional
public boolean shouldSkip(@Nullable AnnotatedTypeMetadata metadata, @Nullable ConfigurationPhase phase) {
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<>();
for (String[] conditionClasses : getConditionClasses(metadata)) {
for (String conditionClass : conditionClasses) {
//实例化Condition
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();
}
// 终于看到了condition的matches,这里直接跳到SpringBootCondition的matches方法,形成闭环,解析完成
if ((requiredPhase == null || requiredPhase == phase) && !condition.matches(this.context, metadata)) {
return true;
}
}
return false;
}
如果有地方有疑惑或者写的有不好,可以评论或者通过邮箱联系我creazycoder@sina.com
文章参考:
《Spring源码深度解析》