zoukankan      html  css  js  c++  java
  • 【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();
    }
  • 相关阅读:
    Python中的生成器与yield
    【爬虫系列】(一)最简单的爬虫
    【刷题笔记】--lintcode木头加工(java)
    使用TaskManager爬取2万条代理IP实现自动投票功能
    开源任务管理平台TaskManager介绍
    数据字典生成工具之旅系列文章导航
    使用工具安装,运行,停止,卸载Window服务
    Quartz Cron表达式 在线生成器
    Oracle .NET Core Beta驱动已出,自己动手写EF Core Oracle
    .net core2.0下Ioc容器Autofac使用
  • 原文地址:https://www.cnblogs.com/clarino/p/15211958.html
Copyright © 2011-2022 走看看