Spring Boot条件化自动装配

标准@Configuration类是自动装配的底层实现,并且搭配Spring Framework @Conditional注解,使其能合理地在不同环境中运作。在《SpringBoot自动装配》中讨论过,@EnableAutoConfiguration利用AutoConfigurationImportFilter实现类OnClassCondition等过滤非法自动装配Class,从而间接地接触条件注解@ConditionalOnClass。

条件注解@ConditionalOnClass采用元标注@Conditional(OnClassCondition.class)的方式定义,所以它包含@Conditional的属性元信息。关于@Conditional参考《@Conditional详解》

实际上,所有Spring Boot条件注解@Conditional*均采用元标注@Conditional的方式实现。spring boot包含许多@Conditional注释,您可以通过注释@Configuration类或单个@Bean方法在自己的代码中重用这些注释。这些注释包括:

1、Class条件注解

@ConditionalOnClass和@ConditionalOnMissingClass注释允许基于特定类的存在或不存在来激活@Configuration类。由于注释元数据是使用ASM解析的,因此可以使用value属性来引用实际的类,即使该类实际上可能不会出现在正在运行的应用程序类路径上。如果希望使用字符串值指定类名,也可以使用name属性。

@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Conditional(OnClassCondition.class)
public @interface ConditionalOnClass {

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

	String[] name() default {};
}

这种机制对@Bean方法的应用方式不同,在@Bean方法中,返回类型通常是条件的目标:在方法上的条件应用之前,JVM将加载类和可能处理的方法引用,如果类不存在,这些方法引用将失败。
为了处理这种情况,可以使用一个单独的@Configuration类来隔离条件,如下例所示:

@Configuration(proxyBeanMethods = false)
// Some conditions
public class MyAutoConfiguration {

    // Auto-configured beans
    @Configuration(proxyBeanMethods = false)
    @ConditionalOnClass(EmbeddedAcmeService.class)
    static class EmbeddedConfiguration {
        @Bean
        @ConditionalOnMissingBean
        public EmbeddedAcmeService embeddedAcmeService() { ... }
    }
}

@ConditionalOnClass和@ConditionalOnMissingClass均使用@Conditional(OnClassCondition.class)实现:

@Order(Ordered.HIGHEST_PRECEDENCE)
class OnClassCondition extends SpringBootCondition
		implements AutoConfigurationImportFilter, BeanFactoryAware, BeanClassLoaderAware {

	@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) {
			List<String> missing = getMatches(onClasses, MatchType.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,
							getMatches(onClasses, MatchType.PRESENT, classLoader));
		}
		List<String> onMissingClasses = getCandidates(metadata,
				ConditionalOnMissingClass.class);
		if (onMissingClasses != null) {
			List<String> present = getMatches(onMissingClasses, MatchType.PRESENT,
					classLoader);
			if (!present.isEmpty()) {
				return ConditionOutcome.noMatch(
						ConditionMessage.forCondition(ConditionalOnMissingClass.class)
								.found("unwanted class", "unwanted classes")
								.items(Style.QUOTE, present));
			}
			matchMessage = matchMessage.andCondition(ConditionalOnMissingClass.class)
					.didNotFind("unwanted class", "unwanted classes").items(Style.QUOTE,
							getMatches(onMissingClasses, MatchType.MISSING, classLoader));
		}
		return ConditionOutcome.match(matchMessage);
	}
}

2、Bean条件注解

@ConditionalOnBean和@ConditionalOnMissingBean注释允许基于特定bean的存在或不存在来包含bean。可以使用value属性按类型指定bean,也可以使用name指定bean。search属性允许您限制在搜索bean时应考虑的ApplicationContext层次结构。

@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Conditional(OnBeanCondition.class)
public @interface ConditionalOnBean {

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

	String[] type() default {};

	Class<? extends Annotation>[] annotation() default {};

	String[] name() default {};

	SearchStrategy search() default SearchStrategy.ALL;
}
属性方法属性类型语义说明使用场景起始版本
value()Class[]Bean类型集合类型安全的属性设置1.0
type()String[]Bean类名集合当类型不存在时的属性设置1.3
annotation()Class[]Bean声明注解类型集合当Bean标注了某注解类型时1.0
name()String[]Bean名称集合指定具体Bean名称集合1.0
search()SearchStrategy层次性应用上下文搜索策略三种应用上下文搜索策略:当前、父(祖先)、所有1.0

当放置在@Bean方法上时,目标类型默认为方法的返回类型,如下例所示:

@Configuration(proxyBeanMethods = false)
public class MyAutoConfiguration {

    @Bean
    @ConditionalOnMissingBean
    public MyService myService() { ... }

}

声明@Bean方法时,在方法的返回类型中提供尽可能多的类型信息。例如,如果bean的具体类实现了一个接口,那么bean方法的返回类型应该是具体类而不是接口。当使用Bean条件时,在@Bean方法中提供尽可能多的类型信息尤其重要,因为它们的计算只能依赖于方法签名中可用的类型信息。

从Spring Boot1.2.5开始,@ConditionalOnMissingBean引入了两个新的属性方法:

Class<?>[] ignored() default {};
String[] ignoredType() default {};

其中,属性方法ignored()用于Bean类型的忽略或排除,而ignoredType()则用于忽略或排除指定Bean名称。显然两者不会孤立存在需要配合如属性方法value()或annotation()提供细粒度忽略匹配。
与Class条件注解类似,@ConditionalOnBean和@ConditionalOnMissingBean同样采用单Condition实现处理语义对立条件注解。其Condition实现类为OnBeanCondition,该类同样扩展了抽象类SpringBootCondition:

@Order(Ordered.LOWEST_PRECEDENCE)
class OnBeanCondition extends SpringBootCondition implements ConfigurationCondition {

	@Override
	public ConditionOutcome getMatchOutcome(ConditionContext context,
			AnnotatedTypeMetadata metadata) {
		ConditionMessage matchMessage = ConditionMessage.empty();
		if (metadata.isAnnotated(ConditionalOnBean.class.getName())) {
			BeanSearchSpec spec = new BeanSearchSpec(context, metadata,
					ConditionalOnBean.class);
			MatchResult matchResult = getMatchingBeans(context, spec);
			if (!matchResult.isAllMatched()) {
				String reason = createOnBeanNoMatchReason(matchResult);
				return ConditionOutcome.noMatch(ConditionMessage
						.forCondition(ConditionalOnBean.class, spec).because(reason));
			}
			matchMessage = matchMessage.andCondition(ConditionalOnBean.class, spec)
					.found("bean", "beans")
					.items(Style.QUOTE, matchResult.getNamesOfAllMatches());
		}
		if (metadata.isAnnotated(ConditionalOnSingleCandidate.class.getName())) {
			...
		}
		if (metadata.isAnnotated(ConditionalOnMissingBean.class.getName())) {
			...
		}
		return ConditionOutcome.match(matchMessage);
	}
}

getMatchOutcome()方法的实现相当复杂,除了处理以上两个Bean条件注解,@ConditionalOnSingleCandidate也被纳入其中。这三个注解基本上处理逻辑类似,主要的匹配结果由getMatchingBeans()方法决定:

private MatchResult getMatchingBeans(ConditionContext context, BeanSearchSpec beans) {
		ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
		if (beans.getStrategy() == SearchStrategy.ANCESTORS) {
			BeanFactory parent = beanFactory.getParentBeanFactory();
			Assert.isInstanceOf(ConfigurableListableBeanFactory.class, parent,
					"Unable to use SearchStrategy.PARENTS");
			beanFactory = (ConfigurableListableBeanFactory) parent;
		}
		MatchResult matchResult = new MatchResult();
		boolean considerHierarchy = beans.getStrategy() != SearchStrategy.CURRENT;
		List<String> beansIgnoredByType = getNamesOfBeansIgnoredByType(
				beans.getIgnoredTypes(), beanFactory, context, considerHierarchy);
		for (String type : beans.getTypes()) {
			Collection<String> typeMatches = getBeanNamesForType(beanFactory, type,
					context.getClassLoader(), considerHierarchy);
			typeMatches.removeAll(beansIgnoredByType);
			if (typeMatches.isEmpty()) {
				matchResult.recordUnmatchedType(type);
			}
			else {
				matchResult.recordMatchedType(type, typeMatches);
			}
		}
		for (String annotation : beans.getAnnotations()) {
			...
		}
		for (String beanName : beans.getNames()) {
			...
		}
		return matchResult;
	}

方法参数BeanSearchSpec是Bean条件注解的包装对象,当SearchStrategy为SearchStrategy.ANCESTORS时,beanFactory切换为ParentConfigurableListableBeanFactory。其中getNamesOfBeansIgnoredByType()方法计算排除的Bean名称,后续被typeMatches排除,而核心的匹配逻辑在getBeanNamesForType()方法中完成:

private Collection<String> getBeanNamesForType(ListableBeanFactory beanFactory,
			String type, ClassLoader classLoader, boolean considerHierarchy)
			throws LinkageError {
		try {
			Set<String> result = new LinkedHashSet<>();
			collectBeanNamesForType(result, beanFactory,
					ClassUtils.forName(type, classLoader), considerHierarchy);
			return result;
		}
		catch (ClassNotFoundException | NoClassDefFoundError ex) {
			return Collections.emptySet();
		}
	}

而getBeanNamesForType()方法的计算结果由collectBeanNamesForType()方法完成。当前Bean搜索策略不为当前上下文搜索时,即执行boolean considerHierarchy = beans.getStrategy() != SearchStrategy.CURRENT;语句后,considerHierarchy 为true,collectBeanNamesForType方法将递归调用收集Bean名称集合。其中当前上下文Bean名称集合由BeanTypeRegistry#getNamesForType方法获取:

/**
返回与给定类型(包括子类)匹配的bean的名称,对于FactoryBeans,可以根据bean定义或
FactoryBean.getObjectType()的值来判断。将包括单例,但不会导致早期bean初始化。
**/
Set<String> getNamesForType(Class<?> type) {
	updateTypesIfNecessary();
	return this.beanTypes.entrySet().stream()
			.filter((entry) -> entry.getValue() != null
					&& type.isAssignableFrom(entry.getValue()))
			.map(Map.Entry::getKey)
			.collect(Collectors.toCollection(LinkedHashSet::new));
}
private void updateTypesIfNecessary() {
	if (this.lastBeanDefinitionCount != this.beanFactory.getBeanDefinitionCount()) {
		Iterator<String> names = this.beanFactory.getBeanNamesIterator();
		while (names.hasNext()) {
			String name = names.next();
			if (!this.beanTypes.containsKey(name)) {
				addBeanType(name);
			}
		}
		this.lastBeanDefinitionCount = this.beanFactory.getBeanDefinitionCount();
	}
}
private void addBeanType(String name) {
	if (this.beanFactory.containsSingleton(name)) {
		this.beanTypes.put(name, this.beanFactory.getType(name));
	}
	else if (!this.beanFactory.isAlias(name)) {
		addBeanTypeForNonAliasDefinition(name);
	}
}

根据getNamesForType()方法的调用链路,首先执行updateTypesIfNecessary()方法,其中addBeanType()方法将更新beanTypes的内容,其方法参数name作为Key,Value为BeanFactory#getType()方法的执行结果:

public Class<?> getType(String name) throws NoSuchBeanDefinitionException {
	String beanName = transformedBeanName(name);

	// Check manually registered singletons.
	Object beanInstance = getSingleton(beanName, false);
	if (beanInstance != null && beanInstance.getClass() != NullBean.class) {
		if (beanInstance instanceof FactoryBean && !BeanFactoryUtils.isFactoryDereference(name)) {
			return getTypeForFactoryBean((FactoryBean<?>) beanInstance);
		}
		else {
			return beanInstance.getClass();
		}
	}

	// No singleton instance found -> check bean definition.
	BeanFactory parentBeanFactory = getParentBeanFactory();
	if (parentBeanFactory != null && !containsBeanDefinition(beanName)) {
		// No bean definition found in this factory -> delegate to parent.
		return parentBeanFactory.getType(originalBeanName(name));
	}

	RootBeanDefinition mbd = getMergedLocalBeanDefinition(beanName);

	// Check decorated bean definition, if any: We assume it'll be easier
	// to determine the decorated bean's type than the proxy's type.
	BeanDefinitionHolder dbd = mbd.getDecoratedDefinition();
	if (dbd != null && !BeanFactoryUtils.isFactoryDereference(name)) {
		RootBeanDefinition tbd = getMergedBeanDefinition(dbd.getBeanName(), dbd.getBeanDefinition(), mbd);
		Class<?> targetClass = predictBeanType(dbd.getBeanName(), tbd);
		if (targetClass != null && !FactoryBean.class.isAssignableFrom(targetClass)) {
			return targetClass;
		}
	}

	Class<?> beanClass = predictBeanType(beanName, mbd);

	// Check bean class whether we're dealing with a FactoryBean.
	if (beanClass != null && FactoryBean.class.isAssignableFrom(beanClass)) {
		if (!BeanFactoryUtils.isFactoryDereference(name)) {
			// If it's a FactoryBean, we want to look at what it creates, not at the factory class.
			return getTypeForFactoryBean(beanName, mbd);
		}
		else {
			return beanClass;
		}
	}
	else {
		return (!BeanFactoryUtils.isFactoryDereference(name) ? beanClass : null);
	}
}

该方法首先执行getSingleton()方法,Bean条件装配OnBeanCondition实现了ConfigurationCondition接口:

@Order(Ordered.LOWEST_PRECEDENCE)
class OnBeanCondition extends SpringBootCondition implements ConfigurationCondition {
	@Override
	public ConfigurationPhase getConfigurationPhase() {
		return ConfigurationPhase.REGISTER_BEAN;
	}
}

ConfigurationPhase用于ConditionEvaluator#shouldSkip(AnnotatedTypeMetadata,ConfigurationPhase)方法评估,具体参考《@Conditional详解》

该实现指示ConditionEvaluator在注册Bean阶段(ConfigurationPhase.REGISTER_BEAN)进行评估。因此@ConditionalOnBean和@ConditionalOnMissingBean的javaDoc提示:

该条件只能匹配应用程序上下文迄今为止处理过的bean定义,因此,强烈建议仅在自动配置类上使用该条件。如果候选bean可能是由另一个自动配置创建的,请确保使用此条件的bean在之后运行

从而确保getSingleton()方法返回null,不参与Bean类型的计算。随后通过Bean名称获取RootBeanDefinition,再从RootBeanDefinition中计算Bean类型,于是与上面引用第一句话遥相呼应。

简言之,@ConditionalOnBean和@ConditionalOnMissingBean基于BeanDefinition进行名称或类型的匹配。

3、属性条件注解

@ConditionalOnProperty作为属性条件注解,其属性来源于Spring Environment。在Spring Framework场景中,Java系统属性和环境变量是典型的Spring Enviroment属性配置来源(PropertySource)。而在Spring Boot场景中,application.properties也是其中来源之一。

@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;
}
属性方法使用说明默认值多值属性起始版本
prefix()配置属性名称前缀“”1.1
value()name()的别名,参考name()空数组1.1
name()如果prefix()不为空,则完整配置属性名称为prefix()+name(),否则为name()的内容空数组1.2
havingValue()表示期望的配置属性值,并且禁止使用false“”1.2
matchIfMissing()用于判断当属性值不存在时是否匹配false1.2

通常@ConditionalOnProperty注解作为Spring Boot自动装配组件的属性条件开关,挡自动装配组件需要默认装配时,不妨将matchIfMissing()属性值调整为true,这样能减少Spring Boot应用接入的配置成本,尤其在Spring Boot Starter中效果明显。当应用需要关闭其组件装配时,可以通过属性配置进行调整。这种方式在Spring Boot内建的自动装配组件中尤为常见,比如JMX自动装配:

@Configuration
@ConditionalOnClass({ MBeanExporter.class })
@ConditionalOnProperty(prefix = "spring.jmx", name = "enabled", havingValue = "true", matchIfMissing = true)
public class JmxAutoConfiguration implements EnvironmentAware, BeanFactoryAware {
}

当开发人员配置属性spring.jmx.enabled=false时,JmxAutoConfiguration自动装配失效。

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

4、Resource条件注解

@ConditionalOnResource注释允许仅在存在特定资源时才包含配置。可以使用常用的Spring约定来指定资源,如下例所示:file:/home/user/test.dat。

@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Conditional(OnResourceCondition.class)
public @interface ConditionalOnResource {

	String[] resources() default {};
}

其中属性方法resources()指示只有资源必须存在时条件方可成立,因此结合OnResourceCondition实现加以分析:

@Order(Ordered.HIGHEST_PRECEDENCE + 20)
class OnResourceCondition extends SpringBootCondition {

	private final ResourceLoader defaultResourceLoader = new DefaultResourceLoader();

	@Override
	public ConditionOutcome getMatchOutcome(ConditionContext context,
			AnnotatedTypeMetadata metadata) {
		MultiValueMap<String, Object> attributes = metadata
				.getAllAnnotationAttributes(ConditionalOnResource.class.getName(), true);
		ResourceLoader loader = (context.getResourceLoader() != null
				? context.getResourceLoader() : this.defaultResourceLoader);
		List<String> locations = new ArrayList<>();
		collectValues(locations, attributes.get("resources"));
		Assert.isTrue(!locations.isEmpty(),
				"@ConditionalOnResource annotations must specify at "
						+ "least one resource location");
		List<String> missing = new ArrayList<>();
		for (String location : locations) {
			String resource = context.getEnvironment().resolvePlaceholders(location);
			if (!loader.getResource(resource).exists()) {
				missing.add(location);
			}
		}
		if (!missing.isEmpty()) {
			return ConditionOutcome.noMatch(ConditionMessage
					.forCondition(ConditionalOnResource.class)
					.didNotFind("resource", "resources").items(Style.QUOTE, missing));
		}
		return ConditionOutcome
				.match(ConditionMessage.forCondition(ConditionalOnResource.class)
						.found("location", "locations").items(locations));
	}

	private void collectValues(List<String> names, List<Object> values) {
		for (Object value : values) {
			for (Object item : (Object[]) value) {
				names.add((String) item);
			}
		}
	}

}

以上实现逻辑看似并不复杂,大致分为如下步骤:

  • 获取@ConditionalOnResource注解元属性信息attributes
  • 获取ResourceLoader对象loader
  • 解析@ConditionalOnResource#resources()属性中可能存在的占位符
  • 通过ResourceLoader对象loader逐一判断解析后的资源位置是否存在
    • 如果均已存在则说明成立
    • 否则,条件不成立

不过ResourceLoader loader = (context.getResourceLoader() != null ? context.getResourceLoader() : this.defaultResourceLoader);让问题复杂了。假设context.getResourceLoader()不返回null,那么返回对象具体是哪种ResourceLoader?已知Condition实现均被ConditionEvaluator.shouldSkip()方法调用,评估条件是否成立:

public ConditionEvaluator(@Nullable BeanDefinitionRegistry registry,
		@Nullable Environment environment, @Nullable ResourceLoader resourceLoader) {

	this.context = new ConditionContextImpl(registry, environment, resourceLoader);
}

其中Condition.matches(ConditionContext,AnnotatedTypeMetadata)方法的首参关联的ResourceLoader在内置类ConditionEvaluator.ConditionContextImpl的构造器中完成初始化:

public ConditionContextImpl(@Nullable BeanDefinitionRegistry registry,
	@Nullable Environment environment, @Nullable ResourceLoader resourceLoader) {

	this.registry = registry;
	this.beanFactory = deduceBeanFactory(registry);
	this.environment = (environment != null ? environment : deduceEnvironment(registry));
	this.resourceLoader = (resourceLoader != null ? resourceLoader : deduceResourceLoader(registry));
	this.classLoader = deduceClassLoader(resourceLoader, this.beanFactory);
}
	
private ResourceLoader deduceResourceLoader(@Nullable BeanDefinitionRegistry source) {
	if (source instanceof ResourceLoader) {
		return (ResourceLoader) source;
	}
	return new DefaultResourceLoader();
}

resourceLoader属性的来源有两个,一个是来自外部参数传递,另一个是获取BeanDefinitionRegistry 和ResourceLoader双接口实现对象(如果存在)。简言之,ConditionContext.getResourceLoader()的返回值来源于ConditionEvaluator构造参数ResourceLoader或BeanDefinitionRegistry。然而构造ConditionEvaluator实例的实现分布在四处(Spring Framework5.0.6):

  • AnnotatedBeanDefinitionReader构造器
public AnnotatedBeanDefinitionReader(BeanDefinitionRegistry registry, Environment environment) {
	Assert.notNull(registry, "BeanDefinitionRegistry must not be null");
	Assert.notNull(environment, "Environment must not be null");
	this.registry = registry;
	this.conditionEvaluator = new ConditionEvaluator(registry, environment, null);
	AnnotationConfigUtils.registerAnnotationConfigProcessors(this.registry);
}
  • AnnotatedBeanDefinitionReader#setEnvironment方法
public void setEnvironment(Environment environment) {
	this.conditionEvaluator = new ConditionEvaluator(this.registry, environment, null);
}

结合分析AnnotatedBeanDefinitionReader并没有在ConditionEvaluator构造中传递ResourceLoader实例。

  • ClassPathScanningCandidateComponentProvider#isConditionMatch方法
public class ClassPathScanningCandidateComponentProvider implements EnvironmentCapable, ResourceLoaderAware {

	@Override
	public void setResourceLoader(@Nullable ResourceLoader resourceLoader) {
		this.resourcePatternResolver = ResourcePatternUtils.getResourcePatternResolver(resourceLoader);
		this.metadataReaderFactory = new CachingMetadataReaderFactory(resourceLoader);
		this.componentsIndex = CandidateComponentsIndexLoader.loadIndex(this.resourcePatternResolver.getClassLoader());
	}

	private boolean isConditionMatch(MetadataReader metadataReader) {
		if (this.conditionEvaluator == null) {
			this.conditionEvaluator =
					new ConditionEvaluator(getRegistry(), this.environment, this.resourcePatternResolver);
		}
		return !this.conditionEvaluator.shouldSkip(metadataReader.getAnnotationMetadata());
	}
}

由于ClassPathScanningCandidateComponentProvider实现了ResourceLoaderAware接口,当其作为Spring Bean时,resourcePatternResolver字段将被Spring应用上下文初始化。如果是开发人员自定义实现,则该字段的赋值情况存在变数,总之resourcePatternResolver字段的状态无法确定。

  • .ConfigurationClassBeanDefinitionReader构造器
ConfigurationClassBeanDefinitionReader(BeanDefinitionRegistry registry, SourceExtractor sourceExtractor,
		ResourceLoader resourceLoader, Environment environment, BeanNameGenerator importBeanNameGenerator,
		ImportRegistry importRegistry) {

	this.registry = registry;
	this.sourceExtractor = sourceExtractor;
	this.resourceLoader = resourceLoader;
	this.environment = environment;
	this.importBeanNameGenerator = importBeanNameGenerator;
	this.importRegistry = importRegistry;
	this.conditionEvaluator = new ConditionEvaluator(registry, environment, resourceLoader);
}

在ConditionEvaluator的构造过程中,其所需的ResourceLoader对象来自ConfigurationClassBeanDefinitionReader构造参数,故需要在跟踪其构造地点,即ConfigurationClassPostProcessor#processConfigBeanDefinitions();

public void processConfigBeanDefinitions(BeanDefinitionRegistry registry) {
	...
	if (this.reader == null) {
		this.reader = new ConfigurationClassBeanDefinitionReader(
				registry, this.sourceExtractor, this.resourceLoader, this.environment,
				this.importBeanNameGenerator, parser.getImportRegistry());
	}
	...
}

@Override
public void setResourceLoader(ResourceLoader resourceLoader) {
	Assert.notNull(resourceLoader, "ResourceLoader must not be null");
	this.resourceLoader = resourceLoader;
	if (!this.setMetadataReaderFactoryCalled) {
		this.metadataReaderFactory = new CachingMetadataReaderFactory(resourceLoader);
	}
}

同样ConfigurationClassPostProcessor作为ResourceLoaderAware的实现类,其resourceLoader的初始化来源于覆盖方法setResourceLoader(ResourceLoader),又由于ConfigurationClassPostProcessor是Spring Framework默认的内建BeanDefinitionRegistryPostProcessor Bean组件:

public class AnnotationConfigUtils {
	public static Set<BeanDefinitionHolder> registerAnnotationConfigProcessors(
			BeanDefinitionRegistry registry, @Nullable Object source) {

		...
		Set<BeanDefinitionHolder> beanDefs = new LinkedHashSet<>(4);

		if (!registry.containsBeanDefinition(CONFIGURATION_ANNOTATION_PROCESSOR_BEAN_NAME)) {
			RootBeanDefinition def = new RootBeanDefinition(ConfigurationClassPostProcessor.class);
			def.setSource(source);
			beanDefs.add(registerPostProcessor(registry, def, CONFIGURATION_ANNOTATION_PROCESSOR_BEAN_NAME));
		}
		...
		return beanDefs;
	}
}

因此ConditionEvaluator关联的ResourceLoader来自Spring应用上下文。

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

	this.metadataReaderFactory = metadataReaderFactory;
	this.problemReporter = problemReporter;
	this.environment = environment;
	this.resourceLoader = resourceLoader;
	this.registry = registry;
	this.componentScanParser = new ComponentScanAnnotationParser(
			environment, resourceLoader, componentScanBeanNameGenerator, registry);
	this.conditionEvaluator = new ConditionEvaluator(registry, environment, resourceLoader);
}

同样ConditionEvaluator所需的ResourceLoader来自ConfigurationClassParser构造参数,并且该构造器同样被ConfigurationClassPostProcessor.processConfigBeanDefinitions()方法调用:

public class ConfigurationClassPostProcessor implements BeanDefinitionRegistryPostProcessor,
		PriorityOrdered, ResourceLoaderAware, BeanClassLoaderAware, EnvironmentAware {
	public void processConfigBeanDefinitions(BeanDefinitionRegistry registry) {
		...
		ConfigurationClassParser parser = new ConfigurationClassParser(
				this.metadataReaderFactory, this.problemReporter, this.environment,
				this.resourceLoader, this.componentScanBeanNameGenerator, registry);
		...
	}
}

因此ConditionEvaluator关联的ResourceLoader同样来自Spring应用上下文。

综上所述,默认情况下ConditionContext.getResourceLoader()的返回值存在两种可能:

  • 来源于Spring应用上下文ResourceLoaderAware回调
  • 为null

当OnResourceConditional.getMatchOutcome()方法在执行ResourceLoader loader = (context.getResourceLoader() != null ? context.getResourceLoader() : this.defaultResourceLoader);语句时,loader不是来源于Spring应用上下文ResourceLoaderAware回调,就是DefaultResourceLoader类型的defaultResourceLoader字段。那么ResourceLoaderAware回调的内容需要具体明确。

凡是任意实现ResourceLoaderAware接口的Bean,在其生命周期工程中会被Spring应用上下文设置ResourceLoader对象,即在Bean初始化之前,执行ResourceLoaderAware回调工作。默认情况下,Spring Framework采用的是标准BeanPostProcessor接口的实现ApplicationContextAwareProcessor:

class ApplicationContextAwareProcessor implements BeanPostProcessor {

	private final ConfigurableApplicationContext applicationContext;

	public ApplicationContextAwareProcessor(ConfigurableApplicationContext applicationContext) {
		this.applicationContext = applicationContext;
		...
	}

	@Override
	@Nullable
	public Object postProcessBeforeInitialization(final Object bean, String beanName) throws BeansException {
		...
		if (acc != null) {
			AccessController.doPrivileged((PrivilegedAction<Object>) () -> {
				invokeAwareInterfaces(bean);
				return null;
			}, acc);
		}
		else {
			invokeAwareInterfaces(bean);
		}

		return bean;
	}

	private void invokeAwareInterfaces(Object bean) {
		if (bean instanceof Aware) {
			...
			if (bean instanceof ResourceLoaderAware) {
				((ResourceLoaderAware) bean).setResourceLoader(this.applicationContext);
			}
		}
	}

	@Override
	public Object postProcessAfterInitialization(Object bean, String beanName) {
		return bean;
	}

}

其中生命周期方法postProcessBeforeInitialization(Object, String)委派给invokeAwareInterfaces()方法执行ResourceLoaderAware接口回调。不过该方法传递的ResourceLoader对象却是构造参数ConfigurableApplicationContext实例。换言之ConfigurableApplicationContext是ResourceLoader子接口。再跟踪ApplicationContextAwareProcessor构造位置,即AbstractApplicationContext.prepareBeanFactory方法:

protected void prepareBeanFactory(ConfigurableListableBeanFactory beanFactory) {
	...
	// Configure the bean factory with context callbacks.
	beanFactory.addBeanPostProcessor(new ApplicationContextAwareProcessor(this));
	...
}

ApplicationContextAwareProcessor构造参数恰好是当前Spring应用上下文,同时prepareBeanFactory方法也处于Spring应用上下文启动过程中(AbstractApplicationContext#refresh)的准备ConfigurableListableBeanFactory阶段。因此通常情况下,Spring Framework的ConditionContext.getResourceLoader()方法所返回的ResourceLoader对象即当前Spring应用上下文实例。由于Spring Framework内建多种Spring应用上下文的实现,目前还无法对ResourceLoader的具体类型做出定论,仍需分析ResourceLoader在Spring Framework内部的类层级关系,具体参考《ResourceLoader》,这里直接给出结论,在语句执行ResourceLoader loader = (context.getResourceLoader() != null ? context.getResourceLoader() : this.defaultResourceLoader);时,默认情况下,loader的行为与普通的DefaultResourceLoader实例无差别。

5、Web应用条件注解

@ConditionalOnWebApplication和@ConditionalOnNotWebApplication注释允许根据应用程序是否为“web应用程序”包含配置类。

@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Conditional(OnWebApplicationCondition.class)
public @interface ConditionalOnWebApplication {
	
	Type type() default Type.ANY;

	enum Type {
		/**
		 * Any web application will match.
		 */
		ANY,
		/**
		 * Only servlet-based web application will match.
		 */
		SERVLET,
		/**
		 * Only reactive-based web application will match.
		 */
		REACTIVE
	}
}

@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Conditional(OnWebApplicationCondition.class)
public @interface ConditionalOnNotWebApplication {

}

根据注解的声明可知,OnWebApplicationCondition作为这两个注解的实现类。

@Order(Ordered.HIGHEST_PRECEDENCE + 20)
class OnWebApplicationCondition extends SpringBootCondition {

	@Override
	public ConditionOutcome getMatchOutcome(ConditionContext context,
			AnnotatedTypeMetadata metadata) {
		boolean required = metadata
				.isAnnotated(ConditionalOnWebApplication.class.getName());
		ConditionOutcome outcome = isWebApplication(context, metadata, required);
		if (required && !outcome.isMatch()) {
			return ConditionOutcome.noMatch(outcome.getConditionMessage());
		}
		if (!required && outcome.isMatch()) {
			return ConditionOutcome.noMatch(outcome.getConditionMessage());
		}
		return ConditionOutcome.match(outcome.getConditionMessage());
	}
}

当执行getMatchOutcome()方法时,首先判断required的情况,当required为true时说明当前Configuration Class添加了@ConditionalOnWebApplication 的声明。由于ConditionalOnNotWebApplication 是非public类,所以required为false时说明@ConditionalOnNotWebApplication 标注在Configuration Class上。接下来调用isWebApplication()方法判断当前Spring应用是否为Web应用:

private ConditionOutcome isWebApplication(ConditionContext context, AnnotatedTypeMetadata metadata,
		boolean required) {
	switch (deduceType(metadata)) {
	case SERVLET:
		return isServletWebApplication(context);
	case REACTIVE:
		return isReactiveWebApplication(context);
	default:
		return isAnyWebApplication(context, required);
	}
}

首先根据deduceType(metadata)方法推断应用类型Type,根据不同Type使用不同方法判断是否满足具体类型。

以isServletWebApplication为例,isReactiveWebApplication判断逻辑类似:

private ConditionOutcome isServletWebApplication(ConditionContext context) {
	ConditionMessage.Builder message = ConditionMessage.forCondition("");
	if (!ClassNameFilter.isPresent(SERVLET_WEB_APPLICATION_CLASS, context.getClassLoader())) {
		return ConditionOutcome.noMatch(message.didNotFind("servlet web application classes").atAll());
	}
	if (context.getBeanFactory() != null) {
		String[] scopes = context.getBeanFactory().getRegisteredScopeNames();
		if (ObjectUtils.containsElement(scopes, "session")) {
			return ConditionOutcome.match(message.foundExactly("'session' scope"));
		}
	}
	if (context.getEnvironment() instanceof ConfigurableWebEnvironment) {
		return ConditionOutcome.match(message.foundExactly("ConfigurableWebEnvironment"));
	}
	if (context.getResourceLoader() instanceof WebApplicationContext) {
		return ConditionOutcome.match(message.foundExactly("WebApplicationContext"));
	}
	return ConditionOutcome.noMatch(message.because("not a servlet web application"));
}

该方法首先判断org.springframework.web.context.support.GenericWebApplicationContext是否存在于当前Class Path中,如果不存在则说明并非Servlet Web场景。随后判断上下文中是否存在scope为"session"的Bean,如果存在则说明当前应用属于Servlet Web应用。后续两则判断分别判断Spring应用上下文所关联Environment是否为ConfigurableWebEnvironment,以及ResourceLoader是否为WebApplicationContext。因为AbstractApplicationContext实例是ResourceLoader对象,因此当Spring应用上下文属于WebApplicationContext时,说明条件成立,即当前Spring应用属于Servlet Web类型。

当SpringApplicationBuilder.web(WebApplicationType.NONE)执行后,当前应用被显示地设置为非Web应用,此时的OnWebApplicationCondition是无法成立。原因在于Spring应用上下文既非WebApplicationContext类型也不是ReactiveWebApplicationContext实例,而是普通的AnnotationConfigApplicationContext。

6、Spring表达式条件注解

@ConditionalOnExpression注解允许基于SpEL表达式的结果包含配置类。Spring表达式条件注解声明非常简单,其中属性方法value()用于评估表达式的真伪:

@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.TYPE, ElementType.METHOD })
@Documented
@Conditional(OnExpressionCondition.class)
public @interface ConditionalOnExpression {

	String value() default "true";
}

其条件判断实现OnExpressionCondition:

@Order(Ordered.LOWEST_PRECEDENCE - 20)
class OnExpressionCondition extends SpringBootCondition {

	@Override
	public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) {
		String expression = (String) metadata.getAnnotationAttributes(ConditionalOnExpression.class.getName())
				.get("value");
		expression = wrapIfNecessary(expression);
		ConditionMessage.Builder messageBuilder = ConditionMessage.forCondition(ConditionalOnExpression.class,
				"(" + expression + ")");
		expression = context.getEnvironment().resolvePlaceholders(expression);
		ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
		if (beanFactory != null) {
			boolean result = evaluateExpression(beanFactory, expression);
			return new ConditionOutcome(result, messageBuilder.resultedIn(result));
		}
		return ConditionOutcome.noMatch(messageBuilder.because("no BeanFactory available."));
	}

	private Boolean evaluateExpression(ConfigurableListableBeanFactory beanFactory, String expression) {
		BeanExpressionResolver resolver = beanFactory.getBeanExpressionResolver();
		if (resolver == null) {
			resolver = new StandardBeanExpressionResolver();
		}
		BeanExpressionContext expressionContext = new BeanExpressionContext(beanFactory, null);
		Object result = resolver.evaluate(expression, expressionContext);
		return (result != null && (boolean) result);
	}

	/**
	 * Allow user to provide bare expression with no '#{}' wrapper.
	 * @param expression source expression
	 * @return wrapped expression
	 */
	private String wrapIfNecessary(String expression) {
		if (!expression.startsWith("#{")) {
			return "#{" + expression + "}";
		}
		return expression;
	}

}

首先从注解@ConditionalOnExpression元信息中获取value()属性作为表达式内容expression。如果expression未包含“#{}”时,将其补充。然后随即被Spring应用上下文关联的Environment进行占位符处理,然后BeanExpressionResolver对象评估表达式的真伪,当评估结果为true时条件成立。需要关注的是当Spring应用上下文中的ConfigurableListableBeanFactory 所关联BeanExpressionResolver行为调整后,可能会影响评估的结果。

与@ConditionalOnProperty相比,@ConditionalOnProperty在配置属性的语义上要优于@ConditionalOnExpression,不过它要表达多组配置属性则较为繁琐,然而@ConditionalOnExpression就能轻松解决,比如EndpointMBeanExportAutoConfiguration(spring-boot-actuator-1.2.8.RELEASE.jar)

@Configuration
@ConditionalOnExpression("${endpoints.jmx.enabled:true} && ${spring.jmx.enabled:true}")
@AutoConfigureAfter({EndpointAutoConfiguration.class, JmxAutoConfiguration.class})
@EnableConfigurationProperties({EndpointMBeanExportProperties.class})
public class EndpointMBeanExportAutoConfiguration {
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值