前言:

Spring4推出了@Conditional注解,方便程序根据当前环境或者容器情况来动态注入bean,对@Conditional注解不熟悉的朋友可移步至 Spring @Conditional注解 详细讲解及示例 这篇博客进行学习。

继@Conditional注解后,又基于此注解推出了很多派生注解,比如@ConditionalOnBean、@ConditionalOnMissingBean、@ConditionalOnExpression、@ConditionalOnClass......动态注入bean变得更方便了。本篇将讲解@ConditionalOnBean注解。

配置类中有两个Computer类的bean,一个是笔记本电脑,一个是备用电脑。如果当前容器中已经有电脑bean了,就不注入备用电脑,如果没有,则注入备用电脑,这里需要使用到@ConditionalOnMissingBean。


  1. @Configuration
  2. public class BeanConfig {
  3. @Bean(name = "notebookPC")
  4. public Computer computer1(){
  5. return new Computer("笔记本电脑");
  6. }
  7. @ConditionalOnMissingBean(Computer.class)
  8. @Bean("reservePC")
  9. public Computer computer2(){
  10. return new Computer("备用电脑");
  11. }
  12. }

这个注解就实现了功能,这个@ConditionalOnMissingBean为我们做了什么呢?我们来一探究竟.。

一探究竟:

首先,来看@ConditionalOnMissingBean的声明:


  1. //可以标注在类和方法上
  2. @Target({ElementType.TYPE, ElementType.METHOD})
  3. @Retention(RetentionPolicy.RUNTIME)
  4. @Documented
  5. //使用了@Conditional注解,条件类是OnBeanCondition
  6. @Conditional({OnBeanCondition.class})
  7. public @interface ConditionalOnMissingBean {
  8. Class<?>[] value() default {};
  9. String[] type() default {};
  10. Class<?>[] ignored() default {};
  11. String[] ignoredType() default {};
  12. Class<? extends Annotation>[] annotation() default {};
  13. String[] name() default {};
  14. SearchStrategy search() default SearchStrategy.ALL;
  15. }

这时候,我们就看到了我们熟悉的@Conditional注解,OnBeanCondition作为条件类。

OnBeanCondition类的声明:


  1. //定义带注释的组件的排序顺序,2147483647即为默认值
  2. @Order(2147483647)
  3. class OnBeanCondition extends SpringBootCondition implements ConfigurationCondition {

它继承了SpringBootCondition类,OnBeanCondition类中没有matches方法,而SpringBootCondition类中有实现matches方法。OnBeanCondition还实现了ConfigurationCondition,ConfigurationCondition接口不熟悉的读者可以到Spring ConfigurationCondition接口详解 了解接口。OnBeanCondition类重写了getConfigurationPhase()方法,表示在注册bean的时候注解生效:


  1. public ConfigurationPhase getConfigurationPhase() {
  2. return ConfigurationPhase.REGISTER_BEAN;
  3. }

就从matches方法开始:


  1. //SpringBootCondition类中的matches方法
  2. public final boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
  3. //获取当前的类名或者方法名(由标注的位置决定)
  4. String classOrMethodName = getClassOrMethodName(metadata);
  5. try {
  6. //关键代码:这里就会判断出结果
  7. ConditionOutcome outcome = this.getMatchOutcome(context, metadata);
  8. //存入日志
  9. this.logOutcome(classOrMethodName, outcome);
  10. //存入记录
  11. this.recordEvaluation(context, classOrMethodName, outcome);
  12. //最后返回ConditionOutcome的isMatch就是返回boolean类型结果
  13. return outcome.isMatch();
  14. } catch (NoClassDefFoundError var5) {
  15. throw new IllegalStateException("Could not evaluate condition on " + classOrMethodName + " due to " + var5.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)", var5);
  16. } catch (RuntimeException var6) {
  17. throw new IllegalStateException("Error processing condition on " + this.getName(metadata), var6);
  18. }
  19. }

关键代码在OnBeanCondition的getMatchOutcome方法上:


  1. /**
  2. * 获得判断结果的方法,ConditionOutcome类中存着boolean类型的结果
  3. */
  4. public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) {
  5. //返回一个新的ConditionMessage
  6. ConditionMessage matchMessage = ConditionMessage.empty();
  7. OnBeanCondition.BeanSearchSpec spec;
  8. List matching;
  9. //这是metadata会调用isAnnotated方法判断当前标注的注解是不是ConditionalOnMissingBean
  10. //其实@ConditionalOnBean、@ConditionalOnMissingBean和@ConditionalOnSingleCandidate都是使用这个条件类,所以这里做判断
  11. if (metadata.isAnnotated(ConditionalOnBean.class.getName())) {
  12. spec = new OnBeanCondition.BeanSearchSpec(context, metadata, ConditionalOnBean.class);
  13. matching = this.getMatchingBeans(context, spec);
  14. if (matching.isEmpty()) {
  15. return ConditionOutcome.noMatch(ConditionMessage.forCondition(ConditionalOnBean.class, new Object[]{spec}).didNotFind("any beans").atAll());
  16. }
  17. matchMessage = matchMessage.andCondition(ConditionalOnBean.class, new Object[]{spec}).found("bean", "beans").items(Style.QUOTE, matching);
  18. }
  19. if (metadata.isAnnotated(ConditionalOnSingleCandidate.class.getName())) {
  20. OnBeanCondition.BeanSearchSpec spec = new OnBeanCondition.SingleCandidateBeanSearchSpec(context, metadata, ConditionalOnSingleCandidate.class);
  21. matching = this.getMatchingBeans(context, spec);
  22. if (matching.isEmpty()) {
  23. return ConditionOutcome.noMatch(ConditionMessage.forCondition(ConditionalOnSingleCandidate.class, new Object[]{spec}).didNotFind("any beans").atAll());
  24. }
  25. if (!this.hasSingleAutowireCandidate(context.getBeanFactory(), matching, spec.getStrategy() == SearchStrategy.ALL)) {
  26. return ConditionOutcome.noMatch(ConditionMessage.forCondition(ConditionalOnSingleCandidate.class, new Object[]{spec}).didNotFind("a primary bean from beans").items(Style.QUOTE, matching));
  27. }
  28. matchMessage = matchMessage.andCondition(ConditionalOnSingleCandidate.class, new Object[]{spec}).found("a primary bean from beans").items(Style.QUOTE, matching);
  29. }
  30. //如果当前注入的bean是@ConditionalOnMissingBean
  31. if (metadata.isAnnotated(ConditionalOnMissingBean.class.getName())) {
  32. //返回一个spec(说明),这里的spec规定了搜索的内容,比如搜索策略、需要搜索的类名......
  33. spec = new OnBeanCondition.BeanSearchSpec(context, metadata, ConditionalOnMissingBean.class);
  34. //主要的搜索实现在这个方法里,最后返回一个list
  35. matching = this.getMatchingBeans(context, spec);
  36. //判断搜索出来的结果
  37. if (!matching.isEmpty()) {
  38. return ConditionOutcome.noMatch(ConditionMessage.forCondition(ConditionalOnMissingBean.class, new Object[]{spec}).found("bean", "beans").items(Style.QUOTE, matching));
  39. }
  40. matchMessage = matchMessage.andCondition(ConditionalOnMissingBean.class, new Object[]{spec}).didNotFind("any beans").atAll();
  41. }
  42. return ConditionOutcome.match(matchMessage);
  43. }

spec = new OnBeanCondition.BeanSearchSpec(context, metadata, ConditionalOnBean.class);

这句中,相当于从内部类中将标注@ConditionalOnMissingBean注解时的属性都取出来:


  1. BeanSearchSpec(ConditionContext context, AnnotatedTypeMetadata metadata, Class<?> annotationType) {
  2. this.annotationType = annotationType;
  3. MultiValueMap<String, Object> attributes = metadata.getAllAnnotationAttributes(annotationType.getName(), true);
  4. //将attributes这个map中的数据放到对应的list成员变量中
  5. this.collect(attributes, "name", this.names);
  6. this.collect(attributes, "value", this.types);
  7. this.collect(attributes, "type", this.types);
  8. this.collect(attributes, "annotation", this.annotations);
  9. this.collect(attributes, "ignored", this.ignoredTypes);
  10. this.collect(attributes, "ignoredType", this.ignoredTypes);
  11. this.strategy = (SearchStrategy)metadata.getAnnotationAttributes(annotationType.getName()).get("search");
  12. OnBeanCondition.BeanTypeDeductionException deductionException = null;
  13. try {
  14. if (this.types.isEmpty() && this.names.isEmpty()) {
  15. this.addDeducedBeanType(context, metadata, this.types);
  16. }
  17. } catch (OnBeanCondition.BeanTypeDeductionException var7) {
  18. deductionException = var7;
  19. }
  20. this.validate(deductionException);
  21. }
  22. //验证的方法
  23. protected void validate(OnBeanCondition.BeanTypeDeductionException ex) {
  24. if (!this.hasAtLeastOne(this.types, this.names, this.annotations)) {
  25. String message = this.annotationName() + " did not specify a bean using type, name or annotation";
  26. if (ex == null) {
  27. throw new IllegalStateException(message);
  28. } else {
  29. throw new IllegalStateException(message + " and the attempt to deduce the bean's type failed", ex);
  30. }
  31. }
  32. }

看一下OnBeanCondition类中的getMatchingBeans方法,里面有用到搜索策略,详见搜索策略介绍


  1. private List<String> getMatchingBeans(ConditionContext context, OnBeanCondition.BeanSearchSpec beans) {
  2. //获得当前bean工厂
  3. ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
  4. //判断当前的搜索策略是否是PARENTS或者ANCESTORS,默认是ALL
  5. if (beans.getStrategy() == SearchStrategy.PARENTS || beans.getStrategy() == SearchStrategy.ANCESTORS) {
  6. BeanFactory parent = beanFactory.getParentBeanFactory();
  7. Assert.isInstanceOf(ConfigurableListableBeanFactory.class, parent, "Unable to use SearchStrategy.PARENTS");
  8. //如果是PARENTS或者ANCESTORS,当前bean工厂就用父工厂
  9. beanFactory = (ConfigurableListableBeanFactory)parent;
  10. }
  11. if (beanFactory == null) {
  12. return Collections.emptyList();
  13. } else {
  14. List<String> beanNames = new ArrayList();
  15. //如果当前搜索策略等于CURRENT,为true
  16. boolean considerHierarchy = beans.getStrategy() != SearchStrategy.CURRENT;
  17. //这里的type就是需要查找的bean的类型
  18. //下面,会从属性中找bean
  19. Iterator var6 = beans.getTypes().iterator();
  20. String beanName;
  21. while(var6.hasNext()) {
  22. beanName = (String)var6.next();
  23. //如果找到了类型,接下来就是根据类型找bean的实例名,找示例名的方法在下方,实际上就是一个getNamesForType
  24. beanNames.addAll(this.getBeanNamesForType(beanFactory, beanName, context.getClassLoader(), considerHierarchy));
  25. }
  26. var6 = beans.getIgnoredTypes().iterator();
  27. while(var6.hasNext()) {
  28. beanName = (String)var6.next();
  29. beanNames.removeAll(this.getBeanNamesForType(beanFactory, beanName, context.getClassLoader(), considerHierarchy));
  30. }
  31. var6 = beans.getAnnotations().iterator();
  32. while(var6.hasNext()) {
  33. beanName = (String)var6.next();
  34. beanNames.addAll(Arrays.asList(this.getBeanNamesForAnnotation(beanFactory, beanName, context.getClassLoader(), considerHierarchy)));
  35. }
  36. var6 = beans.getNames().iterator();
  37. while(var6.hasNext()) {
  38. beanName = (String)var6.next();
  39. if (this.containsBean(beanFactory, beanName, considerHierarchy)) {
  40. beanNames.add(beanName);
  41. }
  42. }
  43. //将存放bean实例名的list返回
  44. return beanNames;
  45. }
  46. }
  47. //根据类型获取bean的name
  48. private Collection<String> getBeanNamesForType(ListableBeanFactory beanFactory, String type, ClassLoader classLoader, boolean considerHierarchy) throws LinkageError {
  49. try {
  50. Set<String> result = new LinkedHashSet();
  51. this.collectBeanNamesForType(result, beanFactory, ClassUtils.forName(type, classLoader), considerHierarchy);
  52. return result;
  53. } catch (ClassNotFoundException var6) {
  54. return Collections.emptySet();
  55. } catch (NoClassDefFoundError var7) {
  56. return Collections.emptySet();
  57. }
  58. }
  59. private void collectBeanNamesForType(Set<String> result, ListableBeanFactory beanFactory, Class<?> type, boolean considerHierarchy) {
  60. result.addAll(BeanTypeRegistry.get(beanFactory).getNamesForType(type));
  61. if (considerHierarchy && beanFactory instanceof HierarchicalBeanFactory) {
  62. BeanFactory parent = ((HierarchicalBeanFactory)beanFactory).getParentBeanFactory();
  63. if (parent instanceof ListableBeanFactory) {
  64. this.collectBeanNamesForType(result, (ListableBeanFactory)parent, type, considerHierarchy);
  65. }
  66. }
  67. }

找完bean了之后,回到刚才的代码里:


  1. //如果当前注入的bean是@ConditionalOnMissingBean
  2. if (metadata.isAnnotated(ConditionalOnMissingBean.class.getName())) {
  3. //返回一个spec(说明),这里的spec规定了搜索的内容,比如搜索策略、需要搜索的类名......
  4. spec = new OnBeanCondition.BeanSearchSpec(context, metadata, ConditionalOnMissingBean.class);
  5. matching = this.getMatchingBeans(context, spec);
  6. if (!matching.isEmpty()) {
  7. return ConditionOutcome.noMatch(ConditionMessage.forCondition(ConditionalOnMissingBean.class, new Object[]{spec}).found("bean", "beans").items(Style.QUOTE, matching));
  8. }
  9. matchMessage = matchMessage.andCondition(ConditionalOnMissingBean.class, new Object[]{spec}).didNotFind("any beans").atAll();
  10. }

如果第5行返回的list不是空的,就会返回ConditionOutcome对象noMatch方法,表示不匹配。ConditionOutcome类用于存放过滤结果,只有两个变量:


  1. /**
  2. * 过滤结果类
  3. */
  4. public class ConditionOutcome {
  5. /**
  6. * 匹配结果 true or false
  7. */
  8. private final boolean match;
  9. /**
  10. * 匹配结果信息
  11. */
  12. private final ConditionMessage message;

两者区别:

@ConditionOnBean在判断list的时候,如果list没有值,返回false,否则返回true

@ConditionOnMissingBean在判断list的时候,如果list没有值,返回true,否则返回false,其他逻辑都一样

例子:

  • @ConditionalOnBean(javax.sql.DataSource.class)    

    Spring容器或者所有父容器中需要存在至少一个javax.sql.DataSource类的实例

SpringBoot @ConditionalOnBean、@ConditionalOnMissingBean注解源码分析与示例的更多相关文章

  1. SpringBoot的条件注解源码解析

    SpringBoot的条件注解源码解析 @ConditionalOnBean.@ConditionalOnMissingBean 启动项目 会在ConfigurationClassBeanDefini ...

  2. Springboot 加载配置文件源码分析

    Springboot 加载配置文件源码分析 本文的分析是基于springboot 2.2.0.RELEASE. 本篇文章的相关源码位置:https://github.com/wbo112/blogde ...

  3. Spring笔记(5) - 声明式事务@EnableTransactionManagement注解源码分析

    一.背景 前面详解了实现Spring事务的两种方式的不同实现:编程式事务和声明式事务,对于配置都使用到了xml配置,今天介绍Spring事务的注解开发,例如下面例子: 配置类:注册数据源.JDBC模板 ...

  4. java io系列02之 ByteArrayInputStream的简介,源码分析和示例(包括InputStream)

    我们以ByteArrayInputStream,拉开对字节类型的“输入流”的学习序幕.本章,我们会先对ByteArrayInputStream进行介绍,然后深入了解一下它的源码,最后通过示例来掌握它的 ...

  5. java io系列03之 ByteArrayOutputStream的简介,源码分析和示例(包括OutputStream)

    前面学习ByteArrayInputStream,了解了“输入流”.接下来,我们学习与ByteArrayInputStream相对应的输出流,即ByteArrayOutputStream.本章,我们会 ...

  6. java io系列04之 管道(PipedOutputStream和PipedInputStream)的简介,源码分析和示例

    本章,我们对java 管道进行学习. 转载请注明出处:http://www.cnblogs.com/skywang12345/p/io_04.html java 管道介绍 在java中,PipedOu ...

  7. springBoot从入门到源码分析

    先分享一个springBoot搭建学习项目,和springboot多数据源项目的传送门:https://github.com/1057234721/springBoot 1. SpringBoot快速 ...

  8. spring注解源码分析--how does autowired works?

    1. 背景 注解可以减少代码的开发量,spring提供了丰富的注解功能.我们可能会被问到,spring的注解到底是什么触发的呢?今天以spring最常使用的一个注解autowired来跟踪代码,进行d ...

  9. SpringBoot拦截器及源码分析

    1.拦截器是什么 java里的拦截器(Interceptor)是动态拦截Action调用的对象,它提供了一种机制可以使开发者在一个Action执行的前后执行一段代码,也可以在一个Action执行前阻止 ...

随机推荐

  1. P3402 【模板】可持久化并查集

    传送门 //minamoto #include<bits/stdc++.h> using namespace std; #define getc() (p1==p2&&(p ...

  2. java io 文件下载

    /** * 文件下载 * @param response * @param downloadPath * @param docName */ public void downLoadFile( Htt ...

  3. BFS(最短路) HDU 2612 Find a way

    题目传送门 /* BFS:和UVA_11624差不多,本题就是分别求两个点到KFC的最短路,然后相加求最小值 */ /***************************************** ...

  4. Linux重启和关机命令

    Linux重启命令: 方式1:shutdown –r now 方式2:reboot Linux关机命令: shutdown –h now

  5. pycharm但多行注释快捷键

    pycharm中同时注释多行代码快捷键: 代码选中的条件下,同时按住 Ctrl+/,被选中行被注释,再次按下Ctrl+/,注释被取消

  6. IKanalyzer、ansj_seg、jcseg三种中文分词器的实战较量

    转自:http://lies-joker.iteye.com/blog/2173086 选手:IKanalyzer.ansj_seg.jcseg 硬件:i5-3470 3.2GHz 8GB win7 ...

  7. 这样的设计是否有违背MVC设计原则??

    MVC 皆知为 Model-View-Controller 请求模型-〉Client发现请求-〉Controller接收+处理-〉返回Model给前端-〉前端接收处理模型Result 但是最近发现一个 ...

  8. [ CQOI 2014 ] 数三角形

    \(\\\) Description 求 \(N\times M\) 的网格图上有多少个格点构成的三角形. 当三点共线的时候我们不认为这是一个三角形. \(n,m\le 10^4\) \(\\\) S ...

  9. 通过重写.htaccess文件添加404

    如果说是用linux服务器的系统 想要给自己的网站设置404怎么弄?如果你不会给自己的Ecs服务器添加服务器管理系统,或是你购买的云虚拟主机没有304.404设置,那么就要通过自己重写文件来设置404 ...

  10. oracle添加联合主键

    1 alter table tablename add constraint unionkeyname primary key (column1,column2); 上面语句中: tablename为 ...