@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),进行了注册的条件判断

posted @ 2022-01-05 16:29  lixuelong  阅读(207)  评论(0编辑  收藏  举报