标准@Configuration类是自动装配的底层实现,并且搭配Spring Framework @Conditional注解,使其能合理地在不同环境中运作。在《SpringBoot自动装配》中讨论过,@EnableAutoConfiguration利用AutoConfigurationImportFilter实现类OnClassCondition等过滤非法自动装配Class,从而间接地接触条件注解@ConditionalOnClass。
条件注解@ConditionalOnClass采用元标注@Conditional(OnClassCondition.class)的方式定义,所以它包含@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)方法评估
该实现指示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() | 用于判断当属性值不存在时是否匹配 | false | 否 | 1.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 {
}