條件裝配@Conditional源碼分析

博客索引java

Spring提供了一個基於條件的bean建立(@Conditional)。@Conditional根據知足的某一特定條件建立一個特定的bean。條件註解的註解太多了,好比@ConditionalOnBean,@ConditionalOnMissingBean@ConditionalOnProperty等等都在包org.springframework.boot.autoconfigure.condition下面。spring

@ConditionalOnProperty

我就以這個@ConditionalOnProperty爲例子來說解,只要這個註解原理弄清楚了,一通百通。bash

1. 使用

建立一個類以下,若是applicaiton.properties裏面包含study.enable=true,那麼該類會被建立。app

@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這行配置,項目正常運行。ide

原理解析

話很少說,先看看@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 post

EurekaClientAutoConfiguration

圖片中標記的這行註解意思是,當application.properties中或者環境變量中配置了eureka.client.enabled=true或者不配置(不配置的話,缺省值matchIfMissing也是true),都會初始化這個類。只有當你顯示的配置eureka.client.enabled=false的時候,纔不會初始化這個類。因此在Eureka Client2.2.3中,不添加註解@EnableDiscoveryClient也是能夠向Eureka Server註冊的。咱們會發現,在Cloud中會有不少這種條件註解的使用,因此這些基礎東西必須熟練掌握,才能玩轉Cloud體系。測試

其實咱們會發現,條件註解都是基於@Conditional實現的,那麼看看@Conditional源碼ui

@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源碼this

@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源碼深度解析》

相關文章
相關標籤/搜索