Spring 条件注解没生效?咋回事
条件注解相信各位小伙伴都用过,Spring 中的多环境配置 profile 底层就是通过条件注解来实现的,松哥在之前的 Spring 视频中也有和大家详细介绍过条件注解的使用,感兴趣的小伙伴戳这里:Spring源码应该怎么学?。
从 Spring4.0 开始,Spring 提供了一个更加细粒度的条件注解: ConfigurationCondition。从名字上就可以看出来这个是搭配 @Configuration 注解一起使用的,ConfigurationCondition 提供了一种更加细粒度的条件匹配,可以在配置或者 Bean 注册的时候去评估条件注解是否满足。
也就是说,当一个类上存在条件注解的时候,我们可以有两个评估条件注解是否满足的时机:
- 在配置的时候去评估。
- 在 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 条件注解没生效?咋回事
条件注解相信各位小伙伴都用过,Spring 中的多环境配置 profile 底层就是通过条件注解来实现的,松哥在之前的 Spring 视频中也有和大家详细介绍过条件注解的使用,感兴趣的小伙伴戳这里:Spring源码应该怎么学?。 从 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 的依赖项注入库,可减少在项目中执行手动依赖项注入的样板代码。执行 手动依赖项注入 要求您手动构造每个类及其依赖项,并借助容器重复使用和管理依赖项。 Hilt 通过为项目中的每个 Android 类提供容器并自动管理其生命周期,…...
批量采集的时间管理与优化
在进行大规模数据采集时,如何合理安排和管理爬取任务的时间成为了每个专业程序员需要面对的挑战。本文将分享一些关于批量采集中时间管理和优化方面的实用技巧,帮助你提升爬虫工作效率。 1. 制定明确目标并设置合适频率 首先要明确自己所需获取数据的范…...
uniApp监听左右滑动事件
监听左右滑动事件的步骤 1. 添加需要监听滑动事件的元素 在你的页面中,添加需要监听滑动事件的元素。这可以是一个 view、swiper 或其他组件,取决于你的需求。例如: <template><view class"body" touchstart"touc…...
十八、MySQL添加外键?
1、外键 外键是用来让两张表的数据之间建立联系,从而保证数据的一致性和完整性。 注意,父表被关联的字段类型,必须和子表被关联的字段类型一致。 2、实际操作 (1)初始化两张表格: 子表: 父…...
图像文件的操作MATLAB基础函数使用
简介 MATLAB中的图像处理工具箱体统了一套全方位的标准算法和图形工具,用于进行图像处理、分析、可视化和算法开发。这里仅仅对常用的基础函数做个使用介绍。 查询图像文件的信息 使用如下函数 imfinfo(filename,fmt) 函数imfinfo返回一个结构体的infoÿ…...
【k8s】Kubernetes版本v1.17.3 kubesphere 3.1.1 默认用户登录失败
1.发帖: Kubernetes版本v1.17.3 kubesphere 3.11 默认用户登录失败 - KubeSphere 开发者社区 2. 问题日志: 2.1问题排查方法 : 用户无法登录 http://192.168.56.100:30880/ 2.2查看用户状态 kubectl get users [rootk8s-node1 ~]# k…...
Mysql加密功能
Mysql加密功能 InnoDB加密功能查询条件问题开启整个数据库加密 InnoDB加密功能 InnoDB是MySQL数据库引擎的一种,它提供了加密存储的功能。具体来说,InnoDB引擎支持以下两种方式的加密存储: 表级加密:InnoDB支持表级加密ÿ…...
redis-win10安装和解决清缓存报错“Error: Protocol error, got “H“ as reply type byte”
win10安装 https://github.com/microsoftarchive/redis/releases 下载最新的zip,解压,把路径加到Path里,每次直接在cmd里 redis-server.exeError: Protocol error, got “H” as reply type byte 这个报错是因为我端口写错了。。无语 D:…...
【视觉检测】电源线圈上的导线弯直与否视觉检测系统软硬件方案
检测内容 线圈上的导线弯直与否检测系统。 检测要求 检测线圈上的导线有无弯曲,弯曲度由客户自己设定。检测速度5K/8H625PCS/H。 视觉可行性分析 对样品进行了光学实验,并进行图像处理,原则上可以使用机器视觉进行测试测量…...
Java elasticsearch scroll模板实现
一、scroll说明和使用场景 scroll的使用场景:大数据量的检索和操作 scroll顾名思义,就是游标的意思,核心的应用场景就是遍历 elasticsearch中的数据; 通常我们遍历数据采用的是分页,elastcisearch还支持from size的…...
嵌入式基础知识-信息安全与加密
本篇来介绍计算机领域的信息安全以及加密相关基础知识,这些在嵌入式软件开发中也同样会用到。 1 信息安全 1.1 信息安全的基本要素 保密性:确保信息不被泄露给未授权的实体。包括最小授权原则、防暴露、信息加密、物理加密。完整性:保证数…...
TCP的三次握手与四次挥手
首先,源端口号和目标端口号是不可少的,这一点和 UDP 是一样的。如果没有这两个端口号。数据就不知道应该发给哪个应用。 接下来是包的序号。为什么要给包编号呢?当然是为了解决乱序的问题。不编好号怎么确认哪个应该先来,哪个应该…...
【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、基础知识 在开始之前,先介绍etcd中 Version, Revision, ModRevision, CreateRevision 几个基本概念。 1、version 作用域为key,表示某个key的版本,每个key刚创建的version为1&#…...
Android 网络配置
ip tables 和 ip route 是两个不同的工具,它们在不同的阶段执行不同的功能。ip route 是用来管理和控制路由表的,它决定了数据包应该从哪个网卡或网关发送出去。ip tables 是用来配置、管理和控制网络数据包的过滤、转发和转换的,它根据用户定…...
【网络通信 -- 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)
简介 多语言药房管理系统(MPMS)是用PHP和MySQL开发的,该软件的主要目的是在药房和客户之间提供一套接口,客户是该软件的主要用户。该软件有助于为药房业务创建一个综合数据库,并根据到期、产品等各种参数提供各种报告…...
LeetCode-77-组合
一:题目描述: 给定两个整数 n 和 k,返回范围 [1, n] 中所有可能的 k 个数的组合。 你可以按 任何顺序 返回答案。 二:示例与提示 示例 1: 输入:n 4, k 2 输出: [[2,4],[3,4],[2,3],[1,2],[1,3],[1,4…...
酒店门锁V10SDK接口说明-幽冥大陆(一百23)—东方仙盟
相关文件系统环境C# :NET.20,NET3.5,NET4,NET4.5,NET 5.0C:VS2005,VS2012,VS2015操作系统:未来之窗VOSWEB:CHROME43核心代码完整代码using System; using System.Collections.Generic; using System.Text; using System.Collections.Specialized;using System.Windo…...
保姆级教程:Windows系统下Arcgis 10.2从下载、安装到汉化一次搞定(附常见License启动失败解决方案)
Windows系统下Arcgis 10.2完整安装与汉化实战指南第一次接触Arcgis的新手往往会被复杂的安装流程和神秘的License Manager搞得晕头转向。作为一款功能强大的地理信息系统软件,Arcgis在科研、城市规划、环境监测等领域有着广泛应用,但它的安装过程确实会让…...
【与我学 ClaudeCode】协作篇 之 Worktree + Task Isolation :目录隔离的并行执行通道
作者:逆境不可逃 技术永无止境 希望我的内容可以帮助到你!!!! 大家吼 ! 我是 逆境不可逃 今天给大家带来文章《【与我学 ClaudeCode】协作篇 之 Worktree Task Isolation :目录隔离的并行执行通道》. Le…...
Cesium动态数据可视化实战:CallbackProperty结合setInterval打造实时运动轨迹
Cesium动态数据可视化实战:CallbackProperty结合setInterval打造实时运动轨迹 在三维地理信息系统中,实时数据可视化一直是开发者面临的挑战之一。想象一下,当我们需要在地球表面追踪一架正在飞行的无人机,或者监控城市中数百辆出…...
为什么你的辉光总像P图?——拆解Adobe Stock Top 10辉光作品的MJ底层prompt结构,含--v 6.2专属glow injection指令
更多请点击: https://intelliparadigm.com 第一章:辉光效果的视觉认知误区与本质解构 辉光(Glow)常被误认为是“发光物体自身辐射出的光”,实则是一种典型的后处理视觉错觉——它不改变光源物理属性,也不增…...
【Midjourney霓虹效果终极指南】:20年AI视觉工程师亲授5大参数组合+3类光源建模公式,97%新手一周内复刻赛博朋克海报
更多请点击: https://kaifayun.com 第一章:霓虹美学的视觉原理与Midjourney适配性解析 霓虹美学源于20世纪都市夜景中的荧光灯管、电子广告与赛博朋克文化,其核心视觉特征包括高饱和度冷暖对比、边缘辉光(glow)、深色…...
D3KeyHelper终极指南:5分钟掌握暗黑3最强自动化工具
D3KeyHelper终极指南:5分钟掌握暗黑3最强自动化工具 【免费下载链接】D3keyHelper D3KeyHelper是一个有图形界面,可自定义配置的暗黑3鼠标宏工具。 项目地址: https://gitcode.com/gh_mirrors/d3/D3keyHelper D3KeyHelper是一款专为《暗黑破坏神3…...
Unity Cinemachine相机系统深度使用:除了自动跟随,它的边界限制(Confiner)功能才是宝藏
Unity Cinemachine Confiner:解锁专业级镜头边界控制的实战指南在游戏开发中,镜头控制往往是被低估的艺术。许多开发者对Cinemachine的印象停留在"智能跟随相机"层面,却不知道它的Confiner功能能够彻底改变游戏镜头的专业度。想象一…...
024、NPU指令集架构(ISA)概述:从CISC到VLIW
024、NPU指令集架构(ISA)概述:从CISC到VLIW 去年冬天调试一块国产NPU芯片的卷积算子,跑ResNet-50前向推理,死活比理论算力低了一个数量级。抓了三天波形,最后发现是指令发射槽的冲突——两条MAC指令争同一个数据总线,硬件自动插入三个空泡周期。那一刻我盯着逻辑分析仪…...
游戏开发/机器人导航必看:极坐标到底比XY坐标强在哪?Unity/ROS中的实战案例
你的输出 (必须严格遵循以下YAML格式,无需任何分析过程)相关性: ... 改写后查询: ... 企业名称: ... 基础信息: ... 职位: ... json {"business_segment": "礼品","main_product": "百度电商","reason": "用…...
