1. 概述
在前面的文章,我们已经看过 Spring Boot 如何实现自动配置的功能,但是,实际场景下,这显然不够。为什么呢?因为每个框架的配置,需要满足一定的条件,才应该进行自动配置。这时候,我们很自然就可以想到 Spring Boot 的 Condition 功能。不过呢,Condition 功能并不是 Spring Boot 所独有,而是在 Spring Framework 中就已经提供了。那么,究竟是什么样的关系呢,我们在 「2. Condition 演进史」 来瞅瞅。
2. Condition 演进史
2.1 Profile 的出场
在 Spring3.1 的版本,为了满足不同环境注册不同的 Bean ,引入了 @Profile
注解。例如:
|
- 在测试环境下,我们注册单机 MySQL 的 DataSource Bean 。
- 在生产环境下,我们注册集群 MySQL 的 DataSource Bean 。
org.springframework.context.annotation.@Profile
,代码如下:
// Profile.java |
- 这是 Spring5 版本的
@Profile
注解的代码。它已经是经过 Condition 改造的实现。详细的,我们放在 「2.2 Condition」 。 -
让我们在来看一眼 Spring3 版本的
@Profile
注解的代码。如下:// Profile.java
(RetentionPolicy.RUNTIME)
({
ANNOTATION_TYPE, // @Profile may be used as a meta-annotation
TYPE // In conjunction with @Component and its derivatives
})
public Profile {
static final String CANDIDATE_PROFILES_ATTRIB_NAME = "value";
String[] value();
}- 可以大体猜出,此时并没有将 Profile 作为 Condition 的一种情况。
2.2 Condition 的出现
在 Spring4 的版本,正式出现 Condition 功能,体现在 org.springframework.context.annotation.Condition
接口,代码如下:
// Condition.java |
- 很简洁的一个接口,只有一个
#matches(...)
方法,用于判断是佛匹配。从参数中就可以看出,它是和注解配合,而这个注解便是@Conditional
。
org.springframework.context.annotation.@Conditional
注解,也是在 Spring4 的版本,一起出现。代码如下:
// Conditional.java |
- 可以注解在方法、或者在类上,表示需要满足的条件(Condition)。
- 在 「2.1 Profile 的出现」 小节中,我们已经看到
@Profile
上,有@Conditional(ProfileCondition.class)
的注解,表示使用org.springframework.context.annotation.ProfileCondition
作为条件。 -
当然,我们也可以直接在 Configuration 类上使用。例如:
public class TestConfiguration {
(XXXCondition.class)
public Object xxxObject() {
return new Object();
}
}- 即,创建
#xxxObject()
方法对应的 Bean 对象,需要满足 XXXCondition 条件。
- 即,创建
在 Spring5 中,艿艿整理了下目前提供的 Condition 实现类,如下图:
- 显然,默认提供的 Condition 实现类非常少。
2.3 SpringBootCondition 的进击
为了满足更加丰富的 Condition(条件)的需要,Spring Boot 进一步拓展了更多的实现类,如下图所示:
-
org.springframework.boot.autoconfigure.condition.SpringBootCondition
,是 Spring Boot 实现 Condition 的抽象类,且是 Spring Boot 所有 Condition 实现类的基类。 - 分别对应如下注解:
-
@ConditionalOnBean
:当容器里有指定 Bean 的条件下。 -
@ConditionalOnMissingBean
:当容器里没有指定 Bean 的情况下。 -
@ConditionalOnSingleCandidate
:当指定 Bean 在容器中只有一个,或者虽然有多个但是指定首选 Bean 。 -
@ConditionalOnClass
:当类路径下有指定类的条件下。 -
@ConditionalOnMissingClass
:当类路径下没有指定类的条件下。 -
@ConditionalOnProperty
:指定的属性是否有指定的值 -
@ConditionalOnResource
:类路径是否有指定的值 -
@ConditionalOnExpression
:基于 SpEL 表达式作为判断条件。 -
@ConditionalOnJava
:基于 Java 版本作为判断条件 -
@ConditionalOnJndi
:在 JNDI 存在的条件下差在指定的位置 -
@ConditionalOnNotWebApplication
:当前项目不是 Web 项目的条件下 -
@ConditionalOnWebApplication
:当前项目是 Web项 目的条件下。
-
2.4 小结
到了此处,我们基本算是理清了 Condition 的整个演进构成:
-
@Profile
注解,在 Spring3.1 提出,可以作为是 Condition 的雏形。 - Condition 接口,在 Spring4 提出,是 Condition 的正式出现。
- SpringCondition 抽象类,在 Spring Boot 实现,是对 Condition 进一步拓展。
下面,我们就正式开始撸 Condition 相关的源码落。
3. Condition 如何生效?
在上面的文章中,我们已经看到,@Conditional
注解,可以添加:
- 类级别上
- 方法级别上
添加到注解上,相当于添加到类级别或者方法级别上。
并且,一般情况下我们和配置类(Configuration)一起使用,但是实际上,我们也可以添加到普通的 Bean 类上。例如:
// DemoController.java |
那么,究竟 Condition 是如何生效的呢?分成两种情况:
- 方式一,配置类。添加到配置类(Configuration)上面。
-
方式二,创建 Bean 对象。添加到配置类(Configuration)、或者 Bean Class 的上面。
本质上,方式二上的两种,都是创建 Bean 对象,所以统一处理方式即可。
假设,我们在 TestConfiguration 这个示例下进行测试,看看具体的调用链。代码如下:
// TestConfiguration.java |
本小节,不会讲特别细的源码。
3.1 方式一:配置类
在 TestCondition 的 #matches(...)
方法中,打个断点。看看方式一情况下的具体的表现。如下图所示:
- 通过调用
Condition#matches(...)
方法,判断该是否匹配。如果不匹配,内部所有方法,都无法创建 Bean 对象。
3.2 方式二:创建 Bean 对象
在 TestCondition 的 #matches(...)
方法中,打个断点。看看方式二情况下的具体的表现。如下图所示:
- 通过调用
Condition#matches(...)
方法,判断是否匹配。如果吧匹配,则不从该方法加载 BeanDefinition 。这样,就不会创建对应的 Bean 对象了。
3.3 小结
至此,我们已经看到 Condition 如何生效。还是相对比较简单的。
下面,我们一起来看看 SpringBootCondition 如何实现它的进击。
4. ProfileCondition
艿艿:先插播下 ProfileCondition 的实现代码。
org.springframework.context.annotation.ProfileCondition
,实现 Condition 接口,给 @Profile
使用的 Condition 实现类。代码如下:
// ProfileCondition.java |
- 核心逻辑,获得
@Profile
的value
属性,和environment
是否有匹配的。如果有,则表示匹配。
5. SpringBootCondition
org.springframework.boot.autoconfigure.condition.SpringBootCondition
,实现 Condition 接口,Spring Boot Condition 的抽象基类,主要用于提供相应的日志,帮助开发者判断哪些被进行加载。如下是其上的类注释:
/** |
5.1 matches
实现 #matches(ConditionContext context, AnnotatedTypeMetadata metadata)
方法,实现匹配逻辑。代码如下:
// SpringBootCondition.java |
-
<1>
处,调用#getClassOrMethodName(AnnotatedTypeMetadata metadata)
方法,获得注解的是方法名还是类名。代码如下:// SpringBootCondition.java
private static String getClassOrMethodName(AnnotatedTypeMetadata metadata) {
// 类
if (metadata instanceof ClassMetadata) {
ClassMetadata classMetadata = (ClassMetadata) metadata;
return classMetadata.getClassName();
}
// 方法
MethodMetadata methodMetadata = (MethodMetadata) metadata;
return methodMetadata.getDeclaringClassName() + "#" + methodMetadata.getMethodName();
}
-
<2>
处,调用#getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata)
抽象方法,执行匹配,返回匹配结果。这是一个抽象方法,由子类进行实现。 -
<3>
处,调用#logOutcome(String classOrMethodName, ConditionOutcome outcome)
方法,打印结果日志。代码如下:// SpringBootCondition.java
protected final void logOutcome(String classOrMethodName, ConditionOutcome outcome) {
if (this.logger.isTraceEnabled()) {
this.logger.trace(getLogMessage(classOrMethodName, outcome));
}
}
private StringBuilder getLogMessage(String classOrMethodName, ConditionOutcome outcome) {
StringBuilder message = new StringBuilder();
message.append("Condition ");
message.append(ClassUtils.getShortName(getClass()));
message.append(" on ");
message.append(classOrMethodName);
message.append(outcome.isMatch() ? " matched" : " did not match");
if (StringUtils.hasLength(outcome.getMessage())) {
message.append(" due to ");
message.append(outcome.getMessage());
}
return message;
} -
<4>
处,调用#recordEvaluation(ConditionContext context, String classOrMethodName, ConditionOutcome outcome)
方法,记录到 ConditionEvaluationReport 。代码如下:// SpringBootCondition.java
private void recordEvaluation(ConditionContext context, String classOrMethodName, ConditionOutcome outcome) {
if (context.getBeanFactory() != null) {
ConditionEvaluationReport.get(context.getBeanFactory()).recordConditionEvaluation(classOrMethodName, this, outcome);
}
}- 关于 org.springframework.boot.autoconfigure.condition.ConditionEvaluationReport 类,先不详细看,避免研究过深。
-
<5>
处,返回是否匹配。
5.2 anyMatches
#anyMatches(ConditionContext context, AnnotatedTypeMetadata metadata, Condition... conditions)
方法,判断是否匹配指定的 Condition 们中的任一一个。代码如下:
艿艿:总感觉这个方法,应该是个静态方法才合适。所以,胖友即酱油看看即可。
// SpringBootCondition.java |
- 遍历
conditions
数组,调用#matches(ConditionContext context, AnnotatedTypeMetadata metadata, Condition condition)
方法,执行匹配。代码如下:// SpringBootCondition.java
protected final boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata, Condition condition) {
// 如果是 SpringBootCondition 类型,执行 SpringBootCondition 的直接匹配方法(无需日志)
if (condition instanceof SpringBootCondition) {
return ((SpringBootCondition) condition).getMatchOutcome(context, metadata).isMatch();
}
return condition.matches(context, metadata);
}
总的来说,SpringBootCondition 这个类,没啥好说,重点还是在子类。
6. SpringBootCondition 的实现类
我们在回忆下,SpringBootCondition 的实现类,主要如下图:
显然,我们不会去看每一个类的 SpringBootCondition 的实现类。所以呢,艿艿也不会每个类都写。
旁白君:偷懒都偷的如此猥琐,哈哈哈哈。
6.1 OnPropertyCondition
艿艿:来来来,先看一个容易的(捏个软柿子)。
org.springframework.boot.autoconfigure.condition.OnPropertyCondition
,继承 SpringBootCondition 抽象类,给 @ConditionalOnProperty
使用的 Condition 实现类。
如果胖友不熟悉 @ConditionalOnProperty
注解,赶紧打开 《@ConditionalOnProperty 来控制 Configuration 是否生效》 学习 3 分钟~不能再多了。
6.1.1 getMatchOutcome
#getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata)
方法,获得匹配结果。代码如下:
// OnPropertyCondition.java |
-
<1>
处,调用#annotationAttributesFromMultiValueMap(MultiValueMap<String, Object> multiValueMap)
方法,获得@ConditionalOnProperty
注解的属性。代码如下:// OnPropertyCondition.java
private List<AnnotationAttributes> annotationAttributesFromMultiValueMap(MultiValueMap<String, Object> multiValueMap) {
List<Map<String, Object>> maps = new ArrayList<>();
multiValueMap.forEach((key, value) -> {
for (int i = 0; i < value.size(); i++) {
Map<String, Object> map;
if (i < maps.size()) {
map = maps.get(i);
} else {
map = new HashMap<>();
maps.add(map);
}
map.put(key, value.get(i));
}
});
List<AnnotationAttributes> annotationAttributes = new ArrayList<>(maps.size());
for (Map<String, Object> map : maps) {
annotationAttributes.add(AnnotationAttributes.fromMap(map));
}
return annotationAttributes;
}- 懒的看整个代码实现的过程,可以直接看最终执行的结果图:
-
<2>
处,存储匹配和不匹配的结果消息结果。 -
<3>
处,遍历annotationAttributes
属性数组,逐个调用#determineOutcome(AnnotationAttributes annotationAttributes, PropertyResolver resolver)
方法,判断是否匹配,并添加到结果。详细解析,见 「6.1.2 determineOutcome」 。 -
<4.1>
处,如果有不匹配的,则返回不匹配。返回结果示例如下: -
<4.2>
处,如果都匹配,则返回匹配。返回结果示例如下:
6.1.2 determineOutcome
#determineOutcome(AnnotationAttributes annotationAttributes, PropertyResolver resolver)
方法,判断是否匹配。代码如下:
// OnPropertyCondition.java |
-
<1>
处,解析成 Spec 对象。Spec 是 OnPropertyCondition 的内部静态类。代码如下:// OnPropertyCondition#Spec.java
private static class Spec {
/**
* 属性前缀
*/
private final String prefix;
/**
* 是否有指定值
*/
private final String havingValue;
/**
* 属性名
*/
private final String[] names;
/**
* 如果属性不存在,是否认为是匹配的。
*
* 如果为 false 时,就认为属性丢失,即不匹配。
*/
private final boolean matchIfMissing;
Spec(AnnotationAttributes annotationAttributes) {
String prefix = annotationAttributes.getString("prefix").trim();
if (StringUtils.hasText(prefix) && !prefix.endsWith(".")) {
prefix = prefix + ".";
}
this.prefix = prefix;
this.havingValue = annotationAttributes.getString("havingValue");
this.names = getNames(annotationAttributes);
this.matchIfMissing = annotationAttributes.getBoolean("matchIfMissing");
}
// 从 value 或者 name 属性种,获得值
private String[] getNames(Map<String, Object> annotationAttributes) {
String[] value = (String[]) annotationAttributes.get("value");
String[] name = (String[]) annotationAttributes.get("name");
Assert.state(value.length > 0 || name.length > 0, "The name or value attribute of @ConditionalOnProperty must be specified");
Assert.state(value.length == 0 || name.length == 0, "The name and value attributes of @ConditionalOnProperty are exclusive");
return (value.length > 0) ? value : name;
}
// ... 省略其它方法先~
} -
<2>
处,创建结果数组。 -
<3>
处,调用Spec#collectProperties(PropertyResolver resolver, List<String> missing, List<String> nonMatching)
方法,收集是否不匹配的信息,到missingProperties
、nonMatchingProperties
中。代码如下:// OnPropertyCondition#Spec.java
private void collectProperties(PropertyResolver resolver, List<String> missing, List<String> nonMatching) {
// 遍历 names 数组
for (String name : this.names) {
// 获得完整的 key
String key = this.prefix + name;
// 如果存在指定属性
if (resolver.containsProperty(key)) {
// 匹配值是否匹配
if (!isMatch(resolver.getProperty(key), this.havingValue)) {
nonMatching.add(name);
}
// 如果不存在指定属性
} else {
// 如果属性为空,并且 matchIfMissing 为 false ,则添加到 missing 中
if (!this.matchIfMissing) {
missing.add(name);
}
}
}
}
private boolean isMatch(String value, String requiredValue) {
// 如果 requiredValue 非空,则进行匹配
if (StringUtils.hasLength(requiredValue)) {
return requiredValue.equalsIgnoreCase(value);
}
// 如果 requiredValue 为空,要求值不为 false
return !"false".equalsIgnoreCase(value);
}- 匹配的逻辑,胖友自己瞅瞅。可能比较绕的逻辑是,
matchIfMissing
那块,也就看两眼就明白。
- 匹配的逻辑,胖友自己瞅瞅。可能比较绕的逻辑是,
-
<4.1>
处,如果有属性缺失,则返回不匹配。 -
<4.2>
处,如果有属性不匹配,则返回不匹配。 -
<4.3>
处,返回匹配。
6.2 其它实现类
SpringBootCondition 的其它实现类,胖友可以自己去瞅瞅啦。当然,有部分实现类,我们会在 「8. FilteringSpringBootCondition」 分享。
7. AutoConfigurationImportFilter
在 《精尽 Spring Boot 源码分析 —— 自动配置》 一文中,我们埋了一个 AutoConfigurationImportSelector#filter(List<String> configurations, AutoConfigurationMetadata autoConfigurationMetadata)
方法的坑,没有进行详细解析。所以呢,这一节我们将填掉这个坑。
org.springframework.boot.autoconfigure.AutoConfigurationImportFilter
接口,用于过滤掉无需自动引入的自动配置类(Configuration)。正如其类上的注释:
// AutoConfigurationImportFilter.java |
- 重点是
"fast removal of auto-configuration classes before their bytecode is even read"
。因为自动配置类可能会很多,如果无需使用,而将字节码读取到内存中,这个是一种浪费。
AutoConfigurationImportFilter 的代码如下:
// AutoConfigurationImportFilter.java |
- 将传入的
autoConfigurationClasses
配置类们,根据autoConfigurationMetadata
的元数据(主要是注解信息),进行匹配,判断是否需要引入,然后返回的boolean[]
结果。 - 并且,
boolean[]
结果和autoConfigurationClasses
配置类们是一一对应的关系噢。假设autoConfigurationClasses[0]
对应的boolean[0]
为false
,表示无需引入,反之则需要引入。
7.1 AutoConfigurationImportFilter 类图
AutoConfigurationImportFilter 的子类如下图所示:
- 从图中,我们很容易就看出,AutoConfigurationImportFilter 的最终实现类,都是构建在 SpringBootCondition 之上。
发表评论
相关推荐
本示例“spring boot整合JPA——demo”将演示如何在Spring Boot项目中配置和使用JPA。 首先,我们需要理解Spring Boot与JPA的关系。Spring Boot是基于Spring框架的快速开发工具,它通过自动化配置减少了常规设置...
**Spring Boot创建与运行项目详解** Spring Boot是Java开发领域中的一个热门框架,它通过简化配置和自动装配,使得创建和运行Spring应用变得更加容易。在本篇内容中,我们将深入探讨如何利用Spring Boot来创建和...
深入学习spring boot 懂得各个标签,注解的用途和原理
《果子学院Spring Boot源码解析》是一套深入学习Spring Boot源码的教程,旨在帮助开发者深入了解这个流行的Java开发框架的内部工作机制。Spring Boot简化了Java应用的初始搭建以及开发过程,它集成了大量常用的第三...
在这个版本中,我们将深入探讨Spring Boot的核心特性、工作原理以及如何通过源码来理解其内部机制。 首先,Spring Boot的核心理念是“约定优于配置”,它通过预设许多默认配置,减少了开发者需要手动配置的繁琐工作...
通过源码分析,开发者可以更好地理解Spring Boot的自动装配、启动流程以及如何自定义启动器。Spring Boot的自动装配原理涉及到Spring Boot的核心特性,它简化了基于Spring的应用开发,通过自动配置减少了大量的配置...
《Spring Boot实战派》源码提供了丰富的学习材料,旨在帮助开发者深入理解并...通过分析《Spring Boot实战派》源码,读者不仅可以了解上述技术点,还能学习到如何在实际项目中应用这些技术,提升开发效率和代码质量。
下面,我们将深入探讨Spring Boot的源码,揭示其内部工作原理。 1. **自动配置**:Spring Boot的自动配置是其核心特性之一。在`spring-boot-autoconfigure`模块中,通过条件注解(如`@ConditionalOnClass`, `@...
基于spring boot餐厅管理系统源码 基于spring boot餐厅管理系统源码 基于spring boot餐厅管理系统源码 基于spring boot餐厅管理系统源码 基于spring boot餐厅管理系统源码 基于spring boot餐厅管理系统源码 ...
java毕业设计——基于spring boot的音乐播放网站设计与实现(源码+数据库).zip java毕业设计——基于spring boot的音乐播放网站设计与实现(源码+数据库).zip java毕业设计——基于spring boot的音乐播放网站设计与...
基于 Spring Boot + MySQL 开发的博客系统源码 基于 Spring Boot + MySQL 开发的博客系统源码 基于 Spring Boot + MySQL 开发的博客系统源码 基于 Spring Boot + MySQL 开发的博客系统源码 基于 Spring ...
Spring Boot是Java开发领域的一款非常流行的框架,它简化了基于Spring的应用程序开发流程。Spring Boot 2.6.2是该框架...通过分析源码,我们可以学习到Spring框架的最佳实践,以及如何设计和实现一个健壮的微服务架构。
通过深入阅读和分析Spring Boot 2.7.0的源码,我们可以了解到Spring Boot是如何实现其核心特性的,以及如何利用Spring Framework进行扩展和定制。同时,这也有助于我们更好地利用Spring Boot进行微服务开发,提高...
在深入理解Spring Boot的源码时,我们首先要明白其核心设计理念——“约定优于配置”。Spring Boot通过预设默认配置,使得开发者能够快速启动并运行应用,同时提供了丰富的启动器(Starters)来简化依赖管理。 ...
本学习资源包“java maven工程 spring boot 学习源码”提供了一个可以直接运行的示例工程,有助于深入理解Spring Boot和Maven的结合使用。 首先,我们需要了解Spring Boot的核心特性。Spring Boot通过内嵌的Servlet...
《Spring5 源码分析(第 2 版)》是针对Spring框架第五个主要版本的深度解析著作,由知名讲师倾心打造,旨在帮助读者深入理解Spring框架的内部工作机制,提升对Java企业级应用开发的专业技能。本书涵盖了Spring框架的...
java毕业设计——基于spring boot的就业信息管理网站设计与实现(源码+数据库).zip java毕业设计——基于spring boot的就业信息管理网站设计与实现(源码+数据库).zip java毕业设计——基于spring boot的就业信息管理...
本资源包"使用Gradle构建Spring Boot工程系列项目源码"是针对一系列教程的配套源代码,旨在帮助开发者深入理解如何利用Gradle有效地构建Spring Boot应用程序。通过分析这些源码,我们可以学习到以下关键知识点: 1....
这本《Vue Spring Boot前后端分离开发实战》的源码提供了深入学习和实践这一技术栈的机会。以下是对其中涉及知识点的详细说明: 1. **Vue.js**:Vue.js是一个轻量级的前端JavaScript框架,以其易学易用、组件化和...
5. **源码分析**:在提供的压缩包 `springcloud-test` 中,可能包含了 Spring Boot Admin 与 Spring Boot 应用集成的示例代码。你可以通过查看 `Application.java` 文件来了解如何启动和配置服务器及客户端,同时...