11

Spring 创建Bean 时是怎样判断条件的?

 3 years ago
source link: http://developer.51cto.com/art/202102/644348.htm
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

我们在 Spring/ Spring Boot Starter 或者一些框架的源码里经常能看到类似如下的注解声明,可能作用在类上,也可能在某个方法上:

@ConditionalOnProperty(name = "spring.cloud.refresh.enabled", matchIfMissing = true)

@ConditionalOnProperty(prefix = "management.metrics.export.atlas", name = "enabled", havingValue = "true",

matchIfMissing = true)

我们一眼都能看出来,这是来「谈条件」的。需要满足某个属性存在,或者属性值是xx这一类的。

对于属性的匹配,是会在 Environment 里查找是否包含当前需要的属性,如果没指定 havingValue 的话,那需要同时属性的值不为「false」这个字符串,其它的东西都视为true。

今天的这篇做为铺垫,先来描述一下注解的工作原理,后面一篇我会写写与此有关的一个有趣的案例。

工作原理

浓缩版

在SpringBoot 启动过程中,会扫描当前依赖里的 @Configuration,然后遍历的过程中会判断其中哪些是要讲条件的。对于讲条件的这些,会判断

shouldSkip ,这里的是否跳过,会根据注解作用在类上,方法上,转向不同的Metadata,提取对应的实现类,但本质上还是通过 resolver 去Environment 里找找这个属性在不在,不在跳过,在的话是否值匹配。从而决定 Confirutaion 是否生效。

源码版

我们知道 Spring 启动的过程,也是创建和初始化Bean 的过程,在这个过程中,会先拿到BeanNames,并一个个的去创建和初始化。

此时,对于Configuration,是通过BeanPostProcessor的方式来处理的.

public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {

int registryId = System.identityHashCode(registry);

this.registriesPostProcessed.add(registryId);

processConfigBeanDefinitions(registry);// 对,是这里

}

部分调用栈如下:

java.lang.Thread.State: RUNNABLE

at org.springframework.boot.autoconfigure.condition.OnPropertyCondition.getMatchOutcome(OnPropertyCondition.java:65)

at org.springframework.boot.autoconfigure.condition.SpringBootCondition.matches(SpringBootCondition.java:47)

at org.springframework.context.annotation.ConditionEvaluator.shouldSkip(ConditionEvaluator.java:108)

at org.springframework.context.annotation.ConfigurationClassBeanDefinitionReader.loadBeanDefinitionsForBeanMethod(ConfigurationClassBeanDefinitionReader.java:181)

at org.springframework.context.annotation.ConfigurationClassBeanDefinitionReader.loadBeanDefinitionsForConfigurationClass(ConfigurationClassBeanDefinitionReader.java:142)

at org.springframework.context.annotation.ConfigurationClassBeanDefinitionReader.loadBeanDefinitions(ConfigurationClassBeanDefinitionReader.java:118)

at org.springframework.context.annotation.ConfigurationClassPostProcessor.processConfigBeanDefinitions(ConfigurationClassPostProcessor.java:328)

at org.springframework.context.annotation.ConfigurationClassPostProcessor.postProcessBeanDefinitionRegistry(ConfigurationClassPostProcessor.java:233)

这里对于 Class 和 Method,都在该方法中,处理入口不一样,传入的Meta也有所区别

/**

* Build and validate a configuration model based on the registry of

* {@link Configuration} classes.

*/

public void processConfigBeanDefinitions(BeanDefinitionRegistry registry) {

List configCandidates = new ArrayList<>();

String[] candidateNames = registry.getBeanDefinitionNames();

for (String beanName : candidateNames) {

BeanDefinition beanDef = registry.getBeanDefinition(beanName);

if (ConfigurationClassUtils.isFullConfigurationClass(beanDef) ||

ConfigurationClassUtils.isLiteConfigurationClass(beanDef)) {

}

else if (ConfigurationClassUtils.checkConfigurationClassCandidate(beanDef, this.metadataReaderFactory)) {

configCandidates.add(new BeanDefinitionHolder(beanDef, beanName));

}

}

// Return immediately if no @Configuration classes were found

if (configCandidates.isEmpty()) {

return;

}

// Parse each @Configuration class

ConfigurationClassParser parser = new ConfigurationClassParser(

this.metadataReaderFactory, this.problemReporter, this.environment,

this.resourceLoader, this.componentScanBeanNameGenerator, registry);

Set candidates = new LinkedHashSet<>(configCandidates);

Set alreadyParsed = new HashSet<>(configCandidates.size());

do {

parser.parse(candidates); // 这里处理class

parser.validate();

Set configClasses = new LinkedHashSet<>(parser.getConfigurationClasses());

configClasses.removeAll(alreadyParsed);

// Read the model and create bean definitions based on its content

if (this.reader == null) {

this.reader = new ConfigurationClassBeanDefinitionReader(

registry, this.sourceExtractor, this.resourceLoader, this.environment,

this.importBeanNameGenerator, parser.getImportRegistry());

}

this.reader.loadBeanDefinitions(configClasses); // 这里处理Method

alreadyParsed.addAll(configClasses);

while (!candidates.isEmpty());

}

里面的逻辑,则都是在判断这些Condition 是否match,重点看这一行

condition.matches(this.context, metadata)

for (Condition condition : conditions) {

ConfigurationPhase requiredPhase = null;

if (condition instanceof ConfigurationCondition) {

requiredPhase = ((ConfigurationCondition) condition).getConfigurationPhase();

}

if ((requiredPhase == null || requiredPhase == phase) && !condition.matches(this.context, metadata)) {

return true;

}

}

图片

通过观察 Condition 这个接口你也能发现,和我们上面说的一样,这里不同的处理metadata是不同的。

在 SpringBoot 里,ConditionalOnProperty 的 Condition 实现,运用了一个模板方法模式, SpringBootCondition 做为模板,再调用各子类的实现方法

public final boolean matches(ConditionContext context,

AnnotatedTypeMetadata metadata) {

String classOrMethodName = getClassOrMethodName(metadata);

ConditionOutcome outcome = getMatchOutcome(context, metadata);// 这里交给了抽象方法

recordEvaluation(context, classOrMethodName, outcome);

return outcome.isMatch();

}

来看子类的实现

public ConditionOutcome getMatchOutcome(ConditionContext context,

AnnotatedTypeMetadata metadata) {

List allAnnotationAttributes = annotationAttributesFromMultiValueMap(

metadata.getAllAnnotationAttributes(

ConditionalOnProperty.class.getName()));

List noMatch = new ArrayList<>();

List 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));

}

其中determineOutcome,来选择是否有满足条件的时候,就是从Environment里读属性的过程

private ConditionOutcome determineOutcome(AnnotationAttributes annotationAttributes,

PropertyResolver resolver) {

Spec spec = new Spec(annotationAttributes);

List missingProperties = new ArrayList<>();

List nonMatchingProperties = new ArrayList<>();

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"));

}

有了这个判断,对于 OnClass 之类的,你也能猜个八九不离十。

同样会有一个子类的实现

图片

只不过判断的从属性,换成了在classloader里查找已加载的类。

图片


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK