Spring 源码解读(五):@Conditional及其衍生扩展注解(5千字大章)

github地址:https://github.com/cass-pwx/conditional-demo

1、概述

条件装配Spring Boot一大特点,根据是否满足指定的条件来决定是否装配 Bean ,做到了动态灵活性,starter的自动配置类中就是使用@Conditional及其衍生扩展注解

上一篇文章@SpringBootApplication使用及原理详解中我们也提到了AutoConfigurationImportSelector最终其实就是加载了META-INF/spring.factories目录下的自动配置类,那这一次我们就来看看动配置类的条件装配注解

2、@Conditional

2.1、是什么

@Conditional:该注解是在spring4中新加的,其作用顾名思义就是按照一定的条件进行判断,满足条件才将bean注入到容器中,注解源码如下:

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Conditional {/*** All {@link Condition} classes that must {@linkplain Condition#matches match}* in order for the component to be registered.*/Class<? extends Condition>[] value();}

从注释我们可以看到,只有在所有指定条件匹配时才有资格注册

从代码中可知,该注解可作用在类,方法上,同时只有一个属性value,是一个Class数组,并且需要继承或者实现Condition接口:

我们再看看Condition接口

@FunctionalInterface
public interface Condition {/*** Determine if the condition matches.* @param context the condition context* @param metadata the metadata of the {@link org.springframework.core.type.AnnotationMetadata class}* or {@link org.springframework.core.type.MethodMetadata method} being checked* @return {@code true} if the condition matches and the component can be registered,* or {@code false} to veto the annotated component's registration*/boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata);}

通过@FunctionalInterface我们可以看到,这是一个函数式接口,即可以使用函数式编程,lambda表达式。

再通过前面的Conditional解释,我们可以总结出:

  • Condition是个接口,需要实现matches方法,返回true则注入bean,false则不注入
  • @Conditional通过传入一个或者多个实现了Condition并重写了matches的类,然后判断是否进行注册。

有的小伙伴可能还不是很清晰,我们接下来举一个例子来看看。

2.2、举例

我们先随便建一个model类

import lombok.Builder;
import lombok.Data;/*** @author pengweixin*/
@Data
@Builder
public class Language {private Long id;private String content;
}

然后建两个实现了Condition的类

import org.springframework.context.annotation.Condition;
import org.springframework.context.annotation.ConditionContext;
import org.springframework.core.env.Environment;
import org.springframework.core.type.AnnotatedTypeMetadata;import java.util.Objects;/*** @author pengweixin*/
public class ChineseCondition implements Condition {@Overridepublic boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {Environment environment = context.getEnvironment();String property = environment.getProperty("lang");return Objects.equals(property, "zh_CN");}
}
import org.springframework.context.annotation.Condition;
import org.springframework.context.annotation.ConditionContext;
import org.springframework.core.env.Environment;
import org.springframework.core.type.AnnotatedTypeMetadata;import java.util.Objects;/*** @author pengweixin*/
public class EnglishCondition implements Condition {@Overridepublic boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {Environment environment = context.getEnvironment();String property = environment.getProperty("lang");return Objects.equals(property, "en_US");}
}

然后接下来建一个配置类

import com.pwx.model.Language;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Conditional;
import org.springframework.context.annotation.Configuration;/*** @author pengweixin*/
@Configuration
public class MyConfig {@Bean@Conditional(ChineseCondition.class)public Language chinese() {return Language.builder().id(1L).content("华流才是最屌的").build();}@Bean@Conditional(EnglishCondition.class)public Language english() {return Language.builder().id(2L).content("english is good").build();}public static void main(String[] args) {System.setProperty("lang", "zh_CN");AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(MyConfig.class);String[] beanDefinitionNames = applicationContext.getBeanDefinitionNames();// 遍历Spring容器中的beanNamefor (String beanDefinitionName : beanDefinitionNames) {System.out.println(beanDefinitionName);}}
}

代码比较简单,就不进行解释了,我们直接看结果

在这里插入图片描述

这说明根据条件匹配到ChineseCondition返回true,成功注入bean

2.3、@Conditional 衍生注解

@Conditional 衍生注解被定义在了spring-boot-autoconfigure包中

在这里插入图片描述

这些注解各位不用现在记住,我们后面拿几个看看,看懂了记起来就没那么难了。

2.4、@ConditionalOnBean

这个注解,我们从名字上就能够很清楚的看出,注入的条件是Bean。也就是说,如果Bean存在,就注入,不存在就不注入。这也是一个比较重要的注解,我们看看代码

注意:@ConditionalOnBean 是匹配目前为止由应用程序上下文处理过的bean定义,而不是 bean实例

@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;Class<?>[] parameterizedContainer() default {};}

这里平时用的比较多的有value、type、name三个,这三个可以看到都是数组,也就是说可以配置多个。

2.4.1、怎么用

在这里插入图片描述

我们可以从这段注释中看到,@ConditionalOnBean注解使用在@Bean标识的方法上,都知道@Bean注解的作用是向容器中注入一个bean,也就是@ConditionalOnBean作用的时机是在生成bean的时候。

同时,从上面这段话中,我们也可以看到,@ConditionalOnBean的使用是具有先后顺序的

我们通过一个例子具体看看。

1、创建两个model类

@Data
@AllArgsConstructor
public class School {private String name;
}@Data
@AllArgsConstructor
public class Student {private String name;
}

2、创建一个config类

@Configuration
public class BeanConfiguration {@Beanpublic School school() {return new School("清华大学");}@ConditionalOnBean(School.class)@Bean("zhangsan")public Student student() {return new Student("张三");}public static void main(String[] args) {AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(BeanConfiguration.class);String[] beanDefinitionNames = applicationContext.getBeanDefinitionNames();// 遍历Spring容器中的beanNamefor (String beanDefinitionName : beanDefinitionNames) {System.out.println(beanDefinitionName);}}
}

运行结果:

在这里插入图片描述

如果换一个写法,把school()方法和student()方法互换位置。

@Configuration
public class BeanConfiguration {@ConditionalOnBean(School.class)@Bean("zhangsan")public Student student() {return new Student("张三");}@Beanpublic School school() {return new School("清华大学");}public static void main(String[] args) {AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(BeanConfiguration.class);String[] beanDefinitionNames = applicationContext.getBeanDefinitionNames();// 遍历Spring容器中的beanNamefor (String beanDefinitionName : beanDefinitionNames) {System.out.println(beanDefinitionName);}}
}

运行结果如下:

在这里插入图片描述

可以看到,student类没有被初始化了。这也满足我们之前说的,@ConditionalOnBean的使用是具有先后顺序的

对于type属性,type要求填入的是类的全路径,比如com.pwx.model.School

对于name属性,name要求填入的是类名,比如school

对于这两个属性,同样满足@ConditionalOnBean需要按照@Bean的顺序的要求。

2.4.2、源码解析

我们可以看到核心就是@Conditional(OnBeanCondition.class),点进去到OnBeanCondition类中

class OnBeanCondition extends FilteringSpringBootCondition implements ConfigurationCondition {//.....
}

可以看到这个类继承了FilteringSpringBootCondition,点进去看看

abstract class FilteringSpringBootCondition extends SpringBootConditionimplements AutoConfigurationImportFilter, BeanFactoryAware, BeanClassLoaderAware {//.....
}

可以看到,他继承了SpringBootCondition

public abstract class SpringBootCondition implements Condition {// ....
}

可以看到,他实现了了Condition,这个我们知道,我们看看具体的matches()方法实现:

public abstract class SpringBootCondition implements Condition {@Overridepublic final boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {//获取当前注解标记的类名或者方法名(由标注的位置决定)String classOrMethodName = getClassOrMethodName(metadata);try {//关键代码:这里就会判断出结果ConditionOutcome outcome = getMatchOutcome(context, metadata);//存入日志logOutcome(classOrMethodName, outcome);//存入日志recordEvaluation(context, classOrMethodName, outcome);//最后返回ConditionOutcome的isMatch就是返回boolean类型结果return outcome.isMatch();}catch (NoClassDefFoundError ex) {throw new IllegalStateException("Could not evaluate condition on " + classOrMethodName + " due to "+ ex.getMessage() + " not found. Make sure your own configuration does not rely on "+ "that class. This can also happen if you are "+ "@ComponentScanning a springframework package (e.g. if you "+ "put a @ComponentScan in the default package by mistake)", ex);}catch (RuntimeException ex) {throw new IllegalStateException("Error processing condition on " + getName(metadata), ex);}}
}

然后来到了我们的OnBeanCondition.getMatchOutcome()方法

class OnBeanCondition extends FilteringSpringBootCondition implements ConfigurationCondition {@Overridepublic ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) {ConditionMessage matchMessage = ConditionMessage.empty();MergedAnnotations annotations = metadata.getAnnotations();// ConditionalOnBean 注解处理if (annotations.isPresent(ConditionalOnBean.class)) {Spec<ConditionalOnBean> spec = new Spec<>(context, metadata, annotations, ConditionalOnBean.class);MatchResult matchResult = getMatchingBeans(context, spec);if (!matchResult.isAllMatched()) {String reason = createOnBeanNoMatchReason(matchResult);return ConditionOutcome.noMatch(spec.message().because(reason));}matchMessage = spec.message(matchMessage).found("bean", "beans").items(Style.QUOTE,matchResult.getNamesOfAllMatches());}// ConditionalOnSingleCandidate 注解处理// ConditionalOnMissingBean 注解处理return ConditionOutcome.match(matchMessage);}
}

后面那两个部分我们后面再看,先看看ConditionalOnBean 注解处理

  1. 首先,判断是不是存在这个ConditionalOnBean.class注解的标识,这个我们的student类是使用了该注解,所以这个判断返回true

  2. 生成一个spec对象,该类是从底层的注解中提取的搜索规范;

    在这里插入图片描述

  3. 进入getMatchingBeans(context, spec)方法

    在这里插入图片描述

    可以看得出来,就是封装了一个MatchResult对象。

    1. 从上下文【context】中获取 ClassLoaderConfigurableListableBeanFactory

      • ClassLoaderJava 中的一个接口,用于加载类。它是 Java 类加载机制的核心部分,负责将 .class 文件转换为 Java 类实例。

      • ClassLoader 可以从不同的来源(如文件系统、网络、数据库等)加载类,也可以实现自定义的类加载逻辑。

      • ConfigurableListableBeanFactory 是 Spring 框架中的一个核心接口,它扩展了ListableBeanFactory 接口,提供了更多的配置和扩展功能。

      • 它是一个 bean 工厂的抽象概念,用于管理 Spring 容器中的 bean 对象。ConfigurableListableBeanFactory 提供了添加、移除、注册和查找 bean 的方法,以及设置和获取 bean 属性值的功能。它还支持bean 的后处理和事件传播。

    2. 这里根据 Spec 对象的 SearchStrategy 属性来确定是否考虑 bean 的层次结构。如果SearchStrategy 是 CURRENT,则不考虑层次结构【即 considerHierarchy 为 false】;否则,考虑层次结构【即 considerHierarchy 为 true】。

      • debug中我们可以看到,这里我们的strategy是SearchStrategy.ALL,所以这里是返回true

      • 这里默认都是SearchStrategy.ALL

        public @interface ConditionalOnBean {SearchStrategy search() default SearchStrategy.ALL;
        }
        
    3. 获取 Spec 对象的 parameterizedContainers 属性,这是一个包含参数化容器类型的集合

    4. 判断Spec对象的SearchStrategy属性是SearchStrategy.ANCESTORS,调用 getParentBeanFactory 方法获取其父工厂,并将其转换为 ConfigurableListableBeanFactory 类型。当然,我们这里并不会进入这个判断。

    5. 新建一个 MatchResult 对象,用于存储匹配结果;

    6. 调用 getNamesOfBeansIgnoredByType 方法,获取被忽略类型的 bean 名称集合 beansIgnoredByType

      • 这一段@ConditionalOnBean并不需要做相应的判断,是在@ConditionalOnMissingBean中使用的,所以这里跳过
    7. 接下来就是进行匹配,并封装了一个MatchResult对象

  4. 如果都没匹配上,那就结束了。

    if (!matchResult.isAllMatched()) {String reason = createOnBeanNoMatchReason(matchResult);return ConditionOutcome.noMatch(spec.message().because(reason));
    }public static ConditionOutcome noMatch(ConditionMessage message) {return new ConditionOutcome(false, message);
    }
    
  5. 匹配上了,就封装一个匹配信息,然后返回true,完成注入

    在这里插入图片描述

2.5、@ConditionalOnMissingBean

看完了@ConditionalOnBean,@ConditionalOnMissingBean其实也是一样的

@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Conditional(OnBeanCondition.class)
public @interface ConditionalOnMissingBean {Class<?>[] value() default {};String[] type() default {};Class<?>[] ignored() default {};String[] ignoredType() default {};Class<? extends Annotation>[] annotation() default {};String[] name() default {};SearchStrategy search() default SearchStrategy.ALL;Class<?>[] parameterizedContainer() default {};}

2.5.1、怎么用

先跑个例子看看看

@Configuration
public class BeanConfig {@Bean(name = "notebookPC")public Computer computer1(){return new Computer("笔记本电脑");}@ConditionalOnMissingBean(Computer.class)@Bean("reservePC")public Computer computer2(){return new Computer("备用电脑");}public static void main(String[] args) {AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(BeanConfig.class);String[] beanDefinitionNames = applicationContext.getBeanDefinitionNames();// 遍历Spring容器中的beanNamefor (String beanDefinitionName : beanDefinitionNames) {System.out.println(beanDefinitionName);}}
}

运行结果:

在这里插入图片描述

稍微修改一下:

@Configuration
public class BeanConfig {/*    @Bean(name = "notebookPC")public Computer computer1(){return new Computer("笔记本电脑");}*/@ConditionalOnMissingBean(Computer.class)@Bean("reservePC")public Computer computer2(){return new Computer("备用电脑");}public static void main(String[] args) {AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(BeanConfig.class);String[] beanDefinitionNames = applicationContext.getBeanDefinitionNames();// 遍历Spring容器中的beanNamefor (String beanDefinitionName : beanDefinitionNames) {System.out.println(beanDefinitionName);}}
}

运行结果如下:

在这里插入图片描述

很清楚的可以看出,@ConditionalOnMissingBean@ConditionalOnBean的作用是相反的

2.5.2、源码解析

之前我们看@ConditionalOnBean的时候,我们看到,其实@ConditionalOnMissingBean的源码和@ConditionalOnBean就只在OnBeanCondition.getMatchOutcome()方法不一样

class OnBeanCondition extends FilteringSpringBootCondition implements ConfigurationCondition {@Overridepublic ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) {ConditionMessage matchMessage = ConditionMessage.empty();MergedAnnotations annotations = metadata.getAnnotations();// ConditionalOnBean 注解处理if (annotations.isPresent(ConditionalOnBean.class)) {Spec<ConditionalOnBean> spec = new Spec<>(context, metadata, annotations, ConditionalOnBean.class);MatchResult matchResult = getMatchingBeans(context, spec);if (!matchResult.isAllMatched()) {String reason = createOnBeanNoMatchReason(matchResult);return ConditionOutcome.noMatch(spec.message().because(reason));}matchMessage = spec.message(matchMessage).found("bean", "beans").items(Style.QUOTE,matchResult.getNamesOfAllMatches());}// ConditionalOnSingleCandidate 注解处理// ConditionalOnMissingBean 注解处理if (metadata.isAnnotated(ConditionalOnMissingBean.class.getName())) {Spec<ConditionalOnMissingBean> spec = new Spec<>(context, metadata, annotations,ConditionalOnMissingBean.class);MatchResult matchResult = getMatchingBeans(context, spec);if (matchResult.isAnyMatched()) {String reason = createOnMissingBeanNoMatchReason(matchResult);return ConditionOutcome.noMatch(spec.message().because(reason));}matchMessage = spec.message(matchMessage).didNotFind("any beans").atAll();}return ConditionOutcome.match(matchMessage);}
}

从代码的层面上,我们可以很清楚的看出,@ConditionalOnMissingBean的处理方式和@ConditionalOnBean是完全相反的。

不过,我们之前说的@ConditionalOnMissingBean多了一个处理被忽略类型的 bean,这个一般是处理父类的。

public @interface ConditionalOnMissingBean {Class<?>[] ignored() default {};String[] ignoredType() default {};
}

我们跑一个例子看看

@Configuration
public class BeanConfig {@Bean(name = "desktopPC")public DesktopPC desktopPC(){return new DesktopPC("台式电脑", "华硕全家桶");}@ConditionalOnMissingBean(value = Computer.class)@Bean("notebookPC")public Computer computer(){return new Computer("笔记本电脑");}public static void main(String[] args) {AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(BeanConfig.class);String[] beanDefinitionNames = applicationContext.getBeanDefinitionNames();// 遍历Spring容器中的beanNamefor (String beanDefinitionName : beanDefinitionNames) {System.out.println(beanDefinitionName);}}
}

执行结果:

在这里插入图片描述

稍微修改一下

@Configuration
public class BeanConfig {@Bean(name = "desktopPC")public DesktopPC desktopPC(){return new DesktopPC("台式电脑", "华硕全家桶");}@ConditionalOnMissingBean(value = Computer.class, ignored = DesktopPC.class)@Bean("notebookPC")public Computer computer(){return new Computer("笔记本电脑");}public static void main(String[] args) {AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(BeanConfig.class);String[] beanDefinitionNames = applicationContext.getBeanDefinitionNames();// 遍历Spring容器中的beanNamefor (String beanDefinitionName : beanDefinitionNames) {System.out.println(beanDefinitionName);}}
}

执行结果:

在这里插入图片描述

我们进入到源码看看

在这里插入图片描述

可以看到,多了一个ignoredTypes。

this.ignoredTypes = extract(attributes, "ignored", "ignoredType");

继续往下看

class OnBeanCondition extends FilteringSpringBootCondition implements ConfigurationCondition {private Set<String> getNamesOfBeansIgnoredByType(ClassLoader classLoader, ListableBeanFactory beanFactory,boolean considerHierarchy, Set<String> ignoredTypes, Set<Class<?>> parameterizedContainers) {Set<String> result = null;for (String ignoredType : ignoredTypes) {Collection<String> ignoredNames = getBeanNamesForType(classLoader, considerHierarchy, beanFactory, ignoredType, parameterizedContainers);result = addAll(result, ignoredNames);}return (result != null) ? result : Collections.emptySet();}private Set<String> getBeanNamesForType(ClassLoader classLoader, boolean considerHierarchy, ListableBeanFactory beanFactory, String type, Set<Class<?>> parameterizedContainers) throws LinkageError {try {return getBeanNamesForType(beanFactory, considerHierarchy, resolve(type, classLoader),parameterizedContainers);}catch (ClassNotFoundException | NoClassDefFoundError ex) {return Collections.emptySet();}}private Set<String> getBeanNamesForType(ListableBeanFactory beanFactory, boolean considerHierarchy, Class<?> type, Set<Class<?>> parameterizedContainers) {Set<String> result = collectBeanNamesForType(beanFactory, considerHierarchy, type, parameterizedContainers,null);return (result != null) ? result : Collections.emptySet();}private Set<String> collectBeanNamesForType(ListableBeanFactory beanFactory, boolean considerHierarchy, Class<?> type, Set<Class<?>> parameterizedContainers, Set<String> result) {result = addAll(result, beanFactory.getBeanNamesForType(type, true, false));for (Class<?> container : parameterizedContainers) {ResolvableType generic = ResolvableType.forClassWithGenerics(container, type);result = addAll(result, beanFactory.getBeanNamesForType(generic, true, false));}if (considerHierarchy && beanFactory instanceof HierarchicalBeanFactory) {// 做递归查看父工厂BeanFactory parent = ((HierarchicalBeanFactory) beanFactory).getParentBeanFactory();if (parent instanceof ListableBeanFactory) {result = collectBeanNamesForType((ListableBeanFactory) parent, considerHierarchy, type, parameterizedContainers, result);}}return result;}
}

这就生成了beansIgnoredByType,然后在后面的匹配中进行排除。

2.6.3、彩蛋

有时候我们会看到某一个配置上只写了一个@ConditionalOnMissingBean,类似于

在这里插入图片描述

这种情况一般是指,返回值的这个类要求是单例,也就是说,如果有别的地方已经完成了实例化,这里就不会再实例化了

这里我们可以到Spec的构造方法中看到

private static class Spec<A extends Annotation> {Spec(ConditionContext context, AnnotatedTypeMetadata metadata, MergedAnnotations annotations,Class<A> annotationType) {MultiValueMap<String, Object> attributes = annotations.stream(annotationType).filter(MergedAnnotationPredicates.unique(MergedAnnotation::getMetaTypes)).collect(MergedAnnotationCollectors.toMultiValueMap(Adapt.CLASS_TO_STRING));MergedAnnotation<A> annotation = annotations.get(annotationType);this.classLoader = context.getClassLoader();this.annotationType = annotationType;this.names = extract(attributes, "name");this.annotations = extract(attributes, "annotation");this.ignoredTypes = extract(attributes, "ignored", "ignoredType");this.parameterizedContainers = resolveWhenPossible(extract(attributes, "parameterizedContainer"));this.strategy = annotation.getValue("search", SearchStrategy.class).orElse(null);Set<String> types = extractTypes(attributes);BeanTypeDeductionException deductionException = null;if (types.isEmpty() && this.names.isEmpty()) {try {types = deducedBeanType(context, metadata);}catch (BeanTypeDeductionException ex) {deductionException = ex;}}this.types = types;validate(deductionException);}private Set<String> deducedBeanType(ConditionContext context, AnnotatedTypeMetadata metadata) {if (metadata instanceof MethodMetadata && metadata.isAnnotated(Bean.class.getName())) {return deducedBeanTypeForBeanMethod(context, (MethodMetadata) metadata);}return Collections.emptySet();}private Set<String> deducedBeanTypeForBeanMethod(ConditionContext context, MethodMetadata metadata) {try {Class<?> returnType = getReturnType(context, metadata);return Collections.singleton(returnType.getName());}catch (Throwable ex) {throw new BeanTypeDeductionException(metadata.getDeclaringClassName(), metadata.getMethodName(), ex);}}
}

不过这里而我们在日常使用中,可能有一个小问题:

  • 注意返回值需不需要设置成接口,也就是说,如果设置成接口,就是让 接口 只有一个实现的bean。

2.6、@ConditionalOnSingleCandidate

这个注解,平时接触的可能比较少,在这里做一个顺带介绍吧

@ConditionalOnSingleCandidate对应的Condition处理类也是OnBeanCondition。

如果当指定Bean在容器中只有一个,或者虽然有多个但是指定首选Bean的时候则生效。

@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Conditional(OnBeanCondition.class)
public @interface ConditionalOnSingleCandidate {/*** 需要作为条件的类的Class对象*/Class<?> value() default Object.class;/*** 需要作为条件的类的Name, Class.getName()*/String type() default "";/*** 搜索容器层级,当前容器,父容器*/SearchStrategy search() default SearchStrategy.ALL;}

直接到使用环节!

2.6.1、怎么用

@Configuration
public class BeanSingleCandidateConfig {@Beanpublic Computer desktopPC() {return new Computer("台式电脑");}@Beanpublic Computer computer() {return new Computer("笔记本电脑");}/*** 一个学生只能有一个电脑** @return -*/@Bean(name = "小明")@ConditionalOnSingleCandidate(Computer.class)public Student getStudent() {return new Student("小明");}public static void main(String[] args) {AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(BeanSingleCandidateConfig.class);String[] beanDefinitionNames = applicationContext.getBeanDefinitionNames();// 遍历Spring容器中的beanNamefor (String beanDefinitionName : beanDefinitionNames) {System.out.println(beanDefinitionName);}}
}

运行结果:

在这里插入图片描述

可以看到,student.class没有被初始化

我们注释掉其中一个

@Configuration
public class BeanSingleCandidateConfig {@Beanpublic Computer desktopPC() {return new Computer("台式电脑");}/*    @Beanpublic Computer computer() {return new Computer("笔记本电脑");}*//*** 一个学生只能有一个电脑** @return -*/@Bean(name = "小明")@ConditionalOnSingleCandidate(Computer.class)public Student getStudent() {return new Student("小明");}public static void main(String[] args) {AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(BeanSingleCandidateConfig.class);String[] beanDefinitionNames = applicationContext.getBeanDefinitionNames();// 遍历Spring容器中的beanNamefor (String beanDefinitionName : beanDefinitionNames) {System.out.println(beanDefinitionName);}}
}

运行结果:

在这里插入图片描述

结果上很清楚的看到结果!

2.6.2、源码分析

还是回到我们熟悉的那个方法

class OnBeanCondition extends FilteringSpringBootCondition implements ConfigurationCondition {@Overridepublic ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) {ConditionMessage matchMessage = ConditionMessage.empty();MergedAnnotations annotations = metadata.getAnnotations();// ConditionalOnBean 注解处理// ConditionalOnSingleCandidate 注解处理if (metadata.isAnnotated(ConditionalOnSingleCandidate.class.getName())) {Spec<ConditionalOnSingleCandidate> spec = new SingleCandidateSpec(context, metadata, annotations);MatchResult matchResult = getMatchingBeans(context, spec);if (!matchResult.isAllMatched()) {return ConditionOutcome.noMatch(spec.message().didNotFind("any beans").atAll());}Set<String> allBeans = matchResult.getNamesOfAllMatches();if (allBeans.size() == 1) {matchMessage = spec.message(matchMessage).found("a single bean").items(Style.QUOTE, allBeans);} else {List<String> primaryBeans = getPrimaryBeans(context.getBeanFactory(), allBeans,spec.getStrategy() == SearchStrategy.ALL);if (primaryBeans.isEmpty()) {return ConditionOutcome.noMatch(spec.message().didNotFind("a primary bean from beans").items(Style.QUOTE, allBeans));}if (primaryBeans.size() > 1) {return ConditionOutcome.noMatch(spec.message().found("multiple primary beans").items(Style.QUOTE, primaryBeans));}matchMessage = spec.message(matchMessage).found("a single primary bean '" + primaryBeans.get(0) + "' from beans").items(Style.QUOTE, allBeans);}}// ConditionalOnMissingBean 注解处理}protected final MatchResult getMatchingBeans(ConditionContext context, Spec<?> spec) {ClassLoader classLoader = context.getClassLoader();ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();boolean considerHierarchy = spec.getStrategy() != SearchStrategy.CURRENT;Set<Class<?>> parameterizedContainers = spec.getParameterizedContainers();//跳过if (spec.getStrategy() == SearchStrategy.ANCESTORS) {BeanFactory parent = beanFactory.getParentBeanFactory();Assert.isInstanceOf(ConfigurableListableBeanFactory.class, parent,"Unable to use SearchStrategy.ANCESTORS");beanFactory = (ConfigurableListableBeanFactory) parent;}MatchResult result = new MatchResult();//这块是@ConditionalOnMissingBean 的,不管Set<String> beansIgnoredByType = getNamesOfBeansIgnoredByType(classLoader, beanFactory, considerHierarchy,spec.getIgnoredTypes(), parameterizedContainers);// 核心,获取类的实例for (String type : spec.getTypes()) {Collection<String> typeMatches = getBeanNamesForType(classLoader, considerHierarchy, beanFactory, type,parameterizedContainers);Iterator<String> iterator = typeMatches.iterator();while (iterator.hasNext()) {String match = iterator.next();//这块是@ConditionalOnMissingBean 的,不管if (beansIgnoredByType.contains(match) || ScopedProxyUtils.isScopedTarget(match)) {iterator.remove();}}if (typeMatches.isEmpty()) {result.recordUnmatchedType(type);}else {result.recordMatchedType(type, typeMatches);}}// ....return result;}private Set<String> getBeanNamesForType(ClassLoader classLoader, boolean considerHierarchy,ListableBeanFactory beanFactory, String type, Set<Class<?>> parameterizedContainers) throws LinkageError {try {return getBeanNamesForType(beanFactory, considerHierarchy, resolve(type, classLoader),parameterizedContainers);}catch (ClassNotFoundException | NoClassDefFoundError ex) {return Collections.emptySet();}}private Set<String> getBeanNamesForType(ListableBeanFactory beanFactory, boolean considerHierarchy, Class<?> type,Set<Class<?>> parameterizedContainers) {Set<String> result = collectBeanNamesForType(beanFactory, considerHierarchy, type, parameterizedContainers,null);return (result != null) ? result : Collections.emptySet();}private Set<String> collectBeanNamesForType(ListableBeanFactory beanFactory, boolean considerHierarchy,Class<?> type, Set<Class<?>> parameterizedContainers, Set<String> result) {result = addAll(result, beanFactory.getBeanNamesForType(type, true, false));for (Class<?> container : parameterizedContainers) {ResolvableType generic = ResolvableType.forClassWithGenerics(container, type);result = addAll(result, beanFactory.getBeanNamesForType(generic, true, false));}if (considerHierarchy && beanFactory instanceof HierarchicalBeanFactory) {BeanFactory parent = ((HierarchicalBeanFactory) beanFactory).getParentBeanFactory();if (parent instanceof ListableBeanFactory) {result = collectBeanNamesForType((ListableBeanFactory) parent, considerHierarchy, type,parameterizedContainers, result);}}return result;}
}

这些代码前面我们都看过,就不详细介绍了

在这里插入图片描述

在这里插入图片描述

2.7、@ConditionalOnClass

这个注解,我们从名字上就能够很清楚的看出,注入的条件是Class。也就是说,当给定的类名在类路径上存在,则实例化当前Bean。这也是一个比较重要的注解,我们看看代码

@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Conditional(OnClassCondition.class)
public @interface ConditionalOnClass {Class<?>[] value() default {};String[] name() default {};}

可以看到,这个和之前的@ConditionalOnMissingBean@ConditionalOnBean不一样了。涉及的是OnClassCondition

而且,我们从注释中可以看到,@ConditionalOnClass没有和@ConditionalOnBean一样强调顺序性。

2.7.1、怎么用

这个的使用和@ConditionalOnBean差不多,而且Spring中有大量的例子,就不做举例了

我们直接进入源码阶段。

2.7.2、源码分析

OnClassCondition类其实和OnBeanCondition差不多,都是实现了FilteringSpringBootCondition类,所以最主要的,还是getMatchOutcome方法

class OnClassCondition extends FilteringSpringBootCondition {// 该方法分三部分// 1. 处理ConditionalOnClass注解// 2. 处理ConditionalOnMissingClass注解// 3. 返回条件判断的结果@Overridepublic ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) {ClassLoader classLoader = context.getClassLoader();ConditionMessage matchMessage = ConditionMessage.empty();// 1. 处理ConditionalOnClass注解//获得@ConditionalOnClass注解中配置的value和name属性的值// 如有加载不到的则放到missing中List<String> onClasses = getCandidates(metadata, ConditionalOnClass.class);if (onClasses != null) {// filter()方法内部 对onClass表示的类进行反射,条件为MISSING,// 如果得到的集合不为空,则说明类路径中不存在ConditionalOnClass注解中标注的类// 这种情况下直接通过ConditionOutcome.noMatch()封装ConditionOutcome条件判断的结果并返回,noMatch()即表示不通过。List<String> missing = filter(onClasses, ClassNameFilter.MISSING, classLoader);if (!missing.isEmpty()) {return ConditionOutcome.noMatch(ConditionMessage.forCondition(ConditionalOnClass.class).didNotFind("required class", "required classes").items(Style.QUOTE, missing));}// 对ConditionalOnClass注解的条件判断通过,并保存对应的信息到matchMessagematchMessage = matchMessage.andCondition(ConditionalOnClass.class).found("required class", "required classes").items(Style.QUOTE, filter(onClasses, ClassNameFilter.PRESENT, classLoader));}// 2. 处理ConditionalOnMissingClass注解// 获取该元数据表示的类或方法上的ConditionalOnMissingClass注解中标注的类的限定名,// 表示这些类应当在classpath类路径中不存在,所以叫onMissingClass// 例如:@ConditionalOnMissingClass({ RabbitTemplate.class, Channel.class }),//      则返回RabbitTemplate和Channel的全限定类名List<String> onMissingClasses = getCandidates(metadata, ConditionalOnMissingClass.class);if (onMissingClasses != null) {// filter()方法内部 对onMissingClasses表示的类进行反射,条件为PRESENT,// 如果得到的集合不为空,则说明类路径中存在ConditionalOnMissingClass注解中标注的类// 这种情况下直接通过ConditionOutcome.noMatch()封装ConditionOutcome条件判断的结果并返回,noMatch()即表示不通过。List<String> present = filter(onMissingClasses, ClassNameFilter.PRESENT, classLoader);if (!present.isEmpty()) {return ConditionOutcome.noMatch(ConditionMessage.forCondition(ConditionalOnMissingClass.class).found("unwanted class", "unwanted classes").items(Style.QUOTE, present));}// 对ConditionalOnMissingClass注解的条件判断通过,并保存对应的信息到matchMessagematchMessage = matchMessage.andCondition(ConditionalOnMissingClass.class).didNotFind("unwanted class", "unwanted classes").items(Style.QUOTE, filter(onMissingClasses, ClassNameFilter.MISSING, classLoader));}// 3. 返回条件判断的结果,到这一步,就说明ConditionalOnClass注解和ConditionalOnMissingClass注解上的条件都已经通过了。return ConditionOutcome.match(matchMessage);}private List<String> getCandidates(AnnotatedTypeMetadata metadata, Class<?> annotationType) {MultiValueMap<String, Object> attributes = metadata.getAllAnnotationAttributes(annotationType.getName(), true);if (attributes == null) {return null;}List<String> candidates = new ArrayList<>();addAll(candidates, attributes.get("value"));addAll(candidates, attributes.get("name"));return candidates;}/*** 判断是否加载到*/protected final List<String> filter(Collection<String> classNames, ClassNameFilter classNameFilter,ClassLoader classLoader) {if (CollectionUtils.isEmpty(classNames)) {return Collections.emptyList();}List<String> matches = new ArrayList<>(classNames.size());for (String candidate : classNames) {if (classNameFilter.matches(candidate, classLoader)) {matches.add(candidate);}}return matches;}
}

在这里插入图片描述

在这里插入图片描述

可以看到,@ConditionalOnClass注解中判断配置的类是否存在使用的方法是Class.forName,类加载。

对于@ConditionalOnMissingClass,也是一样的逻辑,这里就不展开说明了

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/452942.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

spring boot3x登录开发-上(整合jwt)

⛰️个人主页: 蒾酒 &#x1f525;系列专栏&#xff1a;《spring boot实战》 &#x1f30a;山高路远&#xff0c;行路漫漫&#xff0c;终有归途。 目录 前置条件 jwt简介 导依赖 编写jwt工具类 1.配置项直接嵌入代码&#xff0c;通过类名.静态方法使用 2.配置项写到…

ARM PAC指针认证的侧信道攻击——PACMAN安全漏洞

目录 Q1. PACMAN论文的内容是什么&#xff1f; Q2. Arm处理器是否存在漏洞&#xff1f; Q3. 受Arm合作伙伴架构许可设计的处理器实现是否受到影响&#xff1f; Q4. Cortex-M85受到影响吗&#xff1f; Q5. Cortex-R82受到影响吗&#xff1f; Q6. 指针认证如何保护软件&…

配置visualsvn提交后自动邮件通知

参考&#xff1a; https://blog.csdn.net/wiker_yong/article/details/10334967 # -*- coding: utf-8 -*- import sys import os import smtplib from email.mime.text import MIMEText from email.header import Headermail_host smtp.163.com #发送邮件的smtp地址 mail_us…

Swift Vapor 教程(查询数据、插入数据)

上一篇简单写了 怎么创建 Swift Vapor 项目以及在开发过程中使用到的软件。 这一篇写一个怎么在创建的项目中创建一个简单的查询数据和插入数据。 注&#xff1a;数据库配置比较重要 先将本地的Docker启动起来&#xff0c;用Docker管理数据库 将项目自己创建的Todo相关的都删掉…

小程序中封装下拉选择框

小程序中没有现成的下拉选择组件&#xff0c;有个picker组件&#xff0c;但是是底部弹出的&#xff0c;不满足我的需求&#xff0c;所以重新封装了一个。 封装的下拉组件 html部分&#xff1a; <view class"select_all_view"><!-- 内容说明&#xff0c;可…

es6中标签模板

之所以写这篇文章&#xff0c;是因为标签模板是一个很容易让人忽略的知识点 首先我们已经非常熟悉模板字符串的使用方法 const name "诸葛亮" const templateString hello, My name is ${name}标签模板介绍 这里的标签模板其实不是模板&#xff0c;而是函数调用…

简单的JavaScript去下载转换为Base64的PDF文件

新建一个文件&#xff0c;内容填写如下&#xff0c;然后保存为 .html 类型的文件 再用浏览器打开&#xff0c;就会是下面这样子&#xff1a; 图一红色textarea里面&#xff0c;可以将PDF文件转换成BASE64位后的内容贴进去&#xff0c;点击下载时&#xff0c;就可以直接下载成PD…

腾讯云幻兽帕鲁Palworld服务器价格表,2024年2月最新

腾讯云幻兽帕鲁服务器价格32元起&#xff0c;4核16G12M配置32元1个月、96元3个月、156元6个月、312元一年&#xff0c;支持4-8个玩家&#xff1b;8核32G22M幻兽帕鲁服务器115元1个月、345元3个月&#xff0c;支持10到20人在线开黑。腾讯云百科txybk.com分享更多4核8G12M、16核6…

使用SPM_batch进行批量跑脚本(matlab.m)

软件&#xff1a;spm8matlab2023bwin11 数据格式&#xff1a; F:\ASL\HC\CBF\HC_caishaoqing\CBF.nii F:\ASL\HC\CBF\HC_caishaoqing\T1.nii F:\ASL\HC\CBF\HC_wangdonga\CBF.nii F:\ASL\HC\CBF\HC_wangdonga\T1.nii clear spmdirD:\AnalysisApps\spm8; datadirF:\ASL\HC\CBF…

MySQL如何实时同步数据到ES?试试阿里开源的Canal

前言 前几天在网上冲浪的时候发现了一个比较成熟的开源中间件—— Canal 。在了解了它的工作原理和使用场景后&#xff0c;顿时产生了浓厚的兴趣。今天&#xff0c;就让我们跟随我的脚步&#xff0c;一起来揭开它神秘的面纱吧。 目录 前言 简介 工作原理 MySQL主备复制…

深度学习在智能交互中的应用:人与机器的和谐共生

深度学习与人类的智能交互是当前人工智能领域研究的热点之一。深度学习作为机器学习的一个重要分支&#xff0c;具有强大的特征学习和模式识别能力&#xff0c;可以模拟人脑的神经网络进行数据分析和预测。而人类的智能交互则是指人类与机器之间的信息交流和操作互动&#xff0…

ShardingSphere 5.x 系列【5】Spring Boot 3.1 集成Sharding Sphere-JDBC并实现读写分离

有道无术&#xff0c;术尚可求&#xff0c;有术无道&#xff0c;止于术。 本系列Spring Boot 版本 3.1.0 本系列ShardingSphere 版本 5.4.0 源码地址&#xff1a;https://gitee.com/pearl-organization/study-sharding-sphere-demo 文章目录 1. 概述2. 使用限制3. 案例演示3.…