Анализ исходного кода SpringBoot — принцип аннотации @ConditionalOnXXX

задняя часть

В предыдущем разделе упоминалось, что автоматическая конфигурация Springboot основана на аннотациях, связанных с @Conditional, в качестве условия суждения, а затем в этом разделе давайте разберемся с принципом аннотаций, связанных с @Conditional.


Демонстрация использования @Conditional

Создайте новый класс ControllerConditional, реализуйте интерфейс Condition, реализуйте метод match и верните false.

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

Добавьте аннотацию @Conditional(ControllerConditional.class) к классу Controller.

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

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

}

Попробуйте получить класс Controller в основной функции.

@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));
    }

}

Неудивительно, что консоль выводит пустой массив[]. На этом этапе удалите аннотацию @Conditional(ControllerConditional.class) в классе Controller, и консоль сможет распечатать [controller]

Принцип аннотации @Conditional

После приведенного выше простого примера вам должно быть ясно об использовании аннотации @Conditional, Если метод совпадений возвращает false, класс не будет сканироваться, в противном случае он будет сканироваться в контейнер Spring. Давайте посмотрим на их принципы ниже.

Вернитесь к предыдущему разделу, где мы говорили об анализе аннотаций 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. Найдите конструктор текущего класса и найдите следующую информацию.

	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 и найдите его обратно по методу.

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

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

метаданные — это StandardAnnotationMetadata, а второй параметр — это перечисление. Сделав эти приготовления, начните вводить метод shouldSkip.

	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 несложная: получить все классы параметров в условных аннотациях, вызвать по очереди метод match и пропустить класс, если какой-либо из методов вернет false. Итак, здесь мы видим параметры и вызовы метода match. В этом случае не должно возникнуть проблем с принципом условной аннотации.

Затем давайте рассмотрим аннотации типа ConditionalOnXXX, полученные из условных аннотаций, на примере.

Принцип аннотации @ConditionalOnClass

Откройте исходный код аннотации ConditionalOnClass, которая сама имеет два атрибута: значение типа класса и имя типа String. В то же время сама аннотация ConditionalOnClass также имеет аннотацию @Conditional(OnClassCondition.class). Таким образом, фактически условие оценки аннотации ConditionalOnClass заключается в методеmatches класса OnClassCondition.

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

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

	String[] name() default {};

}

Так что тут и говорить нечего, идем сразу в класс OnClassCondition и ищем метод match. Наконец, в его родительском классе SpringBootCondition был найден метод match. код показывает, как показано ниже:

@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.

Затем посмотрите на метод фильтра. Прежде чем вводить метод фильтра, посмотрите на условие оценки 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);
		}

Логика очень ясна, если класс может быть загружен, оценка успешна, в противном случае оценка не удалась. Теперь переходим к методу фильтра.

	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;
	}

Метод фильтрации состоит в том, чтобы использовать только что вынесенные условия оценки, добавить те, которые не соответствуют списку, и вернуть их вместе, и, наконец, сгенерировать результат.

Так что здесь принципы условно-связанных объяснений должны быть понятны, а принципы других производных классов в основном схожи, поэтому я не буду разбирать их по отдельности.


Назад к содержанию