SpringBoot源碼解析-@ConditionalOnXXX註解原理

上一節講到springboot自動化配置是以@Conditional相關注解做爲判斷條件,那麼這一節咱們來了解一下@Conditional相關注解的原理。spring


@Conditional使用示範

新建一個ControllerConditional類,實現Condition接口,實現matches方法,返回false數組

public class ControllerConditional implements Condition {
    @Override
    public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
        return false;
    }
}
複製代碼

在Controller類上添加@Conditional(ControllerConditional.class)註解springboot

@RestController
@Conditional(ControllerConditional.class)
public class Controller {

    @RequestMapping("/hello")
    public String hello(){
        return "hello";
    }

}
複製代碼

在main函數中嘗試獲取Controller類。bash

@SpringBootApplication
public class Application {

    public static void main(String[] args) {
        ApplicationContext context = SpringApplication.run(Application.class, args);
        String[] beanNamesForType = context.getBeanNamesForType(Controller.class);
        System.out.println(Arrays.toString(beanNamesForType));
    }

}
複製代碼

不出意外控制檯會打印出空數組[]。此時去掉Controller類上的@Conditional(ControllerConditional.class)註解,控制檯又能夠打印出[controller]app

@Conditional註解的原理

通過上面的簡單示例,對於@Conditional註解的使用你們應該清楚了,若是matches方法返回false,那麼這個類就不會被掃描,反之則會被掃描進spring容器。下面就來了解一下他們的原理。ide

回到上一節咱們講解析Component,PropertySources,ComponentScan這幾個註解的地方,進入processConfigurationClass方法,發如今解析以前有一行代碼。函數

protected void processConfigurationClass(ConfigurationClass configClass) throws IOException {
		if (this.conditionEvaluator.shouldSkip(configClass.getMetadata(), ConfigurationPhase.PARSE_CONFIGURATION)) {
			return;
		}
複製代碼

shouldSkip方法就是判斷@Conditional註解的地方(這個shouldSkip方法其餘地方也有,可是基本原理都是同樣的,或者說就是同樣的),在進入以前,咱們先了解一下他的參數以及conditionEvaluator。找到當前類的構造函數,發現以下信息。post

public ConfigurationClassParser(MetadataReaderFactory metadataReaderFactory,
			ProblemReporter problemReporter, Environment environment, ResourceLoader resourceLoader,
			BeanNameGenerator componentScanBeanNameGenerator, BeanDefinitionRegistry registry) {

		...
		this.conditionEvaluator = new ConditionEvaluator(registry, environment, resourceLoader);
	}
	
	public ConditionEvaluator(@Nullable BeanDefinitionRegistry registry,
			@Nullable Environment environment, @Nullable ResourceLoader resourceLoader) {

		this.context = new ConditionContextImpl(registry, environment, resourceLoader);
	}
複製代碼

構造函數不復雜,應該沒啥問題。接下來了解一下shouldSkip方法的兩個參數,順着方法找回去。ui

this.metadata = new StandardAnnotationMetadata(beanClass, true);

	public StandardAnnotationMetadata(Class<?> introspectedClass, boolean nestedAnnotationsAsMap) {
		super(introspectedClass);
		this.annotations = introspectedClass.getAnnotations();
		this.nestedAnnotationsAsMap = nestedAnnotationsAsMap;
	}
複製代碼

metadata就是這邊的StandardAnnotationMetadata,第二個參數是一個枚舉。作好這些準備工做後,開始進入shouldSkip方法。this

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);
		}
		//獲取該類的全部@Conditional註解裏面的參數類
		List<Condition> conditions = new ArrayList<>();
		for (String[] conditionClasses : getConditionClasses(metadata)) {
			for (String conditionClass : conditionClasses) {
				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();
			}
			//依次判斷每一個類的matches方法,有一個方法返回false則跳過這個類
			if ((requiredPhase == null || requiredPhase == phase) && !condition.matches(this.context, metadata)) {
				return true;
			}
		}

		return false;
	}
複製代碼

shouldSkip方法的邏輯不復雜,獲取全部conditional註解裏的參數類,依次調用matches方法,若是任意方法返回false則跳過該類。因此在這兒,咱們就看到了matches方法的參數以及調用。這樣的話,conditional註解的原理你們應該沒啥問題了。

那麼下面經過舉例來看看由conditional註解衍生出的ConditionalOnXXX類型註解。

@ConditionalOnClass註解的原理

打開ConditionalOnClass註解的源代碼,自己帶有兩個屬性,一個class類型的value,一個String類型的name。同時ConditionalOnClass註解自己還帶了一個@Conditional(OnClassCondition.class)註解。因此,其實ConditionalOnClass註解的判斷條件就在於OnClassCondition這個類的matches方法。

@Conditional(OnClassCondition.class)
public @interface ConditionalOnClass {

	Class<?>[] value() default {};

	String[] name() default {};

}
複製代碼

因此沒啥好說的,直接進入OnClassCondition類,尋找matches方法。最終,在他的父類SpringBootCondition中,找到了matches方法。代碼以下:

@Override
	public final boolean matches(ConditionContext context,
			AnnotatedTypeMetadata metadata) {
		//獲取加上了@ConditionalOnClass註解的類或者方法的名稱(咱們就以類分析,加在方法上是一個原理)
		String classOrMethodName = getClassOrMethodName(metadata);
		try {
			//獲取匹配結果
			ConditionOutcome outcome = getMatchOutcome(context, metadata);
			logOutcome(classOrMethodName, outcome);
			recordEvaluation(context, classOrMethodName, outcome);
			return outcome.isMatch();
		}
		...
	}
複製代碼

從代碼不難看出,關鍵方法在getMatchOutcome裏,因此進入該方法。

@Override
	public ConditionOutcome getMatchOutcome(ConditionContext context,
			AnnotatedTypeMetadata metadata) {
		ClassLoader classLoader = context.getClassLoader();
		ConditionMessage matchMessage = ConditionMessage.empty();
		//獲取全部須要判斷是否存在的類
		List<String> onClasses = getCandidates(metadata, ConditionalOnClass.class);
		if (onClasses != null) {
			//篩選這些類,判斷條件爲ClassNameFilter.MISSING
			List<String> missing = filter(onClasses, ClassNameFilter.MISSING,
					classLoader);
			if (!missing.isEmpty()) {
				return ConditionOutcome
						.noMatch(ConditionMessage.forCondition(ConditionalOnClass.class)
								.didNotFind("required class", "required classes")
								.items(Style.QUOTE, missing));
			}
			matchMessage = matchMessage.andCondition(ConditionalOnClass.class)
					.found("required class", "required classes").items(Style.QUOTE,
							filter(onClasses, ClassNameFilter.PRESENT, classLoader));
		}
		...
		return ConditionOutcome.match(matchMessage);
	}
複製代碼

該方法並不複雜,和ConditionalOnClass有關的代碼主要有兩行,getCandidates和filter。 首先看看getCandidates:

private List<String> getCandidates(AnnotatedTypeMetadata metadata,
			Class<?> annotationType) {
		MultiValueMap<String, Object> attributes = metadata
				.getAllAnnotationAttributes(annotationType.getName(), true);
		if (attributes == null) {
			return null;
		}
		List<String> candidates = new ArrayList<>();
		addAll(candidates, attributes.get("value"));
		addAll(candidates, attributes.get("name"));
		return candidates;
	}
複製代碼

主要是獲取了ConditionalOnClass的name屬性和value屬性。

接下來看看filter方法,在進入filter方法前,先看一下判斷條件ClassNameFilter.MISSING

MISSING {

			@Override
			public boolean matches(String className, ClassLoader classLoader) {
				return !isPresent(className, classLoader);
			}

		};
		
		public static boolean isPresent(String className, ClassLoader classLoader) {
			if (classLoader == null) {
				classLoader = ClassUtils.getDefaultClassLoader();
			}
			try {
				forName(className, classLoader);
				return true;
			}
			catch (Throwable ex) {
				return false;
			}
		}

		private static Class<?> forName(String className, ClassLoader classLoader)
				throws ClassNotFoundException {
			if (classLoader != null) {
				return classLoader.loadClass(className);
			}
			return Class.forName(className);
		}
複製代碼

邏輯很清晰,若是該類能被加載則判斷成功,不然判斷失敗。如今進入filter方法。

protected List<String> filter(Collection<String> classNames,
			ClassNameFilter classNameFilter, ClassLoader classLoader) {
		if (CollectionUtils.isEmpty(classNames)) {
			return Collections.emptyList();
		}
		List<String> matches = new ArrayList<>(classNames.size());
		for (String candidate : classNames) {
			//逐個判斷咱們添加的判斷條件,若是有不符合的即添加進list
			if (classNameFilter.matches(candidate, classLoader)) {
				matches.add(candidate);
			}
		}
		return matches;
	}
複製代碼

filter方法就是利用剛剛的判斷條件進行判斷,發現不符合的添加進list一併返回,最後生成結果。

因此到這兒,conditional相關注解的原理應該都清楚了,其餘衍生類原理也大多類似,就再也不一一分析。


返回目錄

相關文章
相關標籤/搜索