@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),进行了注册的条件判断
@Conditional注解分析,SpringBoot自动化配置的关键的更多相关文章
- springboot笔记-1.自动化配置的关键
最近发现看过的东西容易忘,但是写一遍之后印象倒是会深刻的多. 总所周知springboot极大的简化了java开发繁琐性,而其最大的优势应该就是自动化配置了.比如要使用redis,我们直接引入相关的包 ...
- SpringBoot自动化配置之二:自动配置(AutoConfigure)原理、EnableAutoConfiguration、condition
自动配置绝对算得上是Spring Boot的最大亮点,完美的展示了CoC约定优于配置: Spring Boot能自动配置Spring各种子项目(Spring MVC, Spring Security, ...
- SpringBoot自动化配置之四:SpringBoot 之Starter(自动配置)、Command-line runners
Spring Boot Starter是在SpringBoot组件中被提出来的一种概念,stackoverflow上面已经有人概括了这个starter是什么东西,想看完整的回答戳这里 Starter ...
- SpringBoot自动化配置之一:SpringBoot内部的一些自动化配置原理
springboot用来简化Spring框架带来的大量XML配置以及复杂的依赖管理,让开发人员可以更加关注业务逻辑的开发. 比如不使用springboot而使用SpringMVC作为web框架进行开发 ...
- 让SpringBoot自动化配置不再神秘
本文若有任何纰漏.错误,还请不吝指出! 注:本文提到的Spring容器或者Bean容器,或者Spring Bean容器,都是指同一个事情,那就是代指BeanFactory.关于BeanFactory, ...
- SpringBoot自动化配置之四:@Conditional注解详解
前言 之前在分析spring boot 源码时导出可见@ConditionalOnBean 之类的注解,那么它到底是如何使用的以及其工作流程如何,我们这里就围绕以下几点来分析: @Conditiona ...
- SpringBoot自动化配置的注解开关原理
我们以一个最简单的例子来完成这个需求:定义一个注解EnableContentService,使用了这个注解的程序会自动注入ContentService这个bean. @Retention(Retent ...
- SpringBoot自动化配置之三:深入SpringBoot:自定义EnableAutoConfiguration
前言 上面几篇文章介绍了SpringFramework的一些原理,这里开始介绍一下SpringBoot,并通过自定义一些功能来介绍SpringBoot的原理.SpringBoot在SpringFram ...
- SpringBoot 为什么能够自动的注入一些常用的Bean ?详细分析SpringBoot 自动配置的实现
转载至:https://blog.csdn.net/qq_29941401/article/details/79605388 有一个问题一直让我好奇,为什么在SpringBoot中有的bean 我们都 ...
- SpringBoot自动配置(装配)流程
源码分析 SpringBoot自动配置流程 首先,我们要了解在@SpringBootApplication注解的内部,还具有@EnableAutoConfiguration,@SpringBo ...
随机推荐
- 【Java学习day04】Java文件的创建和Java代码的执行
Java文件的创建和Java代码的执行 随便新建一个文件夹,存放代码 在新建的文件夹里新建一个java文件 新建一个文本文档 将新建的文本文档重命名为hello.java 注意了!后缀必须改为.jav ...
- Vue 数组响应
响应渲染 在Vue中,被渲染的数据都是响应式的,即Vue实例中进行改变页面中也会跟着改变: <body> <div id="app"> <p>{ ...
- python的惊艳之举--源于一个同事分享16种字符串反转方式
日期:2018/12/19 最近刚学习了Python的入门课程,准备寻找你的进阶门路,偶尔得到一个老同事微信分享,标题让我这种greenhand心砰砰砰... 好了,看标题: 面试官让用 5 种 py ...
- 自定义select组件
(声明:当前记录篇参考于该人员 https://www.jb51.net/article/166679.htm ) 一.创建组件 1.新建文件夹:select 2.新建Component: selec ...
- python学习:sqlite3 文件型数据库
摘录:https://www.cnblogs.com/decwang/p/4565572.html SQLite 字段类型 一般数据采用的固定的静态数据类型,而SQLite采用的是动态数据类型,会根据 ...
- 集群分发xsync xcall kafka启动脚本命令,命令方式安装epel源
安装epel库源 yum install epel-release -y --nogpgcheck yum install glances 安装开始 建立hosts 白名单 127.0.0.1 loc ...
- 解题报告:Codeforces 768B Code For 1
Codeforces 768B Code For 1 题义 有一个序列,刚开始,只有1个数\(n\),接着按照以下规则变化找到序列中任意一个\(>1\)的数\(p\),将他变为 \(\lfloo ...
- windows下C#读文件遇到一个路径的天坑
在win下面准备了一个文件,右键属性然后"安全"复制完整的文件路径, 放到C#代码(vs中编写)中来读取,竟然死活异常: System.IO.IOException HResult ...
- 220327_IDEA调试debug时step into看不了方法内部的解决办法
220327_问题解决_IDEA Debug时stepinto无法进入方法内部的解决方法 File Settings Build,Execution,Deployment Debugger Stepp ...
- Flutter中的路由 路由替换 返回到根路由
一.Flutter 中返回到上一级页面 Navigator.of(context).pop(); 二.Flutter 中替换路由 比如我们从用户中心页面跳转到了 registerFirst 页面,然后 ...