条件装配@Conditional源码分析

1,764 阅读6分钟

博客索引

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

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图
我们发现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是在哪里调用的?

processConfigurationClass调用链
定位到整个判断逻辑的切入点是在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源码深度解析》