当前位置: 首页 > news >正文

Spring 条件注解没生效?咋回事

条件注解相信各位小伙伴都用过,Spring 中的多环境配置 profile 底层就是通过条件注解来实现的,松哥在之前的 Spring 视频中也有和大家详细介绍过条件注解的使用,感兴趣的小伙伴戳这里:Spring源码应该怎么学?。

从 Spring4.0 开始,Spring 提供了一个更加细粒度的条件注解: ConfigurationCondition。从名字上就可以看出来这个是搭配 @Configuration 注解一起使用的,ConfigurationCondition 提供了一种更加细粒度的条件匹配,可以在配置或者 Bean 注册的时候去评估条件注解是否满足。

也就是说,当一个类上存在条件注解的时候,我们可以有两个评估条件注解是否满足的时机:

  1. 在配置的时候去评估。
  2. 在 Bean 注册的时候评估。

在配置的时候评估,可能会导致当前类都不会被加载,在 Bean 注册的时候再去评估,意味着当前类就会被加载。

1. ConfigurationCondition

我们先来看下这个类的定义:

public interface ConfigurationCondition extends Condition {ConfigurationPhase getConfigurationPhase();enum ConfigurationPhase {PARSE_CONFIGURATION,REGISTER_BEAN}
}

大家看到,这里其实就是定义了两个枚举值,然后提供了一个方法返回枚举值。

  • PARSE_CONFIGURATION:这个表示 Condition 条件应该在解析 @Configuration 类时进行评估,如果评估不通过,则不会将 @Configuration 添加到容器中。
  • REGISTER_BEAN:这个表示添加常规 Bean 的时候去评估 Condition 条件(常规 Bean 就是指非配置类,例如添加搭配 @Bean 注解使用的条件注解),这个条件不会阻止注册 @Configuration 类到容器中。

其实道理很好懂,就是加载配置类的时候就根据条件注解判断要不要加载配置类,还是等到注册 Bean 的时候再去看条件注解是否满足条件。

2. 案例分析

松哥通过一个简单案例来和小伙伴们演示一下。

假设我现在有如下条件:

public class MyCondition implements ConfigurationCondition {@Overridepublic ConfigurationPhase getConfigurationPhase() {return ConfigurationPhase.PARSE_CONFIGURATION;}@Overridepublic boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {return context.getBeanFactory().containsBean("a");}
}

这个条件我没有直接实现 Condition 接口,而是实现类 ConfigurationCondition 接口,在这个接口中,getConfigurationPhase 方法返回了 PARSE_CONFIGURATION,表示在加载配置类的时候就去评估条件是否满足,matches 方法则是去判断容器中是否存在一个名为 a 的 Bean。

现在我有两个配置类,分别是 A 和 B,如下:

@Configuration
public class A {
}
@Configuration
@Conditional(MyCondition.class)
public class B {
}

A 配置类正常加载,B 配置类有一个加载条件,就是得 A 存在,B 才会加载。

现在,在容器中加载 B 和 A 两个配置,如下:

AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
ctx.register(B.class,A.class);
ctx.refresh();
String[] beanDefinitionNames = ctx.getBeanDefinitionNames();
for (String beanDefinitionName : beanDefinitionNames) {System.out.println(beanDefinitionName);
}

大家注意,加载的时候,我先加载了 B,后加载了 A,这点很重要,加载 B 的时候,由于此时容器中还不存在一个名为 a 的 Bean,而我们的评估时机是在处理配置类的时候,因此就会导致 B 配置类不会被加载,最终打印出来的 BeanName 就没有 b。

但是,如果我们将 MyCondition 中,条件注解的评估时机改为 ConfigurationPhase.REGISTER_BEAN,那么就表示在系统启动的时候,并不会去评估条件注解是否满足,而是会将 @Configuration 配置类进行解析,此时启动系统,就会发现最终打印出来的 beanName 里既有 a 又有 b。

3. 源码分析

接下来我们再来从源码的角度来分析一下上述行为。

在 Spring 中,提供了一个专门的内部类 ConditionEvaluator 来处理要不要跳过条件注解,该类中有一个名为 shouldSkip 的方法,用来处理此事:

public boolean shouldSkip(AnnotatedTypeMetadata metadata) {return shouldSkip(metadata, null);
}
public boolean shouldSkip(@Nullable AnnotatedTypeMetadata metadata, @Nullable ConfigurationPhase phase) {if (metadata == null || !metadata.isAnnotated(Conditional.class.getName())) {return false;}if (phase == null) {if (metadata instanceof AnnotationMetadata annotationMetadata &&ConfigurationClassUtils.isConfigurationCandidate(annotationMetadata)) {return shouldSkip(metadata, ConfigurationPhase.PARSE_CONFIGURATION);}return shouldSkip(metadata, ConfigurationPhase.REGISTER_BEAN);}List<Condition> conditions = new ArrayList<>();for (String[] conditionClasses : getConditionClasses(metadata)) {for (String conditionClass : conditionClasses) {Condition condition = getCondition(conditionClass, this.context.getClassLoader());conditions.add(condition);}}AnnotationAwareOrderComparator.sort(conditions);for (Condition condition : conditions) {ConfigurationPhase requiredPhase = null;if (condition instanceof ConfigurationCondition configurationCondition) {requiredPhase = configurationCondition.getConfigurationPhase();}if ((requiredPhase == null || requiredPhase == phase) && !condition.matches(this.context, metadata)) {return true;}}return false;
}

第一个方法不用多说,我们来看第二个重载方法,重载方法多了一个参数 ConfigurationPhase,这个就表示配置的阶段,也就是条件注解生效的阶段。

首先会去判断当前注解是否是一个条件注解,如果不是条件注意,那么就不能跳过,要继续后面的解析(继续后面的解析时 Bean 将会被注册),如果是条件注解,则继续后面的判断。继续判断,如果没有传递 phase 进来,说明没有指定应该在哪个阶段去评估条件注解,那么这个时候就去判断,如果当前注解是一个配置类上的注解,那么就设置 phase 为 PARSE_CONFIGURATION,然后继续调用 shouldSkip 方法,否则就设置 phase 为 REGISTER_BEAN 然后继续调用 shouldSkip 方法。

那么什么样的情况会被认为是一个配置类上的注解呢?如果当前类上添加的注解时 @Component、@ComponentScan、@Import、@ImportResource 以及这四种注解衍生出来的注解,亦或者当前类中有 @Bean 注解标记的方法,那么当前类就是一个配置类,就会设置 phase 为 PARSE_CONFIGURATION。

第二次进入 shouldSkip 方法的时候,就已经有明确的 phase 了。这次进来后,把所有的条件注解的条件收集起来,存入到 conditions 集合中,然后再对该集合进行排序。然后遍历该集合。遍历的时候就去判断这个条件注解是不是 ConfigurationCondition 类型的,如果是,则提取出来其中的 phase 为 requiredPhase,这个就表示这个条件注意希望自己被处理的阶段,接下来去判断,如果 requiredPhase 为空,说明条件并未指定自己的执行时间,那么就执行 matches 方法进行条件评估;如果 requiredPhase 不为空,并且和传入的 phase 相等,那么也是当前评估。其实这个判断核心逻辑就是以参数传入进来的 phase 为准,要么条件没有设置评估时机,要么设置了,但是得和参数传进来的 phase 一致,只有满足这两个条件,才会当场进行评估。

这就是系统条件注解的评估逻辑。

对于配置类来说,是在 AnnotatedBeanDefinitionReader#doRegisterBean 方法中调用评估逻辑的:

private <T> void doRegisterBean(Class<T> beanClass, @Nullable String name,@Nullable Class<? extends Annotation>[] qualifiers, @Nullable Supplier<T> supplier,@Nullable BeanDefinitionCustomizer[] customizers) {AnnotatedGenericBeanDefinition abd = new AnnotatedGenericBeanDefinition(beanClass);if (this.conditionEvaluator.shouldSkip(abd.getMetadata())) {return;}//...
}

调用的时候并未明确指定 phase,所以会在进入到 shouldSkip 方法后,自行分析是哪个阶段评估条件注解。

对于 @Bean 注解标记的类来说,是在 ConfigurationClassBeanDefinitionReader#loadBeanDefinitionsForBeanMethod 方法中调用评估逻辑的:

private void loadBeanDefinitionsForBeanMethod(BeanMethod beanMethod) {ConfigurationClass configClass = beanMethod.getConfigurationClass();MethodMetadata metadata = beanMethod.getMetadata();String methodName = metadata.getMethodName();if (this.conditionEvaluator.shouldSkip(metadata, ConfigurationPhase.REGISTER_BEAN)) {configClass.skippedBeanMethods.add(methodName);return;}//...
}

这个调用的时候,就传入了 phase 了,直接指定了是在 Bean 初始化的时候评估。

好啦,这就是条件注解条件评估时机的两种情况。在 Spring Boot 中定义的条件注解里,有不少都用到了 ConfigurationCondition,而不是传统的 Condition,感兴趣的小伙伴可以自行查看哦~

相关文章:

Spring 条件注解没生效?咋回事

条件注解相信各位小伙伴都用过&#xff0c;Spring 中的多环境配置 profile 底层就是通过条件注解来实现的&#xff0c;松哥在之前的 Spring 视频中也有和大家详细介绍过条件注解的使用&#xff0c;感兴趣的小伙伴戳这里&#xff1a;Spring源码应该怎么学&#xff1f;。 从 Spr…...

96. 不同的二叉搜索树

class Solution { public:int numTrees(int n) {if (n0) {return 1;}vector<int> dp(n1, 0);dp[0] 1;dp[1] 0;for (int i 1; i < n; i) {for (int j 0; j < i; j) {dp[i] dp[j] * dp[i - 1 - j];}}return dp[n];} };...

Android Jetpack 中Hilt的使用

Hilt 是 Android 的依赖项注入库&#xff0c;可减少在项目中执行手动依赖项注入的样板代码。执行 手动依赖项注入 要求您手动构造每个类及其依赖项&#xff0c;并借助容器重复使用和管理依赖项。 Hilt 通过为项目中的每个 Android 类提供容器并自动管理其生命周期&#xff0c;…...

批量采集的时间管理与优化

在进行大规模数据采集时&#xff0c;如何合理安排和管理爬取任务的时间成为了每个专业程序员需要面对的挑战。本文将分享一些关于批量采集中时间管理和优化方面的实用技巧&#xff0c;帮助你提升爬虫工作效率。 1. 制定明确目标并设置合适频率 首先要明确自己所需获取数据的范…...

uniApp监听左右滑动事件

监听左右滑动事件的步骤 1. 添加需要监听滑动事件的元素 在你的页面中&#xff0c;添加需要监听滑动事件的元素。这可以是一个 view、swiper 或其他组件&#xff0c;取决于你的需求。例如&#xff1a; <template><view class"body" touchstart"touc…...

十八、MySQL添加外键?

1、外键 外键是用来让两张表的数据之间建立联系&#xff0c;从而保证数据的一致性和完整性。 注意&#xff0c;父表被关联的字段类型&#xff0c;必须和子表被关联的字段类型一致。 2、实际操作 &#xff08;1&#xff09;初始化两张表格&#xff1a; 子表&#xff1a; 父…...

图像文件的操作MATLAB基础函数使用

简介 MATLAB中的图像处理工具箱体统了一套全方位的标准算法和图形工具&#xff0c;用于进行图像处理、分析、可视化和算法开发。这里仅仅对常用的基础函数做个使用介绍。 查询图像文件的信息 使用如下函数 imfinfo(filename,fmt) 函数imfinfo返回一个结构体的info&#xff…...

【k8s】Kubernetes版本v1.17.3 kubesphere 3.1.1 默认用户登录失败

1.发帖&#xff1a; Kubernetes版本v1.17.3 kubesphere 3.11 默认用户登录失败 - KubeSphere 开发者社区 2. 问题日志&#xff1a; 2.1问题排查方法 &#xff1a; 用户无法登录 http://192.168.56.100:30880/ 2.2查看用户状态 kubectl get users [rootk8s-node1 ~]# k…...

Mysql加密功能

Mysql加密功能 InnoDB加密功能查询条件问题开启整个数据库加密 InnoDB加密功能 InnoDB是MySQL数据库引擎的一种&#xff0c;它提供了加密存储的功能。具体来说&#xff0c;InnoDB引擎支持以下两种方式的加密存储&#xff1a; 表级加密&#xff1a;InnoDB支持表级加密&#xff…...

redis-win10安装和解决清缓存报错“Error: Protocol error, got “H“ as reply type byte”

win10安装 https://github.com/microsoftarchive/redis/releases 下载最新的zip&#xff0c;解压&#xff0c;把路径加到Path里&#xff0c;每次直接在cmd里 redis-server.exeError: Protocol error, got “H” as reply type byte 这个报错是因为我端口写错了。。无语 D:…...

【视觉检测】电源线圈上的导线弯直与否视觉检测系统软硬件方案

 检测内容 线圈上的导线弯直与否检测系统。  检测要求 检测线圈上的导线有无弯曲&#xff0c;弯曲度由客户自己设定。检测速度5K/8H625PCS/H。  视觉可行性分析 对样品进行了光学实验&#xff0c;并进行图像处理&#xff0c;原则上可以使用机器视觉进行测试测量…...

Java elasticsearch scroll模板实现

一、scroll说明和使用场景 scroll的使用场景&#xff1a;大数据量的检索和操作 scroll顾名思义&#xff0c;就是游标的意思&#xff0c;核心的应用场景就是遍历 elasticsearch中的数据&#xff1b; 通常我们遍历数据采用的是分页&#xff0c;elastcisearch还支持from size的…...

嵌入式基础知识-信息安全与加密

本篇来介绍计算机领域的信息安全以及加密相关基础知识&#xff0c;这些在嵌入式软件开发中也同样会用到。 1 信息安全 1.1 信息安全的基本要素 保密性&#xff1a;确保信息不被泄露给未授权的实体。包括最小授权原则、防暴露、信息加密、物理加密。完整性&#xff1a;保证数…...

TCP的三次握手与四次挥手

首先&#xff0c;源端口号和目标端口号是不可少的&#xff0c;这一点和 UDP 是一样的。如果没有这两个端口号。数据就不知道应该发给哪个应用。 接下来是包的序号。为什么要给包编号呢&#xff1f;当然是为了解决乱序的问题。不编好号怎么确认哪个应该先来&#xff0c;哪个应该…...

【Face Swapping综述】Quick Overview of Face Swap Deep Fakes

【Face Swapping综述】Quick Overview of Face Swap Deep Fakes 0、前言Abstract1. Introduction2. Face Swapping Process2.1. Preprocessing2.2. Identity Extraction2.3. Attributes Extractor2.4. Generator2.5. Postprocessing2.6. Evaluation Methods3. Challenges4. Con…...

etcd选举源码分析和例子

本文主要介绍etcd在分布式多节点服务中如何实现选主。 1、基础知识 在开始之前&#xff0c;先介绍etcd中 Version, Revision, ModRevision, CreateRevision 几个基本概念。 1、version 作用域为key&#xff0c;表示某个key的版本&#xff0c;每个key刚创建的version为1&#…...

Android 网络配置

ip tables 和 ip route 是两个不同的工具&#xff0c;它们在不同的阶段执行不同的功能。ip route 是用来管理和控制路由表的&#xff0c;它决定了数据包应该从哪个网卡或网关发送出去。ip tables 是用来配置、管理和控制网络数据包的过滤、转发和转换的&#xff0c;它根据用户定…...

【网络通信 -- WebRTC】Open WebRTC Toolkit 环境搭建指南

【网络通信 -- WebRTC】Open WebRTC Toolkit -- OWT-Server 编译安装指南 【1】OWT Server 与 Web Demo 视频会议环境搭建 【1.1】编译 OWT Server 安装依赖 ./scripts/installDepsUnattended.sh编译 scripts/build.js -t all --check 注意若不支持硬件加速则采用如下命令 s…...

文件上传漏洞(CVE-2022-30887)

简介 多语言药房管理系统&#xff08;MPMS&#xff09;是用PHP和MySQL开发的&#xff0c;该软件的主要目的是在药房和客户之间提供一套接口&#xff0c;客户是该软件的主要用户。该软件有助于为药房业务创建一个综合数据库&#xff0c;并根据到期、产品等各种参数提供各种报告…...

LeetCode-77-组合

一&#xff1a;题目描述&#xff1a; 给定两个整数 n 和 k&#xff0c;返回范围 [1, n] 中所有可能的 k 个数的组合。 你可以按 任何顺序 返回答案。 二&#xff1a;示例与提示 示例 1: 输入&#xff1a;n 4, k 2 输出&#xff1a; [[2,4],[3,4],[2,3],[1,2],[1,3],[1,4…...

Chapter03-Authentication vulnerabilities

文章目录 1. 身份验证简介1.1 What is authentication1.2 difference between authentication and authorization1.3 身份验证机制失效的原因1.4 身份验证机制失效的影响 2. 基于登录功能的漏洞2.1 密码爆破2.2 用户名枚举2.3 有缺陷的暴力破解防护2.3.1 如果用户登录尝试失败次…...

C++_核心编程_多态案例二-制作饮品

#include <iostream> #include <string> using namespace std;/*制作饮品的大致流程为&#xff1a;煮水 - 冲泡 - 倒入杯中 - 加入辅料 利用多态技术实现本案例&#xff0c;提供抽象制作饮品基类&#xff0c;提供子类制作咖啡和茶叶*//*基类*/ class AbstractDr…...

【HarmonyOS 5.0】DevEco Testing:鸿蒙应用质量保障的终极武器

——全方位测试解决方案与代码实战 一、工具定位与核心能力 DevEco Testing是HarmonyOS官方推出的​​一体化测试平台​​&#xff0c;覆盖应用全生命周期测试需求&#xff0c;主要提供五大核心能力&#xff1a; ​​测试类型​​​​检测目标​​​​关键指标​​功能体验基…...

解决Ubuntu22.04 VMware失败的问题 ubuntu入门之二十八

现象1 打开VMware失败 Ubuntu升级之后打开VMware上报需要安装vmmon和vmnet&#xff0c;点击确认后如下提示 最终上报fail 解决方法 内核升级导致&#xff0c;需要在新内核下重新下载编译安装 查看版本 $ vmware -v VMware Workstation 17.5.1 build-23298084$ lsb_release…...

汽车生产虚拟实训中的技能提升与生产优化​

在制造业蓬勃发展的大背景下&#xff0c;虚拟教学实训宛如一颗璀璨的新星&#xff0c;正发挥着不可或缺且日益凸显的关键作用&#xff0c;源源不断地为企业的稳健前行与创新发展注入磅礴强大的动力。就以汽车制造企业这一极具代表性的行业主体为例&#xff0c;汽车生产线上各类…...

视频字幕质量评估的大规模细粒度基准

大家读完觉得有帮助记得关注和点赞&#xff01;&#xff01;&#xff01; 摘要 视频字幕在文本到视频生成任务中起着至关重要的作用&#xff0c;因为它们的质量直接影响所生成视频的语义连贯性和视觉保真度。尽管大型视觉-语言模型&#xff08;VLMs&#xff09;在字幕生成方面…...

Springcloud:Eureka 高可用集群搭建实战(服务注册与发现的底层原理与避坑指南)

引言&#xff1a;为什么 Eureka 依然是存量系统的核心&#xff1f; 尽管 Nacos 等新注册中心崛起&#xff0c;但金融、电力等保守行业仍有大量系统运行在 Eureka 上。理解其高可用设计与自我保护机制&#xff0c;是保障分布式系统稳定的必修课。本文将手把手带你搭建生产级 Eur…...

嵌入式学习笔记DAY33(网络编程——TCP)

一、网络架构 C/S &#xff08;client/server 客户端/服务器&#xff09;&#xff1a;由客户端和服务器端两个部分组成。客户端通常是用户使用的应用程序&#xff0c;负责提供用户界面和交互逻辑 &#xff0c;接收用户输入&#xff0c;向服务器发送请求&#xff0c;并展示服务…...

LLMs 系列实操科普(1)

写在前面&#xff1a; 本期内容我们继续 Andrej Karpathy 的《How I use LLMs》讲座内容&#xff0c;原视频时长 ~130 分钟&#xff0c;以实操演示主流的一些 LLMs 的使用&#xff0c;由于涉及到实操&#xff0c;实际上并不适合以文字整理&#xff0c;但还是决定尽量整理一份笔…...

MySQL 8.0 事务全面讲解

以下是一个结合两次回答的 MySQL 8.0 事务全面讲解&#xff0c;涵盖了事务的核心概念、操作示例、失败回滚、隔离级别、事务性 DDL 和 XA 事务等内容&#xff0c;并修正了查看隔离级别的命令。 MySQL 8.0 事务全面讲解 一、事务的核心概念&#xff08;ACID&#xff09; 事务是…...