Spring 創(chuàng)建Bean 時(shí)是怎樣判斷條件的?
我們在 Spring/ Spring Boot Starter 或者一些框架的源碼里經(jīng)常能看到類似如下的注解聲明,可能作用在類上,也可能在某個(gè)方法上:
- @ConditionalOnProperty(name = "spring.cloud.refresh.enabled", matchIfMissing = true)
- @ConditionalOnProperty(prefix = "management.metrics.export.atlas", name = "enabled", havingValue = "true",
- matchIfMissing = true)
我們一眼都能看出來,這是來「談條件」的。需要滿足某個(gè)屬性存在,或者屬性值是xx這一類的。
對于屬性的匹配,是會(huì)在 Environment 里查找是否包含當(dāng)前需要的屬性,如果沒指定 havingValue 的話,那需要同時(shí)屬性的值不為「false」這個(gè)字符串,其它的東西都視為true。
今天的這篇做為鋪墊,先來描述一下注解的工作原理,后面一篇我會(huì)寫寫與此有關(guān)的一個(gè)有趣的案例。
工作原理
濃縮版
在SpringBoot 啟動(dòng)過程中,會(huì)掃描當(dāng)前依賴?yán)锏? @Configuration,然后遍歷的過程中會(huì)判斷其中哪些是要講條件的。對于講條件的這些,會(huì)判斷
shouldSkip ,這里的是否跳過,會(huì)根據(jù)注解作用在類上,方法上,轉(zhuǎn)向不同的Metadata,提取對應(yīng)的實(shí)現(xiàn)類,但本質(zhì)上還是通過 resolver 去Environment 里找找這個(gè)屬性在不在,不在跳過,在的話是否值匹配。從而決定 Confirutaion 是否生效。
源碼版
我們知道 Spring 啟動(dòng)的過程,也是創(chuàng)建和初始化Bean 的過程,在這個(gè)過程中,會(huì)先拿到BeanNames,并一個(gè)個(gè)的去創(chuàng)建和初始化。
此時(shí),對于Configuration,是通過BeanPostProcessor的方式來處理的.
- public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
- int registryId = System.identityHashCode(registry);
- this.registriesPostProcessed.add(registryId);
- processConfigBeanDefinitions(registry);// 對,是這里
- }
部分調(diào)用棧如下:
- 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也有所區(qū)別
- /**
- * Build and validate a configuration model based on the registry of
- * {@link Configuration} classes.
- */
- public void processConfigBeanDefinitions(BeanDefinitionRegistry registry) {
- List<BeanDefinitionHolder> 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<BeanDefinitionHolder> candidates = new LinkedHashSet<>(configCandidates);
- Set<ConfigurationClass> alreadyParsed = new HashSet<>(configCandidates.size());
- do {
- parser.parse(candidates); // 這里處理class
- parser.validate();
- Set<ConfigurationClass> 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,重點(diǎn)看這一行
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 這個(gè)接口你也能發(fā)現(xiàn),和我們上面說的一樣,這里不同的處理metadata是不同的。
在 SpringBoot 里,ConditionalOnProperty 的 Condition 實(shí)現(xiàn),運(yùn)用了一個(gè)模板方法模式, SpringBootCondition 做為模板,再調(diào)用各子類的實(shí)現(xiàn)方法。
- public final boolean matches(ConditionContext context,
- AnnotatedTypeMetadata metadata) {
- String classOrMethodName = getClassOrMethodName(metadata);
- ConditionOutcome outcome = getMatchOutcome(context, metadata);// 這里交給了抽象方法
- recordEvaluation(context, classOrMethodName, outcome);
- return outcome.isMatch();
- }
來看子類的實(shí)現(xiàn)
- private ConditionOutcome determineOutcome(AnnotationAttributes annotationAttributes,
- PropertyResolver resolver) {
- Spec spec = new Spec(annotationAttributes);
- List<String> missingProperties = new ArrayList<>();
- List<String> 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"));
- }
有了這個(gè)判斷,對于 OnClass 之類的,你也能猜個(gè)八九不離十。
同樣會(huì)有一個(gè)子類的實(shí)現(xiàn)
只不過判斷的從屬性,換成了在classloader里查找已加載的類。
本文轉(zhuǎn)載自微信公眾號(hào)「Tomcat那些事兒」,可以通過以下二維碼關(guān)注。轉(zhuǎn)載本文請聯(lián)系Tomcat那些事兒公眾號(hào)。