一、整体
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 }
关注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 }
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(); }