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

@Conditional注解分析,SpringBoot自动化配置的关键的更多相关文章

  1. springboot笔记-1.自动化配置的关键

    最近发现看过的东西容易忘,但是写一遍之后印象倒是会深刻的多. 总所周知springboot极大的简化了java开发繁琐性,而其最大的优势应该就是自动化配置了.比如要使用redis,我们直接引入相关的包 ...

  2. SpringBoot自动化配置之二:自动配置(AutoConfigure)原理、EnableAutoConfiguration、condition

    自动配置绝对算得上是Spring Boot的最大亮点,完美的展示了CoC约定优于配置: Spring Boot能自动配置Spring各种子项目(Spring MVC, Spring Security, ...

  3. SpringBoot自动化配置之四:SpringBoot 之Starter(自动配置)、Command-line runners

    Spring Boot Starter是在SpringBoot组件中被提出来的一种概念,stackoverflow上面已经有人概括了这个starter是什么东西,想看完整的回答戳这里 Starter ...

  4. SpringBoot自动化配置之一:SpringBoot内部的一些自动化配置原理

    springboot用来简化Spring框架带来的大量XML配置以及复杂的依赖管理,让开发人员可以更加关注业务逻辑的开发. 比如不使用springboot而使用SpringMVC作为web框架进行开发 ...

  5. 让SpringBoot自动化配置不再神秘

    本文若有任何纰漏.错误,还请不吝指出! 注:本文提到的Spring容器或者Bean容器,或者Spring Bean容器,都是指同一个事情,那就是代指BeanFactory.关于BeanFactory, ...

  6. SpringBoot自动化配置之四:@Conditional注解详解

    前言 之前在分析spring boot 源码时导出可见@ConditionalOnBean 之类的注解,那么它到底是如何使用的以及其工作流程如何,我们这里就围绕以下几点来分析: @Conditiona ...

  7. SpringBoot自动化配置的注解开关原理

    我们以一个最简单的例子来完成这个需求:定义一个注解EnableContentService,使用了这个注解的程序会自动注入ContentService这个bean. @Retention(Retent ...

  8. SpringBoot自动化配置之三:深入SpringBoot:自定义EnableAutoConfiguration

    前言 上面几篇文章介绍了SpringFramework的一些原理,这里开始介绍一下SpringBoot,并通过自定义一些功能来介绍SpringBoot的原理.SpringBoot在SpringFram ...

  9. SpringBoot 为什么能够自动的注入一些常用的Bean ?详细分析SpringBoot 自动配置的实现

    转载至:https://blog.csdn.net/qq_29941401/article/details/79605388 有一个问题一直让我好奇,为什么在SpringBoot中有的bean 我们都 ...

  10. SpringBoot自动配置(装配)流程

    源码分析 SpringBoot自动配置流程 ​ ​ 首先,我们要了解在@SpringBootApplication注解的内部,还具有@EnableAutoConfiguration,@SpringBo ...

随机推荐

  1. python随机数模拟

    `#随机数 import random red = range(1,36,1) red_target_list=[] i=1 while i< 6: red_ran_index=random.r ...

  2. drf Serializer基本使用

    drf序列化 在前后端不分离的项目中,可以使用Django自带的forms组件进行数据验证,也可以使用Django自带的序列化组件对模型表数据进行序列化. 那么在前后端分离的项目中,drf也提供了数据 ...

  3. vscode 终端中运行执行js文件

    问题汇总 1.在vscode中执行node -v没有反应或者执行js文件出现下图错误 解决办法: 1.先关闭vscode,找到vscode的执行文件,在兼容性中勾上以管理员身份运行此程序,该问题win ...

  4. python bottle小记

    # coding=utf-8import bottle @bottle.route('/url/url', method=['GET','POST'])def big_data(): # 获取请求参数 ...

  5. strace 跟踪特定系统调用

    /usrdsk/strace -tt -e trace=write   -p 664 /usrdsk/strace -tt -e trace=%memory   -p 664

  6. ESXI密码正确无法登录

    场景描述: 今天新安装了一个VMware ESXi 6.5的系统,密码仍然用的习惯采用的密码.但在使用中,无论是使用vSphere Client连接,还是在vCenter Server中添加这台ESX ...

  7. mac上gitclone出现password: Permission denied, please try again.

    问题在mac上拉取项目出现一下问题,然后我输入密码还是这样的提示. password: Permission denied, please try again.1分析解决情况1:git地址为http协 ...

  8. RTC@@@Real-Time Clock(实时时钟的简称)及电路问题分析

    RTC@@@Real-Time Clock(实时时钟的简称) 实时时钟(Real-Time Clock)是PC主板上的晶振及相关电路组成的时钟电路的生成脉冲,提供稳定的时钟信号给后续电路用.主要功能有 ...

  9. JavaScript的原型和原型链

    说到JavaScript的原型和原型链,相关文章已有不少,但是大都晦涩难懂.本文将换一个角度出发,先理解原型和原型链是什么,有什么作用,再去分析那些令人头疼的关系. 一.引用类型皆为对象 原型和原型链 ...

  10. [CSP-S2019] Emiya 家今天的饭

    洛咕 题意:原题面见链接,简单来说就是给出一个\(n*m\)的矩阵,每一行代表同一种烹饪方法,每一列代表同一种食材,\(a_{i,j}\)表示使用第i种烹饪方法第j种食材能做出多少种菜,要求至少做一道 ...