SpringBoot @ConditionalOnBean、@ConditionalOnMissingBean注解源碼分析與示例


前言:

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類的實例
     

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM