基于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. [转]C#中的自定义事件和EventHandler的使用

    自定义事件: 这里主要模拟刷银行卡,手机提示刷卡信息的过程.     声明一个委托类型 public delegate void DelMethod(string bankName,decimal d ...

  2. selenium执行下载多个文件操作,谷歌浏览器弹出"xxx想要下载多个文件"的处理方法

    背景:   使用selenium框架,批量下载多个目录的不同文件,而下载多个文件时,浏览器会弹出如下窗口 解决方案有2个:1.代码定位到元素并点击[允许].2.修改浏览器的设置,使其能够拥有自动下载的 ...

  3. Linux的top命令原理简单了解

    top命令描述机器的cpu.内存等状态信息. 每3s刷新一次. 是procps工具集中的一个,该工具集还包括free.ps等等 top命令的代码实现逻辑是:由内核动态生成一个伪文件系统,提供一个内核状 ...

  4. py10函数之嵌套-名称空间作用域

    # 函数是第一类对象:函数名指向的值可以被当中参数传递 # 1.函数名可以被传递# name = 'jason'# x = name# print(x)# print(id(x))# def func ...

  5. Physics Informed Deep Learning for Flow and Transport in Porous Media

    Paper presented at the SPE Reservoir Simulation Conference, On-Demand, October 2021. 这篇论文关注石油储藏模拟问题, ...

  6. Go实现KMP和Sunday算法

    KMP 1 func KMP(str, substr string) int { 2 if substr == "" { 3 return 0 4 } 5 strLen := le ...

  7. C# List GroupBy and Sum

    List<PartRequest> partRequests = new List<PartRequest>(); partRequests.Add(new PartReque ...

  8. fix: because the volume group on the selected device also consist of physical volumes on other devices

    because the volume group on the selected device also consist of physical volumes on other devices 目标 ...

  9. uniapp 通用函数说明

    onLoad函数      监听页面加载,在onLoad中发送请求是比较合适的,即页面一加载就发送请求获取数据,option接受其他界面传过来的数据,数据类型为obj onLoad(option) { ...

  10. adb 测试常用命令

    adb 环境安装并配置环境变量 常用命令汇总: 1.查看电脑已经连接的手机设备 adb devices 2.通过logcat日志获取应用APP入口 mac/linux:adb logcat Activ ...