重读Spring系列(4)-Conditional注入Bean的判断

本文深入探讨了Spring Boot中基于@Conditional的条件注入机制,包括@ConditionalOnBean、@ConditionalOnMissingBean等注解的使用,以及它们在Spring Boot自动配置中的作用。文章通过示例代码详细解释了条件判断的过程,并分析了OnBeanCondition的实现逻辑,揭示了Spring Boot如何根据条件决定是否加载特定的Bean。同时,文章还介绍了AutoConfigurationImportFilter在自动配置过程中的作用,展示了如何通过条件判断控制自动配置类的导入。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

这一篇我们要梳理的主要是关于注入Bean的条件判断,只有满足对应的条件才注入对应的BeanDefinition。关于注入的条件判断目前我们知道是有3个地方,其中两个都是使用的ConditionEvaluator处理,另外一个是我们的上一篇提到的AutoConfigurationImportFilter。下面我们就按顺序来依次梳理。建议看下前两篇

一、@Conditional

1、结构

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

   Class<? extends Condition>[] value();

}

​ 首先我们需要了解这个注解,这个注解就是用来条件判断的,只有满足条件(通过value导入的继承Condition接口的类来判断),才导入这个Bean。下面我们通过一个spring源码中的demo来看下这个注解的具体配套使用。

2、demo1

@Configuration
@Conditional(NoBeanOneCondition.class)
static class BeanTwoConfiguration {

   @Bean
   public ExampleBean bean2() {
      return new ExampleBean();
   }
}

​ 这个配置类BeanTwoConfiguration,我们可以看到其本身是要注入bean2()(ExampleBean)的,但其加了一个@Conditional(NoBeanOneCondition.class),就是说这个配置类能不能注入,需要看NoBeanOneCondition的判断。

static class NoBeanOneCondition implements Condition {

   @Override
   public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
      return !context.getBeanFactory().containsBeanDefinition("bean1");
   }
}

​ 这个判断就是看当前Bean工厂中有没有名称为bean1BeanDefiniton,如果没有就返回true,这样就会注入BeanTwoConfiguration以此来注入其中的ExampleBean

@Test
public void conditionalOnMissingBeanNoMatch() throws Exception {
   AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
   ctx.register(BeanTwoConfiguration.class);
   ctx.refresh();
   assertFalse(ctx.containsBean("bean1"));
   assertTrue(ctx.containsBean("bean2"));
   assertTrue(ctx.containsBean("configurationClassWithConditionTests.BeanTwoConfiguration"));
}

​ 这个就是测试类,可以看到其只ctx.register(BeanTwoConfiguration.class),即只注入了BeanTwoConfiguration,就还没有bean1,所以这里就会成功注入bean2(ExampleBean)。

3、demo2

在上面的demo1基础上,我们来看下另一种情况:

@Configuration
static class BeanOneConfiguration {

   @Bean
   public ExampleBean bean1() {
      return new ExampleBean();
   }
}

​ 这个BeanOneConfiguration就是注解注入bean1,然后看测试类:

@Test
public void conditionalOnMissingBeanMatch() throws Exception {
   AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
   ctx.register(BeanOneConfiguration.class, BeanTwoConfiguration.class);
   ctx.refresh();
   assertTrue(ctx.containsBean("bean1"));
   assertFalse(ctx.containsBean("bean2"));
   assertFalse(ctx.containsBean("configurationClassWithConditionTests.BeanTwoConfiguration"));
}

​ 这样就没有注入configurationClassWithConditionTests.BeanTwoConfiguration了,所以也不会注入bean2

二、@Conditional在SpringBoot中的使用

上面这个是简单使用。下面我们就来其在SpringBoot中的使用,这个其实我们上一篇文章有简单提到。下面这些注解都是用在SpringBoot的自动配置。

1、@ConditionalOnBean

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

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

   String[] type() default {};
    ...........

​ 这个注解@ConditionalOnBean有点类似上面demo的NoBeanOneCondition这种,只不过NoBeanOneCondition是类,还要自动注入,这个@ConditionalOnBean只要注解标注就可以了。不过两者的核心都是通过@Conditional来导入Condition接口的实现类。这里是导入的OnBeanCondition

​ 然后这个@ConditionalOnBean只要是用来判断有没有Bean,只有存在的话才注入。下面我们来看下使用demo

1)、demo

@Configuration
@ConditionalOnBean(name = "foo")
protected static class OnBeanNameConfiguration {

   @Bean
   public String bar() {
      return "bar";
   }
}
@Configuration
protected static class FooConfiguration {

   @Bean
   public String foo() {
      return "foo";
   }

}
@Test
public void testNameOnBeanCondition() {
   this.contextRunner.withUserConfiguration(FooConfiguration.class, OnBeanNameConfiguration.class)
         .run(this::hasBarBean);
}
private void hasBarBean(AssertableApplicationContext context) {
   assertThat(context).hasBean("bar");
   assertThat(context.getBean("bar")).isEqualTo("bar");
}

​ 这样就完成了OnBeanNameConfiguration的注入,当然这里还可以指定这个Bean的类似,可以通过value、或type

@Configuration
@ConditionalOnBean(name = "foo", value = Date.class)
protected static class OnBeanNameAndTypeConfiguration {

   @Bean
   public String bar() {
      return "bar";
   }

}
@Test
public void testNameAndTypeOnBeanCondition() {
   this.contextRunner.withUserConfiguration(FooConfiguration.class, OnBeanNameAndTypeConfiguration.class)
         .run((context) -> assertThat(context).doesNotHaveBean("bar"));
}

​ 这样由于类型不匹配,就不能注入了。

当然SpringBoot还有很多的这种注解,来帮助自动配置与注解。

在这里插入图片描述

​ 看名称也大致知道其的作用了。我们下面就来看下OnBeanCondition具体逻辑。

三、OnBeanCondition

class OnBeanCondition extends SpringBootCondition implements ConfigurationCondition {

​ 它有继承SpringBootCondition,然后实现ConfigurationCondition

1、ConfigurationCondition

public interface ConfigurationCondition extends Condition {

   ConfigurationPhase getConfigurationPhase();

   enum ConfigurationPhase {

      PARSE_CONFIGURATION,
      REGISTER_BEAN
   }

}

​ 这个接口主要是为了颗粒化控制Condition的作用,要对应匹配才会返回truePARSE_CONFIGURATION表面这个是Configuration即配置类,REGISTER_BEAN表示这个是一个常规Bean

2、SpringBootCondition

public abstract class SpringBootCondition implements Condition {

​ SpringBootCondition是SpringBoot关于Condition的基础接口,也就是说,SpringBoot所有拓展Condition的都是通过SpringBootCondition来实现Condition接口。下面我们就来具体看下其的方法。

1)、matches

@Override
public final boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
   String classOrMethodName = getClassOrMethodName(metadata);
   try {
      ConditionOutcome outcome = getMatchOutcome(context, metadata);
      logOutcome(classOrMethodName, outcome);
      recordEvaluation(context, classOrMethodName, outcome);
      return outcome.isMatch();
   }
   catch (NoClassDefFoundError ex) {
      throw new IllegalStateException("Could not evaluate condition on " ....
            + "put a @ComponentScan in the default package by mistake)", ex);
   }
   catch (RuntimeException ex) {
      throw new IllegalStateException("Error processing condition on " + getName(metadata), ex);
   }
}

​ 这里首先是通过getMatchOutcome方法来获取匹配结果,然后再通过recordEvaluation来记录评估提交的详细结果,最后返回是否匹配。这里主要是getMatchOutcome方法,因为整个逻辑判断是否匹配都是这个方法,其他方法主要是记录一些信息,我们就略过了。

public class ConditionOutcome {

   private final boolean match;
   private final ConditionMessage message;

2)、getMatchOutcome

public abstract ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata);

​ 这个方法是抽象方法,要让子类来处理是否匹配。

​ 下面我们就来具体看下这个SpringBootCondition的一个子类OnBeanCondition的实现逻辑。

3、getMatchOutcome(SpringBootCondition)

@Override
public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) {
   ConditionMessage matchMessage = ConditionMessage.empty();
   if (metadata.isAnnotated(ConditionalOnBean.class.getName())) {
     ..........
   }
   if (metadata.isAnnotated(ConditionalOnSingleCandidate.class.getName())) {
     .........
   }
   if (metadata.isAnnotated(ConditionalOnMissingBean.class.getName())) {
      ........
   }
   return ConditionOutcome.match(matchMessage);
}

​ 这个我们可以看到其是会判断三种不同的注解ConditionalOnBeanConditionalOnSingleCandidateConditionalOnMissingBean。其中ConditionalOnSingleCandidate是用来判断是不是传入的类是不是有单例BeanConditionalOnMissingBean是判断有没有缺失这个Bean

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

​ 这里面的具体判断我们就先不深入探究了。

四、在注入的时候使用Conditional判断

​ 下面我们就来看下其是怎样使用的,我们在前面的两篇文章是有初步提到过得。

1、以PARSE_CONFIGURATION的方式(ConditionEvaluator源码调用)

protected void processConfigurationClass(ConfigurationClass configClass) throws IOException {
   if (this.conditionEvaluator.shouldSkip(configClass.getMetadata(), ConfigurationPhase.PARSE_CONFIGURATION)) {
      return;
   }
	..........
   // Recursively process the configuration class and its superclass hierarchy.
   SourceClass sourceClass = asSourceClass(configClass);
   do {
      sourceClass = doProcessConfigurationClass(configClass, sourceClass);
   }
   while (sourceClass != null);
   this.configurationClasses.put(configClass, configClass);
}

​ 这里就是在配置类的解析的前面以PARSE_CONFIGURATION也就是解析配置类的方式。

public boolean shouldSkip(@Nullable AnnotatedTypeMetadata metadata, @Nullable ConfigurationPhase phase) {
   if (metadata == null || !metadata.isAnnotated(Conditional.class.getName())) {
      return false;
   }

   if (phase == null) {
      .......
   }

   List<Condition> conditions = new ArrayList<>();
   for (String[] conditionClasses : getConditionClasses(metadata)) {
      .........
   }

   AnnotationAwareOrderComparator.sort(conditions);

   for (Condition condition : conditions) {
    ......
   }

   return false;
}

​ 下面我们就以前面的demo1为例,来看下这个过程。

1)

if (metadata == null || !metadata.isAnnotated(Conditional.class.getName())) {
   return false;
}

​ 首先是判断这个配置类有没有@Conditional注解,如果没有就返回false,主要在processConfigurationClass就不会直接返回,是继续往下解析。

2)

if (phase == null) {
   if (metadata instanceof AnnotationMetadata &&
         ConfigurationClassUtils.isConfigurationCandidate((AnnotationMetadata) metadata)) {
      return shouldSkip(metadata, ConfigurationPhase.PARSE_CONFIGURATION);
   }
   return shouldSkip(metadata, ConfigurationPhase.REGISTER_BEAN);
}

​ 下一步是类型phase判断,如果为null,就需要设置参数,这里的ConfigurationClassUtils.isConfigurationCandidate我们前面文章讲过,就是判断有没有@Component@Bean这些

static {
   candidateIndicators.add(Component.class.getName());
   candidateIndicators.add(ComponentScan.class.getName());
   candidateIndicators.add(Import.class.getName());
   candidateIndicators.add(ImportResource.class.getName());
}
public static boolean isConfigurationCandidate(AnnotationMetadata metadata) {
	....
   // Any of the typical annotations found?
   for (String indicator : candidateIndicators) {
      if (metadata.isAnnotated(indicator)) {
         return true;
      }
   }
   // Finally, let's look for @Bean methods...
   try {
      return metadata.hasAnnotatedMethods(Bean.class.getName());
   }
      return false;
   }
}

​ 是的话,就返回true表面其是PARSE_CONFIGURATION,不然的话就是一个普通的REGISTER_BEAN然后再重新进入shouldSkip方法,就能继续往下走了。

3)

List<Condition> conditions = new ArrayList<>();
for (String[] conditionClasses : getConditionClasses(metadata)) {
   for (String conditionClass : conditionClasses) {
      Condition condition = getCondition(conditionClass, this.context.getClassLoader());
      conditions.add(condition);
   }
}

​ 这里就是获取@Conditional的值,也就是实现Condition接口的类获取,然后通过getCondition去实例化创建添加到conditions中。例如我们的demo中,这里获取的就是NoBeanOneCondition

在这里插入图片描述

4)

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;
   }
}
return false;

​ 这里就是最后一步了,如果是ConfigurationCondition类型,由于其本身是有getConfigurationPhase()返回ConfigurationPhase的,所以这里会判断,然后就上课关键的condition.matches,就是调用Condition的方法,如果不匹配就会true,也就是注解return,不就行后续解析了。不然就最后返回false来继续解析。

五、在@ComponentScan的使用。

protected final SourceClass doProcessConfigurationClass(ConfigurationClass configClass, SourceClass sourceClass)
      throws IOException {
		.........
   // Process any @ComponentScan annotations
   Set<AnnotationAttributes> componentScans = AnnotationConfigUtils.attributesForRepeatable(
         sourceClass.getMetadata(), ComponentScans.class, ComponentScan.class);
   if (!componentScans.isEmpty() &&
         !this.conditionEvaluator.shouldSkip(sourceClass.getMetadata(), ConfigurationPhase.REGISTER_BEAN)) {
      for (AnnotationAttributes componentScan : componentScans) {
         	............
         }
      }
   }
	.....
}

​ 这个方法我们前面文章也就说明。这里之所以会用,就是因为@ComponentScans会主动引入一些Bean提过包扫描,而这些事前面提过processConfigurationClass方法没有引入到的,所以对于这些另外引入的也会进行判断。

六、SpringBoot中AutoConfigurationImportSelector的类似判断

​ 我们在上篇文章中,梳理自动配置的时候,是有遇到上面的判断能不能注入的内容的:

@Override
public String[] selectImports(AnnotationMetadata annotationMetadata) {
	.........
   AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader
         .loadMetadata(this.beanClassLoader);
   AnnotationAttributes attributes = getAttributes(annotationMetadata);
   List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
   configurations = removeDuplicates(configurations);
   Set<String> exclusions = getExclusions(annotationMetadata, attributes);
   checkExcludedClasses(configurations, exclusions);
   configurations.removeAll(exclusions);
   configurations = filter(configurations, autoConfigurationMetadata);
   fireAutoConfigurationImportEvents(configurations, exclusions);
   return StringUtils.toStringArray(configurations);
}

​ 就是这里的filter(configurations, autoConfigurationMetadata)

private List<String> filter(List<String> configurations, AutoConfigurationMetadata autoConfigurationMetadata) {
   long startTime = System.nanoTime();
   String[] candidates = StringUtils.toStringArray(configurations);
   boolean[] skip = new boolean[candidates.length];
   boolean skipped = false;
   for (AutoConfigurationImportFilter filter : getAutoConfigurationImportFilters()) {
      invokeAwareMethods(filter);
      boolean[] match = filter.match(candidates, autoConfigurationMetadata);
      for (int i = 0; i < match.length; i++) {
         if (!match[i]) {
            skip[i] = true;
            skipped = true;
         }
      }
   }
   if (!skipped) {
      return configurations;
   }
   List<String> result = new ArrayList<>(candidates.length);
   for (int i = 0; i < candidates.length; i++) {
      if (!skip[i]) {
         result.add(candidates[i]);
      }
   }
   return new ArrayList<>(result);
}

​ 这里通过filter.match筛选,所有都满足的话,就!skipped注解返回了,如果有些不能不满足,就往下筛选添加到result中。

​ 上面的getAutoConfigurationImportFilters()方法也是从spring.factorias中获取:

protected List<AutoConfigurationImportFilter> getAutoConfigurationImportFilters() {
   return SpringFactoriesLoader.loadFactories(AutoConfigurationImportFilter.class, this.beanClassLoader);
}

​ 然后会返回这3个:

# Auto Configuration Import Filters
org.springframework.boot.autoconfigure.AutoConfigurationImportFilter=\
org.springframework.boot.autoconfigure.condition.OnBeanCondition,\
org.springframework.boot.autoconfigure.condition.OnClassCondition,\
org.springframework.boot.autoconfigure.condition.OnWebApplicationCondition

这3个我最开始看到的时候以为是与上面的OnBeanCondition一样,但其实是不同的。这个是实现的AutoConfigurationImportFilter,虽然也是判断是否注入,但其也是用到了ConfigurationCondition,也是实现的Condition

@Order(Ordered.LOWEST_PRECEDENCE)
class OnBeanCondition extends FilteringSpringBootCondition implements ConfigurationCondition {
public interface ConfigurationCondition extends Condition {
@FunctionalInterface
public interface AutoConfigurationImportFilter {

   boolean[] match(String[] autoConfigurationClasses, AutoConfigurationMetadata autoConfigurationMetadata);

}

​ 这里的AutoConfigurationImportFilter其是返回的boolean[](入参也是多个autoConfigurationClasses),但Condition是直接单个返回。所以内部是具体通过Condition来匹配每个的。

public interface Condition {

   boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata);

}

七、FilteringSpringBootCondition

​ 然后AutoConfigurationImportFilter的初步实现是FilteringSpringBootCondition

1、结构方法

abstract class FilteringSpringBootCondition extends SpringBootCondition
      implements AutoConfigurationImportFilter, BeanFactoryAware, BeanClassLoaderAware {

   private BeanFactory beanFactory;

   private ClassLoader beanClassLoader;

   @Override
   public boolean[] match(String[] autoConfigurationClasses, AutoConfigurationMetadata autoConfigurationMetadata) {
      ConditionEvaluationReport report = ConditionEvaluationReport.find(this.beanFactory);
      ConditionOutcome[] outcomes = getOutcomes(autoConfigurationClasses, autoConfigurationMetadata);
      boolean[] match = new boolean[outcomes.length];
      for (int i = 0; i < outcomes.length; i++) {
         match[i] = (outcomes[i] == null || outcomes[i].isMatch());
         if (!match[i] && outcomes[i] != null) {
            logOutcome(autoConfigurationClasses[i], outcomes[i]);
            if (report != null) {
               report.recordConditionEvaluation(autoConfigurationClasses[i], this, outcomes[i]);
            }
         }
      }
      return match;
   }

​ 然后这里也会获取ConditionEvaluationReport来记录信息。通过getOutcomes数组来记录每个具体的匹配关系。我们以OnBeanConditionFilteringSpringBootCondition具体实现,本身有3个实现返回:

class OnBeanCondition extends FilteringSpringBootCondition implements ConfigurationCondition {

   @Override
   public ConfigurationPhase getConfigurationPhase() {
      return ConfigurationPhase.REGISTER_BEAN;
   }

   @Override
   protected final ConditionOutcome[] getOutcomes(String[] autoConfigurationClasses,
         AutoConfigurationMetadata autoConfigurationMetadata) {
      ConditionOutcome[] outcomes = new ConditionOutcome[autoConfigurationClasses.length];
      for (int i = 0; i < outcomes.length; i++) {
         String autoConfigurationClass = autoConfigurationClasses[i];
         if (autoConfigurationClass != null) {
            Set<String> onBeanTypes = autoConfigurationMetadata.getSet(autoConfigurationClass, "ConditionalOnBean");
            outcomes[i] = getOutcome(onBeanTypes, ConditionalOnBean.class);
            if (outcomes[i] == null) {
               Set<String> onSingleCandidateTypes = autoConfigurationMetadata.getSet(autoConfigurationClass,
                     "ConditionalOnSingleCandidate");
               outcomes[i] = getOutcome(onSingleCandidateTypes, ConditionalOnSingleCandidate.class);
            }
         }
      }
      return outcomes;
   }

​ 这里下面也是遍历然后通过@ConditionalOnBean@ConditionalOnSingleCandidate来具体判断每个匹配。上面是OnBeanCondition,然后另外一个实现OnClassCondition也是类似的。

@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> onMissingClasses = getCandidates(metadata, ConditionalOnMissingClass.class);
   if (onMissingClasses != null) {
     	.......
      matchMessage = matchMessage.andCondition(ConditionalOnMissingClass.class)
            .didNotFind("unwanted class", "unwanted classes")
            .items(Style.QUOTE, filter(onMissingClasses, ClassNameFilter.MISSING, classLoader));
   }
   return ConditionOutcome.match(matchMessage);
}

​ 这里是使用ConditionalOnMissingClassConditionalOnClass来判断,这里之使用是这样匹配,是因为通过AutoConfigurationImportFilterspring.factorios获取的所有内容,都是通过只这些@ConditionalOnMissingClass@ConditionalOnClass来注入我们通过srping-start引入的内容的。例如返回的其中一个valueorg.springframework.boot.autoconfigure.aop.AopAutoConfiguration

@Configuration(proxyBeanMethods = false)
@ConditionalOnProperty(prefix = "spring.aop", name = "auto", havingValue = "true", matchIfMissing = true)
public class AopAutoConfiguration {

   @Configuration(proxyBeanMethods = false)
   @ConditionalOnClass(Advice.class)
   static class AspectJAutoProxyingConfiguration {

      @Configuration(proxyBeanMethods = false)
      @EnableAspectJAutoProxy(proxyTargetClass = false)
      @ConditionalOnProperty(prefix = "spring.aop", name = "proxy-target-class", havingValue = "false",
            matchIfMissing = false)
      static class JdkDynamicAutoProxyConfiguration {

      }

      @Configuration(proxyBeanMethods = false)
      @EnableAspectJAutoProxy(proxyTargetClass = true)
      @ConditionalOnProperty(prefix = "spring.aop", name = "proxy-target-class", havingValue = "true",
            matchIfMissing = true)
      static class CglibAutoProxyConfiguration {

      }

   }
    	.........
}

​ 我们现在还没有引入spring-boot-starter-aop,所以是没有Advice.class,由于ConditionalOnClass就不会注入AspectJAutoProxyingConfiguration这些内容。

在这里插入图片描述

​ 这里现在是没有Advice,当我们引入spring-boot-starter-aop

在这里插入图片描述

​ 就能注入需要的内容了。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值