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

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

[[380145]]

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

  1. @ConditionalOnProperty(name = "spring.cloud.refresh.enabled", matchIfMissing = true
  2.  
  3. @ConditionalOnProperty(prefix = "management.metrics.export.atlas"name = "enabled", havingValue = "true"
  4.     matchIfMissing = true

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

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

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

工作原理

浓缩版

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

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

源码版

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

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

  1. public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) { 
  2.     int registryId = System.identityHashCode(registry); 
  3.     this.registriesPostProcessed.add(registryId); 
  4.     processConfigBeanDefinitions(registry);// 对,是这里 
  5.   } 

部分调用栈如下:

  1. java.lang.Thread.State: RUNNABLE 
  2.     at org.springframework.boot.autoconfigure.condition.OnPropertyCondition.getMatchOutcome(OnPropertyCondition.java:65) 
  3.     at org.springframework.boot.autoconfigure.condition.SpringBootCondition.matches(SpringBootCondition.java:47) 
  4.     at org.springframework.context.annotation.ConditionEvaluator.shouldSkip(ConditionEvaluator.java:108) 
  5.     at org.springframework.context.annotation.ConfigurationClassBeanDefinitionReader.loadBeanDefinitionsForBeanMethod(ConfigurationClassBeanDefinitionReader.java:181) 
  6.     at org.springframework.context.annotation.ConfigurationClassBeanDefinitionReader.loadBeanDefinitionsForConfigurationClass(ConfigurationClassBeanDefinitionReader.java:142) 
  7.     at org.springframework.context.annotation.ConfigurationClassBeanDefinitionReader.loadBeanDefinitions(ConfigurationClassBeanDefinitionReader.java:118) 
  8.     at org.springframework.context.annotation.ConfigurationClassPostProcessor.processConfigBeanDefinitions(ConfigurationClassPostProcessor.java:328) 
  9.     at org.springframework.context.annotation.ConfigurationClassPostProcessor.postProcessBeanDefinitionRegistry(ConfigurationClassPostProcessor.java:233) 

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

  1. /** 
  2.    * Build and validate a configuration model based on the registry of 
  3.    * {@link Configuration} classes. 
  4.    */ 
  5.   public void processConfigBeanDefinitions(BeanDefinitionRegistry registry) { 
  6.     List<BeanDefinitionHolder> configCandidates = new ArrayList<>(); 
  7.     String[] candidateNames = registry.getBeanDefinitionNames(); 
  8.  
  9.     for (String beanName : candidateNames) { 
  10.       BeanDefinition beanDef = registry.getBeanDefinition(beanName); 
  11.       if (ConfigurationClassUtils.isFullConfigurationClass(beanDef) || 
  12.           ConfigurationClassUtils.isLiteConfigurationClass(beanDef)) { 
  13.       } 
  14.       else if (ConfigurationClassUtils.checkConfigurationClassCandidate(beanDef, this.metadataReaderFactory)) { 
  15.         configCandidates.add(new BeanDefinitionHolder(beanDef, beanName)); 
  16.       } 
  17.     } 
  18.     // Return immediately if no @Configuration classes were found 
  19.     if (configCandidates.isEmpty()) { 
  20.       return
  21.     } 
  22.  
  23.     // Parse each @Configuration class 
  24.     ConfigurationClassParser parser = new ConfigurationClassParser( 
  25.         this.metadataReaderFactory, this.problemReporter, this.environment, 
  26.         this.resourceLoader, this.componentScanBeanNameGenerator, registry); 
  27.  
  28.     Set<BeanDefinitionHolder> candidates = new LinkedHashSet<>(configCandidates); 
  29.     Set<ConfigurationClass> alreadyParsed = new HashSet<>(configCandidates.size()); 
  30.     do { 
  31.       parser.parse(candidates); // 这里处理class 
  32.       parser.validate(); 
  33.  
  34.       Set<ConfigurationClass> configClasses = new LinkedHashSet<>(parser.getConfigurationClasses()); 
  35.       configClasses.removeAll(alreadyParsed); 
  36.  
  37.       // Read the model and create bean definitions based on its content 
  38.       if (this.reader == null) { 
  39.         this.reader = new ConfigurationClassBeanDefinitionReader( 
  40.             registry, this.sourceExtractor, this.resourceLoader, this.environment, 
  41.             this.importBeanNameGenerator, parser.getImportRegistry()); 
  42.       } 
  43.       this.reader.loadBeanDefinitions(configClasses); // 这里处理Method 
  44.       alreadyParsed.addAll(configClasses); 
  45.     while (!candidates.isEmpty()); 
  46.   } 

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

condition.matches(this.context, metadata)

  1. for (Condition condition : conditions) { 
  2.       ConfigurationPhase requiredPhase = null
  3.       if (condition instanceof ConfigurationCondition) { 
  4.         requiredPhase = ((ConfigurationCondition) condition).getConfigurationPhase(); 
  5.       } 
  6.       if ((requiredPhase == null || requiredPhase == phase) && !condition.matches(this.context, metadata)) { 
  7.         return true
  8.       } 
  9.     } 

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

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

  1. public final boolean matches(ConditionContext context, 
  2.       AnnotatedTypeMetadata metadata) { 
  3.     String classOrMethodName = getClassOrMethodName(metadata); 
  4.       ConditionOutcome outcome = getMatchOutcome(context, metadata);// 这里交给了抽象方法 
  5.       recordEvaluation(context, classOrMethodName, outcome); 
  6.       return outcome.isMatch(); 
  7.   } 

来看子类的实现

  1. private ConditionOutcome determineOutcome(AnnotationAttributes annotationAttributes, 
  2.       PropertyResolver resolver) { 
  3.     Spec spec = new Spec(annotationAttributes); 
  4.     List<String> missingProperties = new ArrayList<>(); 
  5.     List<String> nonMatchingProperties = new ArrayList<>(); 
  6.     spec.collectProperties(resolver, missingProperties, nonMatchingProperties); 
  7.     if (!missingProperties.isEmpty()) { 
  8.       return ConditionOutcome.noMatch( 
  9.           ConditionMessage.forCondition(ConditionalOnProperty.class, spec) 
  10.               .didNotFind("property""properties"
  11.               .items(Style.QUOTE, missingProperties)); 
  12.     } 
  13.     if (!nonMatchingProperties.isEmpty()) { 
  14.       return ConditionOutcome.noMatch( 
  15.           ConditionMessage.forCondition(ConditionalOnProperty.class, spec) 
  16.               .found("different value in property"
  17.                   "different value in properties"
  18.               .items(Style.QUOTE, nonMatchingProperties)); 
  19.     } 
  20.     return ConditionOutcome.match(ConditionMessage 
  21.         .forCondition(ConditionalOnProperty.class, spec).because("matched")); 
  22.   } 

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

同样会有一个子类的实现

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

本文转载自微信公众号「Tomcat那些事儿」,可以通过以下二维码关注。转载本文请联系Tomcat那些事儿公众号。

 

责任编辑:武晓燕 来源: Tomcat那些事儿
相关推荐

2021-03-08 08:40:25

Spring Bean 创建单例对象

2021-05-11 07:42:59

BeanSpring属性

2011-04-12 09:53:32

Spring

2023-10-07 08:35:07

依赖注入Spring

2021-04-30 20:25:20

Spring MVCJava代码

2024-02-23 10:33:34

SpringBean容器

2009-06-17 17:04:37

BeanFactorySpring

2023-09-28 08:15:05

SpringBean加载

2011-03-18 09:27:00

Spring

2024-01-23 08:47:13

BeanSpring加载方式

2022-05-27 08:25:55

容器Spring

2010-03-24 15:40:39

网管运维管理摩卡软件

2020-10-27 07:34:41

基站手机蜂窝网络

2022-07-20 07:45:52

POJOJavaSpring IoC

2024-05-29 08:19:03

2023-01-13 07:41:20

BeanSpring容器

2022-10-19 23:28:55

Spring生命周期Bean

2022-11-23 14:13:03

Ansible条件判断

2013-08-19 16:17:48

CIO

2009-09-02 20:18:17

域名劫持域名安全
点赞
收藏

51CTO技术栈公众号