SpringBoot(5-@Conditional条件注解) | 总字数: 4.6k | 阅读时长: 21分钟 | 浏览量: | 
属性注入 
@ConfigurationProperties+@Component 
如果一个类只配置了@ConfigurationProperties 注解,而没有使用@Component 注解将该类加入到 IOC 容器中,那么它就不能完成 xxx.properties 文件和 Java Bean 的数据绑定
1 2 xiaomao.name =xiaomaomao xiaomao.age =27 
 
1 2 3 4 5 6 7 8 9 10 @Component @ConfigurationProperties(prefix = "xiaomao") @Data public  class  MyConfigurationProperties  {    private  String name;          private  Integer age;          private  String gender; } 
 
@EnableConfigurationProperties 
@EnableConfigurationProperties(A.class)的作用就是如果 A 这个类上使用了@ConfigurationProperties 注解,那么 A 这个类就会与 xxx.properties 进行动态绑定,并且会将 A 这个类加入 IOC 容器中,并交由 IOC 容器进行管理
1 2 3 4 5 6 7 8 9 @ConfigurationProperties(prefix = "xiaomao") @Data public  class  MyConfigurationProperties  {    private  String name;          private  Integer age;          private  String gender; } 
 
1 2 3 4 5 @Service @EnableConfigurationProperties(MyConfigurationProperties.class) public  class  HelloServiceImpl  implements  HelloService  {} 
 
@Conditional 扩展注解 
Condition 处理类 
条件注解 
实例 
解释 
 
 
OnBeanCondition 
@ConditionalOnBean 
@ConditionalOnBean(DataSource.class) 
Spring 容器中不存在对应的实例生效 
 
OnBeanCondition 
@ConditionalOnMissingBean 
@ConditionalOnMissingBean(name = “redisTemplate”) 
Spring 容器中不存在对应的实例生效 
 
OnBeanCondition 
@ConditionalOnSingleCandidate 
@ConditionalOnSingleCandidate(FilteringNotifier.class) 
Spring 容器中是否存在且只存在一个对应的实例,或者虽然有多个但 是指定首选的 Bean 生效 
 
OnClassCondition 
@ConditionalOnClass 
@ConditionalOnClass(RedisOperations.class) 
类加载器中存在对应的类生效 
 
OnClassCondition 
@ConditionalOnMissingClass 
@ConditionalOnMissingClass(RedisOperations.class) 
类加载器中不存在对应的类生效 
 
OnExpressionCondition 
@ConditionalOnExpression 
@ConditionalOnExpression(“‘’${server.host}'==‘localhost’”) 
判断 SpEL 表达式成立生效 
 
@ConditionalOnJava 
OnJavaCondition 
@ConditionalOnJava(JavaVersion.EIGHT) 
指定 Java 版本符合要求生效 
 
@ConditionalOnProperty 
OnPropertyCondition 
@ConditionalOnProperty(prefix = “spring.aop”, name = “auto”, havingValue = “true”, matchIfMissing = true) 
应用环境中的属性满足条件生效 
 
@ConditionalOnResource 
OnResourceCondition 
@ConditionalOnResource(resources =“mybatis.xml”) 
存在指定的资源文件生效 
 
@ConditionalOnWebApplication 
OnWebApplicationCondition 
 
当前应用是 Web 应用生效 
 
@ConditionalOnNotWebApplication 
OnWebApplicationCondition 
 
当前应用不是 Web 应用生效 
 
 
上面的扩展注解我们可以简单的分为以下几类:
Bean 作为条件:@ConditionalOnBean、@ConditionalOnMissingBean、@ConditionalOnSingleCandidate。 
类作为条件:@ConditionalOnClass、@ConditionalOnMissingClass。 
SpEL 表达式作为条件:@ConditionalOnExpression。 
JAVA 版本作为条件:@ConditionalOnJava 
配置属性作为条件:@ConditionalOnProperty。 
资源文件作为条件:@ConditionalOnResource。 
是否 Web 应用作为判断条件:@ConditionalOnWebApplication、@ConditionalOnNotWebApplication。 
 
@ConditionalOnBean 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 @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  {}; } 
 
@ConditionalOnBean 对应的 Condition 处理类是 OnBeanCondition,如果 Spring 容器里面存在指定的 Bean 则生效。
1 2 3 4 5 6 @Bean @ConditionalOnBean(DataSource.class) public  String onBeanCondition ()  {    return  "DataSource 存在时该 Bean 被创建" ; } 
 
@ConditionalOnMissingBean 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 @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  {}; } 
 
@ConditionalOnMissingBean 对应的 Condition 实现类是 OnBeanCondition,如果 Spring 容器里面不存在指定的 Bean 则生效。
1 2 3 4 5 6 7 8 9 @Bean @ConditionalOnMissingBean(name = "redisTemplate") public  RedisTemplate<Object, Object> redisTemplate (RedisConnectionFactory redisConnectionFactory)         throws  UnknownHostException {     RedisTemplate<Object, Object> template = new  RedisTemplate <>();     template.setConnectionFactory(redisConnectionFactory);     return  template; } 
 
@ConditionalOnSingleCandidate 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 @Target({ ElementType.TYPE, ElementType.METHOD }) @Retention(RetentionPolicy.RUNTIME) @Documented @Conditional(OnBeanCondition.class) public  @interface  ConditionalOnSingleCandidate {         Class<?> value() default  Object.class;          String type ()  default  "" ;          SearchStrategy search ()  default  SearchStrategy.ALL; } 
 
@ConditionalOnSingleCandidate 对应的 Condition 处理类是 OnBeanCondition,如果当指定 Bean 在容器中只有一个,或者虽然有多个但是指定首选 Bean 的时候则生效。
1 2 3 4 5 6 @Bean @ConditionalOnSingleCandidate(DataSource.class) public  String onSingleCandidate ()  {    return  "存在唯一的 DataSource 实例时,该 Bean 被创建" ; } 
 
@ConditionalOnClass 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 @Target({ ElementType.TYPE, ElementType.METHOD }) @Retention(RetentionPolicy.RUNTIME) @Documented @Conditional(OnClassCondition.class) public  @interface  ConditionalOnClass {         Class<?>[] value() default  {};          String[] name() default  {}; } 
 
@ConditionalOnClass 对应的 Condition 处理类是 OnClassCondition,如果当前类路径下面有指定的类的时候则生效。
1 2 3 4 5 6 @Bean @ConditionalOnClass(name = "com.mysql.cj.jdbc.Driver") public  String onClassCondition ()  {    return  "MySQL Driver 存在于 classpath 时,该 Bean 被创建" ; } 
 
@ConditionalOnMissingClass 
1 2 3 4 5 6 7 8 9 10 11 @Target({ ElementType.TYPE, ElementType.METHOD }) @Retention(RetentionPolicy.RUNTIME) @Documented @Conditional(OnClassCondition.class) public  @interface  ConditionalOnMissingClass {         String[] value() default  {}; } 
 
@ConditionalOnMissingClass 对应的 Condition 处理类是 OnClassCondition,如果当前类路径下面没有指定的类的时候则生效。
1 2 3 4 5 6 @Bean @ConditionalOnMissingClass("com.mysql.cj.jdbc.Driver") public  String onMissingClassCondition ()  {    return  "MySQL Driver 不存在于 classpath 时,该 Bean 被创建" ; } 
 
@ConditionalOnExpression 
1 2 3 4 5 6 7 8 9 10 11 @Retention(RetentionPolicy.RUNTIME) @Target({ ElementType.TYPE, ElementType.METHOD }) @Documented @Conditional(OnExpressionCondition.class) public  @interface  ConditionalOnExpression {         String value ()  default  "true" ; } 
 
@ConditionalOnExpression 对应的 Condition 处理类是 OnExpressionCondition,只有当 SpEL 表达式满足条件的时候则生效。
1 2 3 4 5 6 @Bean @ConditionalOnExpression("'${custom.feature.enabled:false}' == 'true'") public  String onExpressionCondition ()  {    return  "SpEL 表达式结果为 true 时,该 Bean 被创建" ; } 
 
@ConditionalOnJava 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 @Target({ ElementType.TYPE, ElementType.METHOD }) @Retention(RetentionPolicy.RUNTIME) @Documented @Conditional(OnJavaCondition.class) public  @interface  ConditionalOnJava {         Range range ()  default  Range.EQUAL_OR_NEWER;          JavaVersion value () ;          enum  Range  {                  EQUAL_OR_NEWER,                  OLDER_THAN     } } 
 
@ConditionalOnJava 对应的 Condition 处理类是 OnJavaCondition,只有当指定的 JAVA 版本条件满足的时候,才会创建对应的 Bean。
1 2 3 4 5 6 @Bean @ConditionalOnJava(range = ConditionalOnJava.Range.EQUAL_OR_NEWER, value = ConditionalOnJava.JavaVersion.ELEVEN) public  String onJavaVersionCondition ()  {    return  "Java 版本 >= 11 时,该 Bean 被创建" ; } 
 
@ConditionalProperty 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 @Retention(RetentionPolicy.RUNTIME) @Target({ ElementType.TYPE, ElementType.METHOD }) @Documented @Conditional(OnPropertyCondition.class) public  @interface  ConditionalOnProperty {         String[] value() default  {};          String prefix ()  default  "" ;          String[] name() default  {};          String havingValue ()  default  "" ;          boolean  matchIfMissing ()  default  false ; } 
 
指定 prefix 和 name 
 
1 2 3 config:   person:      enable:  true  
 
1 2 3 4 5 6 7 8 9 @Configuration public  class  MyBeanConfig  {    @Bean      @ConditionalOnProperty(prefix = "config.person", name = "enable")      public  Person person1 () {         return  new  Person ("Bill Gates" , 66 );     } } 
 
只指定 name 或 value 
 
1 2 3 4 5 6 7 8 9 @Configuration public  class  MyBeanConfig  {    @Bean      @ConditionalOnProperty(value = "config.person.enable")      public  Person person1 () {         return  new  Person ("Bill Gates" , 66 );     } } 
 
指定 havingValue 
 
指定了 havingValue,要把配置项的值与 havingValue 对比,一致则加载 Bean
注意:havingValue 可以不设置,只要配置项的值不是 false 或 “false”,都加载 Bean
1 2 3 4 5 6 7 8 9 @Configuration public  class  MyBeanConfig  {         @Bean      @ConditionalOnProperty(prefix = "config.person", name = "enable", havingValue = "true")      public  Person person1 () {        return  new  Person ("Bill Gates" , 66 );     } } 
 
指定 matchIfMissing 
 
配置文件缺少配置,但配置了 matchIfMissing = true,加载 Bean,否则不加载
1 2 3 4 5 6 7 8 9 @Configuration public  class  MyBeanConfig  {    @Bean      @ConditionalOnProperty(prefix = "config.person", name = "enable", havingValue = "true", matchIfMissing = true)      public  Person person1 () {         return  new  Person ("Bill Gates" , 66 );     } } 
 
@ConditionalOnResource 
1 2 3 4 5 6 7 8 9 10 11 @Target({ ElementType.TYPE, ElementType.METHOD }) @Retention(RetentionPolicy.RUNTIME) @Documented @Conditional(OnResourceCondition.class) public  @interface  ConditionalOnResource {         String[] resources() default  {}; } 
 
@ConditionalOnResource 对应的 Condition 处理类 OnResourceCondition,只有当指定的资源文件出现在 classpath 中则生效。
1 2 3 4 5 6 @Bean @ConditionalOnResource(resources = "classpath:config/application.yaml") public  String onResourceCondition ()  {    return  "classpath 下存在 application.yaml 文件时,该 Bean 被创建" ; } 
 
@ConditionalOnWebApplication 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 @Target({ ElementType.TYPE, ElementType.METHOD }) @Retention(RetentionPolicy.RUNTIME) @Documented @Conditional(OnWebApplicationCondition.class) public  @interface  ConditionalOnWebApplication {         Type type ()  default  Type.ANY;          enum  Type  {                  ANY,                  SERVLET,                  REACTIVE     } } 
 
@ConditionalOnWebApplication 对应的 Condition 处理类是 OnWebApplicationCondition,只有当当前项目是 Web 项目的时候则生效。
1 2 3 4 5 6 @Bean @ConditionalOnWebApplication public  String onWebApplicationCondition ()  {    return  "Web 应用环境下,该 Bean 被创建" ; } 
 
@ConditionalOnNotWebApplication 
1 2 3 4 5 6 7 @Target({ ElementType.TYPE, ElementType.METHOD }) @Retention(RetentionPolicy.RUNTIME) @Documented @Conditional(OnWebApplicationCondition.class) public  @interface  ConditionalOnNotWebApplication {} 
 
@ConditionalOnNotWebApplication 对应的 Condition 处理类是 OnWebApplicationCondition,只有当当前项目不是 Web 项目的时候则生效。
1 2 3 4 5 6 @Bean @ConditionalOnNotWebApplication public  String onNotWebApplicationCondition ()  {    return  "非 Web 应用环境下,该 Bean 被创建" ; } 
 
@Conditional 自定义 
仿照 OnPropertyCondition 源码进行修改,扩展注解 ConditionalOnPropertyExist,指定我们的 Condition 实现类 OnPropertyExistCondition,并且指定两个参数,一个是参数 name 用于指定属性,另一个参数 exist 用于指定是判断存在还是不存在。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.TYPE, ElementType.METHOD}) @Documented @Conditional(OnPropertyExistCondition.class) public  @interface  ConditionalOnPropertyExist {         String name ()  default  "" ;          boolean  exist ()  default  true ; } 
 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 public  class  OnPropertyExistCondition  implements  Condition  {         @Override      public  boolean  matches (ConditionContext conditionContext, AnnotatedTypeMetadata annotatedTypeMetadata)  {         Map<String, Object> annotationAttributes = annotatedTypeMetadata.getAnnotationAttributes(ConditionalOnPropertyExist.class.getName());         if  (annotationAttributes == null ) {             return  false ;         }                  String  propertyName  =  (String) annotationAttributes.get("name" );         boolean  values  =  (boolean ) annotationAttributes.get("exist" );         String  propertyValue  =  conditionContext.getEnvironment().getProperty(propertyName);         if (values) {             return  !StringUtils.isEmpty(propertyValue);         } else  {             return  StringUtils.isEmpty(propertyValue);         }     } } 
 
源码详解 
条件判断的触发时机 
Spring 在以下阶段触发条件判断:
阶段  
触发场景  
相关源码位置  
 
 
配置类解析阶段 
解析 @Configuration 类时 
ConfigurationClassParser 
 
Bean 方法注册阶段 
处理 @Bean 方法时 
ConfigurationClassBeanDefinitionReader 
 
组件扫描阶段 
扫描 @Component 及其派生注解时 
ClassPathBeanDefinitionScanner 
 
 
ConditionEvaluator 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 class  ConditionEvaluator  {    private  final  ConditionContextImpl context;     public  ConditionEvaluator (@Nullable  BeanDefinitionRegistry registry,              @Nullable  Environment environment, @Nullable  ResourceLoader resourceLoader)  {        this .context = new  ConditionContextImpl (registry, environment, resourceLoader);     }     public  boolean  shouldSkip (AnnotatedTypeMetadata metadata)  {         return  shouldSkip(metadata, null );     }          public  boolean  shouldSkip (@Nullable  AnnotatedTypeMetadata metadata, @Nullable  ConfigurationPhase phase)  {                  if  (metadata == null  || !metadata.isAnnotated(Conditional.class.getName())) {             return  false ;         }         if  (phase == null ) {             if  (metadata instanceof  AnnotationMetadata &&                     ConfigurationClassUtils.isConfigurationCandidate((AnnotationMetadata) metadata)) {                 return  shouldSkip(metadata, ConfigurationPhase.PARSE_CONFIGURATION);             }             return  shouldSkip(metadata, ConfigurationPhase.REGISTER_BEAN);         }                  List<Condition> conditions = new  ArrayList <>();         for  (String[] conditionClasses : getConditionClasses(metadata)) {             for  (String conditionClass : conditionClasses) {                                  Condition  condition  =  getCondition(conditionClass, this .context.getClassLoader());                 conditions.add(condition);             }         }         AnnotationAwareOrderComparator.sort(conditions);                  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 ;     }      } 
 
SpringBootCondition 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 public  abstract  class  SpringBootCondition  implements  Condition  {    private  final  Log  logger  =  LogFactory.getLog(getClass());     @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 "  + 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);         }     }     private  String getName (AnnotatedTypeMetadata metadata)  {                  if  (metadata instanceof  AnnotationMetadata) {             return  ((AnnotationMetadata) metadata).getClassName();         }                  if  (metadata instanceof  MethodMetadata) {             MethodMetadata  methodMetadata  =  (MethodMetadata) metadata;             return  methodMetadata.getDeclaringClassName() + "."  + methodMetadata.getMethodName();         }         return  metadata.toString();     }     private  static  String getClassOrMethodName (AnnotatedTypeMetadata metadata)  {         if  (metadata instanceof  ClassMetadata) {             ClassMetadata  classMetadata  =  (ClassMetadata) metadata;             return  classMetadata.getClassName();         }         MethodMetadata  methodMetadata  =  (MethodMetadata) metadata;         return  methodMetadata.getDeclaringClassName() + "#"  + methodMetadata.getMethodName();     }          protected  final  void  logOutcome (String classOrMethodName, ConditionOutcome outcome)  {         if  (this .logger.isTraceEnabled()) {             this .logger.trace(getLogMessage(classOrMethodName, outcome));         }     }     private  StringBuilder getLogMessage (String classOrMethodName, ConditionOutcome outcome)  {         StringBuilder  message  =  new  StringBuilder ();         message.append("Condition " );         message.append(ClassUtils.getShortName(getClass()));         message.append(" on " );         message.append(classOrMethodName);         message.append(outcome.isMatch() ? " matched"  : " did not match" );         if  (StringUtils.hasLength(outcome.getMessage())) {             message.append(" due to " );             message.append(outcome.getMessage());         }         return  message;     }          private  void  recordEvaluation (ConditionContext context, String classOrMethodName, ConditionOutcome outcome)  {                  if  (context.getBeanFactory() != null ) {             ConditionEvaluationReport.get(context.getBeanFactory()).recordConditionEvaluation(classOrMethodName, this ,                     outcome);         }     }     public  abstract  ConditionOutcome getMatchOutcome (ConditionContext context, AnnotatedTypeMetadata metadata) ;          protected  final  boolean  anyMatches (ConditionContext context, AnnotatedTypeMetadata metadata,              Condition... conditions)  {        for  (Condition condition : conditions) {             if  (matches(context, metadata, condition)) {                 return  true ;             }         }         return  false ;     }          protected  final  boolean  matches (ConditionContext context, AnnotatedTypeMetadata metadata, Condition condition)  {         if  (condition instanceof  SpringBootCondition) {             return  ((SpringBootCondition) condition).getMatchOutcome(context, metadata).isMatch();         }         return  condition.matches(context, metadata);     } } 
 
OnPropertyCondition 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 @Order(Ordered.HIGHEST_PRECEDENCE + 40) class  OnPropertyCondition  extends  SpringBootCondition  {         @Override      public  ConditionOutcome getMatchOutcome (ConditionContext context, AnnotatedTypeMetadata metadata)  {                  List<AnnotationAttributes> allAnnotationAttributes = annotationAttributesFromMultiValueMap(                 metadata.getAllAnnotationAttributes(ConditionalOnProperty.class.getName()));                  List<ConditionMessage> noMatch = new  ArrayList <>();                  List<ConditionMessage> match = new  ArrayList <>();                  for  (AnnotationAttributes annotationAttributes : allAnnotationAttributes) {                          ConditionOutcome  outcome  =  determineOutcome(annotationAttributes, context.getEnvironment());                          (outcome.isMatch() ? match : noMatch).add(outcome.getConditionMessage());         }                  if  (!noMatch.isEmpty()) {             return  ConditionOutcome.noMatch(ConditionMessage.of(noMatch));         }                  return  ConditionOutcome.match(ConditionMessage.of(match));     }          private  List<AnnotationAttributes> annotationAttributesFromMultiValueMap (              MultiValueMap<String, Object> multiValueMap)  {        List<Map<String, Object>> maps = new  ArrayList <>();         multiValueMap.forEach((key, value) -> {             for  (int  i  =  0 ; i < value.size(); i++) {                 Map<String, Object> map;                 if  (i < maps.size()) {                     map = maps.get(i);                 }                 else  {                     map = new  HashMap <>();                     maps.add(map);                 }                 map.put(key, value.get(i));             }         });         List<AnnotationAttributes> annotationAttributes = new  ArrayList <>(maps.size());         for  (Map<String, Object> map : maps) {             annotationAttributes.add(AnnotationAttributes.fromMap(map));         }         return  annotationAttributes;     }           private  ConditionOutcome determineOutcome (AnnotationAttributes annotationAttributes, PropertyResolver resolver)  {                  Spec  spec  =  new  Spec (annotationAttributes);                  List<String> missingProperties = new  ArrayList <>();                  List<String> nonMatchingProperties = new  ArrayList <>();                  spec.collectProperties(resolver, missingProperties, nonMatchingProperties);                  if  (!missingProperties.isEmpty()) {             return  ConditionOutcome.noMatch(ConditionMessage.forCondition(ConditionalOnProperty.class, spec)                     .didNotFind("property" , "properties" ).items(Style.QUOTE, missingProperties));         }                  if  (!nonMatchingProperties.isEmpty()) {             return  ConditionOutcome.noMatch(ConditionMessage.forCondition(ConditionalOnProperty.class, spec)                     .found("different value in property" , "different value in properties" )                     .items(Style.QUOTE, nonMatchingProperties));         }                  return  ConditionOutcome                 .match(ConditionMessage.forCondition(ConditionalOnProperty.class, spec).because("matched" ));     }     private  static  class  Spec  {                  private  final  String prefix;                  private  final  String havingValue;                  private  final  String[] names;                  private  final  boolean  matchIfMissing;         Spec(AnnotationAttributes annotationAttributes) {             String  prefix  =  annotationAttributes.getString("prefix" ).trim();                          if  (StringUtils.hasText(prefix) && !prefix.endsWith("." )) {                 prefix = prefix + "." ;             }             this .prefix = prefix;             this .havingValue = annotationAttributes.getString("havingValue" );             this .names = getNames(annotationAttributes);             this .matchIfMissing = annotationAttributes.getBoolean("matchIfMissing" );         }         private  String[] getNames(Map<String, Object> annotationAttributes) {             String[] value = (String[]) annotationAttributes.get("value" );             String[] name = (String[]) annotationAttributes.get("name" );                          Assert.state(value.length > 0  || name.length > 0 ,                     "The name or value attribute of @ConditionalOnProperty must be specified" );                          Assert.state(value.length == 0  || name.length == 0 ,                     "The name and value attributes of @ConditionalOnProperty are exclusive" );             return  (value.length > 0 ) ? value : name;         }                  private  void  collectProperties (PropertyResolver resolver, List<String> missing, List<String> nonMatching)  {                          for  (String name : this .names) {                 String  key  =  this .prefix + name;                                  if  (resolver.containsProperty(key)) {                     if  (!isMatch(resolver.getProperty(key), this .havingValue)) {                         nonMatching.add(name);                     }                 }                 else  {                                          if  (!this .matchIfMissing) {                         missing.add(name);                     }                 }             }         }         private  boolean  isMatch (String value, String requiredValue)  {             if  (StringUtils.hasLength(requiredValue)) {                 return  requiredValue.equalsIgnoreCase(value);             }             return  !"false" .equalsIgnoreCase(value);         }         @Override          public  String toString ()  {             StringBuilder  result  =  new  StringBuilder ();             result.append("(" );             result.append(this .prefix);             if  (this .names.length == 1 ) {                 result.append(this .names[0 ]);             }             else  {                 result.append("[" );                 result.append(StringUtils.arrayToCommaDelimitedString(this .names));                 result.append("]" );             }             if  (StringUtils.hasLength(this .havingValue)) {                 result.append("=" ).append(this .havingValue);             }             result.append(")" );             return  result.toString();         }     } }