@Conditional注解分析,SpringBoot自动化配置的关键
基于SpringBoot 2.1.5.RELEASE分析
@Conditional系列注解
@Conditional系列注解是SpringBoot自动化配置的核心要点之一,主要用于设定条件,在达到一定条件的情况下才能注册Bean。看下@Conditional注解的定义
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Conditional {
/**
* All {@link Condition Conditions} that must {@linkplain Condition#matches match}
* in order for the component to be registered.
*/
Class<? extends Condition>[] value();
}
看定义及注释可知,value是要传入继承于Condition的Class数组,要在满足所有匹配的条件后才会注册组件。Condition如下
@FunctionalInterface
public interface Condition {
boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata);
}
Spring设定的@Conditional系列注解有以下几种,位于包org.springframework.boot.autoconfigure.condition下
注解 | 作用 |
---|---|
@ConditionalOnBen | 当容器中存在指定的Bean时 |
@ConditionalOnClass | 当classpath中存在指定的class时 |
@ConditionalOnExpression | 当指定的EL表达式成立时 |
@ConditionalOnCloudPlatform | 当运行在指定的云平台上时时 |
@ConditionalOnJava | 当Java版本为指定的版本时 |
@ConditionalOnJndi | 当指定JDDI加载后 |
@ConditionalOnMissingBean | 当容器中没有指定的Bean时 |
@ConditionalOnMissingClass | 当classpath中没有指定的class时 |
@ConditionalOnNotWebApplication | 当运行的项目不是web项目时 |
@ConditionalOnProperty | 当指定的属性值符合期望时 |
@ConditionalOnResource | 当指定的资源存在于classpath中时 |
@ConditionalOnSingleCandidate | 当容器中存在指定的Bean且只存在一个时 |
@ConditionalOnWebApplication | 当运行的项目是web项目时 |
这里有些我也没用过,大概知道就可以了
详细分析
那么现在拉个简单的出来分析下,就你了,@ConditionalOnJava吧
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Conditional({OnJavaCondition.class})
public @interface ConditionalOnJava {
ConditionalOnJava.Range range() default ConditionalOnJava.Range.EQUAL_OR_NEWER;
JavaVersion value();
public static enum Range {
EQUAL_OR_NEWER,
OLDER_THAN;
private Range() {
}
}
}
再看下OnJavaCondition
@Order(-2147483628)
class OnJavaCondition extends SpringBootCondition {
private static final JavaVersion JVM_VERSION = JavaVersion.getJavaVersion();
OnJavaCondition() {
}
public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) {
Map<String, Object> attributes = metadata.getAnnotationAttributes(ConditionalOnJava.class.getName());
Range range = (Range)attributes.get("range");
JavaVersion version = (JavaVersion)attributes.get("value");
return this.getMatchOutcome(range, JVM_VERSION, version);
}
protected ConditionOutcome getMatchOutcome(Range range, JavaVersion runningVersion, JavaVersion version) {
boolean match = this.isWithin(runningVersion, range, version);
String expected = String.format(range != Range.EQUAL_OR_NEWER ? "(older than %s)" : "(%s or newer)", version);
ConditionMessage message = ConditionMessage.forCondition(ConditionalOnJava.class, new Object[]{expected}).foundExactly(runningVersion);
return new ConditionOutcome(match, message);
}
private boolean isWithin(JavaVersion runningVersion, Range range, JavaVersion version) {
if (range == Range.EQUAL_OR_NEWER) {
return runningVersion.isEqualOrNewerThan(version);
} else if (range == Range.OLDER_THAN) {
return runningVersion.isOlderThan(version);
} else {
throw new IllegalStateException("Unknown range " + range);
}
}
}
OnJavaCondition里没有Condition中定义的matches方法与SpringBootCondition有关,挑点SpringBootCondition的内容看下
public abstract class SpringBootCondition implements Condition {
//...省略部分内容
public abstract ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata);
protected final boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata, Condition condition) {
return condition instanceof SpringBootCondition ? ((SpringBootCondition)condition).getMatchOutcome(context, metadata).isMatch() : condition.matches(context, metadata);
}
}
可以看到SpringBootCondition实现了Condition接口,并实现了matches方法,当condition是SpringBootCondition或子类时调用getMatchOutcome方法得到ConditionOutcome以isMatch为判断结果,getMatchOutcome方法等待继承者去实现
ConditionOutcome呢,也很简单,new的时候直接传入match和message就行了
public class ConditionOutcome {
private final boolean match;
private final ConditionMessage message;
public ConditionOutcome(boolean match, String message) {
this(match, ConditionMessage.of(message, new Object[0]));
}
//...省略部分内容
}
其它也大致如此,简单明了,那自定义一个应该也不难
自定义条件判断注解
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Conditional({OnTestCondition.class})
public @interface ConditionalOnTest {
String value();
}
条件判断如下,message用于日志打印
public class OnTestCondition extends SpringBootCondition {
@Override
public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) {
Map<String, Object> attributes = metadata.getAnnotationAttributes(ConditionalOnTest.class.getName());
String value = (String) attributes.get("value");
if("123".equals(value)){
return new ConditionOutcome(true, "test yes");
}
return new ConditionOutcome(false, "test no");
}
}
找个地用上这个注解
@ConditionalOnTest("123")
@RestController
@RequestMapping("/")
public class IndexController {
@RequestMapping("hello")
public JSONObject hello() {
JSONObject result = new JSONObject();
result.put("1", "test");
return result;
}
}
写在了controller上做下测试,这样当@ConditionalOnTest中的值是123时,就能会注册IndexController,访问/hello能看到test,否则就不能。
还可以在属性配置文件application.yml中添加属性"debug: true",打开debug开关,然后在启动项目时,可以看到日志输出,这里会出现我们设置的message信息
============================
CONDITIONS EVALUATION REPORT
============================
...
IndexController matched:
- test yes (OnTestCondition)
...
补充
那么@Conditional系列注解执行的地方在哪?在@Conditional同路径的ConditionEvaluator类中shouleSkip方法,如果方法结果为true则跳过这个bean的注册
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;
}
}
就在这condition.matches(this.context, metadata),进行了注册的条件判断