【SpringBoot】@Conditional注解家族

一、整体

  ConditionalOnXXX注解来自于SpringBoot实现。

        以@ConditionalOnProperty 为例说明使用场景:摘抄自 https://www.baeldung.com/spring-conditionalonproperty

    Typically, when developing Spring-based applications, we may need to create some beans conditionally based on the presence and the value of a configuration property.

    For example, we may want to register a DataSource bean to point to a production or a test database depending on if we set a property value to “prod” or “test”.

       主要在spring-boot-autoconfiguresrcmainjavaorgspringframeworkootautoconfigure目录下定义

  常见的定义主要位于condition目录,当然security等目录也存在部分:粗体部分为常见的

  • ConditionalOnBean.java
  • ConditionalOnClass.java
  • ConditionalOnCloudPlatform.java
  • ConditionalOnExpression.java
  • ConditionalOnJava.java
  • ConditionalOnJndi.java
  • ConditionalOnMissingBean.java
  • ConditionalOnMissingClass.java
  • ConditionalOnNotWebApplication.java
  • ConditionalOnProperty.java
  • ConditionalOnResource.java
  • ConditionalOnSingleCandidate.java
  • ConditionalOnWarDeployment.java
  • ConditionalOnWebApplication.java
  • ConditionEvaluationReport.java
  • ConditionEvaluationReportAutoConfigurationImportListener.java
  • ConditionMessage.java
  • ConditionOutcome.java

样例:https://github.com/eugenp/tutorials/tree/master/spring-boot-modules/spring-boot-annotations/src/main/java/com/baeldung/annotations/conditional 

二、注解实现分析(以ConditionalOnProperty为例,其他类似

  源码实现:

 1 @Retention(RetentionPolicy.RUNTIME)
 2 @Target({ ElementType.TYPE, ElementType.METHOD })
 3 @Documented
 4 @Conditional(OnPropertyCondition.class)
 5 public @interface ConditionalOnProperty {
 6 
 7     /**
 8      * Alias for {@link #name()}.
 9      * @return the names
10      */
11     String[] value() default {};
12 
13     /**
14      * A prefix that should be applied to each property. The prefix automatically ends
15      * with a dot if not specified. A valid prefix is defined by one or more words
16      * separated with dots (e.g. {@code "acme.system.feature"}).
17      * @return the prefix
18      */
19     String prefix() default "";
20 
21     /**
22      * The name of the properties to test. If a prefix has been defined, it is applied to
23      * compute the full key of each property. For instance if the prefix is
24      * {@code app.config} and one value is {@code my-value}, the full key would be
25      * {@code app.config.my-value}
26      * <p>
27      * Use the dashed notation to specify each property, that is all lower case with a "-"
28      * to separate words (e.g. {@code my-long-property}).
29      * @return the names
30      */
31     String[] name() default {};
32 
33     /**
34      * The string representation of the expected value for the properties. If not
35      * specified, the property must <strong>not</strong> be equal to {@code false}.
36      * @return the expected value
37      */
38     String havingValue() default "";
39 
40     /**
41      * Specify if the condition should match if the property is not set. Defaults to
42      * {@code false}.
43      * @return if should match if the property is missing
44      */
45     boolean matchIfMissing() default false;
46 
47 }
View Code

关注HavingValue为""时的匹配逻辑,有值时要求精确匹配

   其中注解 @Conditional(OnPropertyCondition.class) 是实现的核心

        @Conditional:来自于spring-framework

        OnPropertyCondition.class:来自于ConditionalOnProperty同目录

  1 @Order(Ordered.HIGHEST_PRECEDENCE + 40)
  2 class OnPropertyCondition extends SpringBootCondition {
  3 
  4     @Override
  5     public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) {
  6         List<AnnotationAttributes> allAnnotationAttributes = annotationAttributesFromMultiValueMap(
  7                 metadata.getAllAnnotationAttributes(ConditionalOnProperty.class.getName()));
  8         List<ConditionMessage> noMatch = new ArrayList<>();
  9         List<ConditionMessage> match = new ArrayList<>();
 10         for (AnnotationAttributes annotationAttributes : allAnnotationAttributes) {
 11             ConditionOutcome outcome = determineOutcome(annotationAttributes, context.getEnvironment());
 12             (outcome.isMatch() ? match : noMatch).add(outcome.getConditionMessage());
 13         }
 14         if (!noMatch.isEmpty()) {
 15             return ConditionOutcome.noMatch(ConditionMessage.of(noMatch));
 16         }
 17         return ConditionOutcome.match(ConditionMessage.of(match));
 18     }
 19 
 20     private List<AnnotationAttributes> annotationAttributesFromMultiValueMap(
 21             MultiValueMap<String, Object> multiValueMap) {
 22         List<Map<String, Object>> maps = new ArrayList<>();
 23         multiValueMap.forEach((key, value) -> {
 24             for (int i = 0; i < value.size(); i++) {
 25                 Map<String, Object> map;
 26                 if (i < maps.size()) {
 27                     map = maps.get(i);
 28                 }
 29                 else {
 30                     map = new HashMap<>();
 31                     maps.add(map);
 32                 }
 33                 map.put(key, value.get(i));
 34             }
 35         });
 36         List<AnnotationAttributes> annotationAttributes = new ArrayList<>(maps.size());
 37         for (Map<String, Object> map : maps) {
 38             annotationAttributes.add(AnnotationAttributes.fromMap(map));
 39         }
 40         return annotationAttributes;
 41     }
 42 
 43     private ConditionOutcome determineOutcome(AnnotationAttributes annotationAttributes, PropertyResolver resolver) {
 44         Spec spec = new Spec(annotationAttributes);
 45         List<String> missingProperties = new ArrayList<>();
 46         List<String> nonMatchingProperties = new ArrayList<>();
 47         spec.collectProperties(resolver, missingProperties, nonMatchingProperties);
 48         if (!missingProperties.isEmpty()) {
 49             return ConditionOutcome.noMatch(ConditionMessage.forCondition(ConditionalOnProperty.class, spec)
 50                     .didNotFind("property", "properties").items(Style.QUOTE, missingProperties));
 51         }
 52         if (!nonMatchingProperties.isEmpty()) {
 53             return ConditionOutcome.noMatch(ConditionMessage.forCondition(ConditionalOnProperty.class, spec)
 54                     .found("different value in property", "different value in properties")
 55                     .items(Style.QUOTE, nonMatchingProperties));
 56         }
 57         return ConditionOutcome
 58                 .match(ConditionMessage.forCondition(ConditionalOnProperty.class, spec).because("matched"));
 59     }
 60 
 61     private static class Spec {
 62 
 63         private final String prefix;
 64 
 65         private final String havingValue;
 66 
 67         private final String[] names;
 68 
 69         private final boolean matchIfMissing;
 70 
 71         Spec(AnnotationAttributes annotationAttributes) {
 72             String prefix = annotationAttributes.getString("prefix").trim();
 73             if (StringUtils.hasText(prefix) && !prefix.endsWith(".")) {
 74                 prefix = prefix + ".";
 75             }
 76             this.prefix = prefix;
 77             this.havingValue = annotationAttributes.getString("havingValue");
 78             this.names = getNames(annotationAttributes);
 79             this.matchIfMissing = annotationAttributes.getBoolean("matchIfMissing");
 80         }
 81 
 82         private String[] getNames(Map<String, Object> annotationAttributes) {
 83             String[] value = (String[]) annotationAttributes.get("value");
 84             String[] name = (String[]) annotationAttributes.get("name");
 85             Assert.state(value.length > 0 || name.length > 0,
 86                     "The name or value attribute of @ConditionalOnProperty must be specified");
 87             Assert.state(value.length == 0 || name.length == 0,
 88                     "The name and value attributes of @ConditionalOnProperty are exclusive");
 89             return (value.length > 0) ? value : name;
 90         }
 91 
 92         private void collectProperties(PropertyResolver resolver, List<String> missing, List<String> nonMatching) {
 93             for (String name : this.names) {
 94                 String key = this.prefix + name;
 95                 if (resolver.containsProperty(key)) {
 96                     if (!isMatch(resolver.getProperty(key), this.havingValue)) {
 97                         nonMatching.add(name);
 98                     }
 99                 }
100                 else {
101                     if (!this.matchIfMissing) {
102                         missing.add(name);
103                     }
104                 }
105             }
106         }
107 
108         private boolean isMatch(String value, String requiredValue) {
109             if (StringUtils.hasLength(requiredValue)) {
110                 return requiredValue.equalsIgnoreCase(value);
111             }
112             return !"false".equalsIgnoreCase(value);
113         }
114 
115         @Override
116         public String toString() {
117             StringBuilder result = new StringBuilder();
118             result.append("(");
119             result.append(this.prefix);
120             if (this.names.length == 1) {
121                 result.append(this.names[0]);
122             }
123             else {
124                 result.append("[");
125                 result.append(StringUtils.arrayToCommaDelimitedString(this.names));
126                 result.append("]");
127             }
128             if (StringUtils.hasLength(this.havingValue)) {
129                 result.append("=").append(this.havingValue);
130             }
131             result.append(")");
132             return result.toString();
133         }
134 
135     }
136 
137 }
View Code

  ConditionalOnProperty关键实现:

    @Order(Ordered.HIGHEST_PRECEDENCE + 40)
    class OnPropertyCondition extends SpringBootCondition : 继承自 SpringBootCondition 

  而SpringBootCondition实现: 

    public abstract class SpringBootCondition implements Condition  //  继承自org.springframework.context.annotation.Condition;


 三、@ConditionalOnProperty使用

1、定义

 1 public interface NotificationSender {
 2     String send(String message);
 3 }
 4 
 5 public class EmailNotification implements NotificationSender {
 6     @Override
 7     public String send(String message) {
 8         return "Email Notification: " + message;
 9     }
10 }

使用1:

     配置当参数 service 存在的时候,bean才被定义

@Bean(name = "emailNotification")
@ConditionalOnProperty(name = "service")
public NotificationSender notificationSender() {
    return new EmailNotification();
}

依赖 application.properties必须包含 notification.service=email

使用2:同时配置 prefix和name字段,则属性名 prefix.name 

    1、新增一种通知实现SMS

public class SmsNotification implements NotificationSender {
    @Override
    public String send(String message) {
        return "SMS Notification: " + message;
    }
}

    2、配置:当且仅当 notification.service is set to sms. 才注册bean

@Bean(name = "smsNotification")
@ConditionalOnProperty(prefix = "notification", name = "service", havingValue = "sms")
public NotificationSender notificationSender2() {
    return new SmsNotification();
}
原文地址:https://www.cnblogs.com/clarino/p/15211958.html