ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

@Conditional注解分析,SpringBoot自动化配置的关键

2022-01-05 16:37:23  阅读:171  来源: 互联网

标签:SpringBoot Conditional Range public range 注解 class condition metadata


基于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,否则就不能

补充

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

标签:SpringBoot,Conditional,Range,public,range,注解,class,condition,metadata
来源: https://www.cnblogs.com/lixuelong/p/15767633.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有