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

news2026/4/2 3:07:07

条件注解相信各位小伙伴都用过,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 {
    @Override
    public ConfigurationPhase getConfigurationPhase() {
        return ConfigurationPhase.PARSE_CONFIGURATION;
    }

    @Override
    public 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,感兴趣的小伙伴可以自行查看哦~

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/998929.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

模板语言-Handlebars

Handlebars 是什么 Handlebars 是一种简单的模板语言。 它使用模板与传入的对象来生成HTML 或者其他文本格式。 Handlebars 模板看起来像是嵌入了handlebars 表达式的普通文本。 <p> {{firstname}} {{lastname}}</p> 一个handlebars表达式是使用两对尖括号包裹…

飞猪店铺小管家软件需求分析说明书

飞猪店铺小管家软件 项目背景&#xff1a; 在飞猪店铺运营过程中&#xff0c;客服人数不足导致客服团队忙不过来&#xff0c;容易出现订票信息错误&#xff0c;进而客户无法顺利参观景点&#xff0c;频繁投诉。这种情况不仅影响客户体验&#xff0c;还可能导致商家因赔付而承受…

玖章算术叶正盛:为什么 PostgreSQL 不如 MySQL 流行?|3306π活动预告

议题大纲 PostgreSQL 是业界功能最强大的开源数据库&#xff0c;为什么在全球流行度没有 MySQL 高&#xff0c;本次分享主题计划从产品、技术、商业等方面综合分析两个数据库的竞争。 议题方向&#xff1a; PostgreSQL 与 MySQL 数据库简介 市场分析 产品定位 技术优劣势 …

TensorFlow与pytorch特定版本虚拟环境的安装

TensorFlow与Python的版本对应&#xff0c;注意&#xff0c;一定要选择对应的版本&#xff0c;否则会让你非常痛苦&#xff0c;折腾很久搞不清楚原因。 建议使用国内镜像源安装 没有GPU后缀的就表示是CPU版本的&#xff0c;不加版本就是最新 pip install tensorflow -i https:…

如何创建集成 LSP 支持多语言的 Web 代码编辑器

对于一个云开发平台来说&#xff0c;一个好的 Web IDE 能很大程度地提高用户的编码体验&#xff0c;而一个 Web IDE 的一个重要组成部分就是代码编辑器。 目前有着多款 web 上的代码编辑器可供选择&#xff0c;比如 Ace、CodeMirror、Monaco&#xff0c;这三款编辑器的比较在这…

[ 云计算 华为云 ] 解决办法:如何更换华为云云耀云服务器L实例的镜像 | 文末送书

文章目录 问题描述分析原因解决办法文末送书《ANSYS Workbench项目分析与案例实操详解》博主推荐理由本书内容简介本书作者简介 废话在前&#xff08;直接看解决办法的这段可以过&#xff09;&#xff1a;讲道理&#xff0c;一般情况下云服务器&#xff0c;镜像是随便更换的&am…

13-JVM调优实战-3

上一篇&#xff1a;12-JVM调优实战-2 今天来介绍一款阿里巴巴的调优工具。 Arthas详解 Arthas 是 Alibaba 在 2018 年 9 月开源的 Java 诊断工具。支持 JDK6&#xff0c; 采用命令行交互模式&#xff0c;可以方便的定位和诊断线上程序运行问题。Arthas 官方文档十分详细&am…

【驱动开发】实现三盏灯的控制,编写应用程序测试

head.h #ifndef __HEAD_H__ #define __HEAD_H__//LED1:PE10 //LED2:PF10 //LED3:PE8#define LED_RCC 0X50000A28 //使能GPIO#define LED_MODER 0X50006000 //设置输出模式 #define LED_ODR 0X50006014 //设置输出高低电平#define LED2_MODER 0X50007000 …

一款 IDEA 插件帮你优雅转化 DTO、VO、BO、PO、DO

转自&#xff1a;码猿技术专栏 POJO 的定义是无规则简单的对象&#xff0c;在日常的代码分层中 pojo 会被分为VO、BO、 PO、 DTO VO &#xff08;view object/value object&#xff09;表示层对象 1、前端展示的数据&#xff0c;在接口数据返回给前端的时候需要转成VO 2、个…

火山引擎DataLeap的数据血缘用例与设计概述

更多技术交流、求职机会&#xff0c;欢迎关注字节跳动数据平台微信公众号&#xff0c;回复【1】进入官方交流群 数据血缘描述了数据的来源和去向&#xff0c;以及数据在多个处理过程中的转换。数据血缘是组织内使数据发挥价值的重要基础能力。本文从字节的数据链路概况开始&…

开学季,长沙又一次戳中年轻人:人才巴士,欢迎“星”同学

初秋临近&#xff0c;又是一年开学季。与往年不一样的是&#xff0c;当数以万计的学子从全国各地来到长沙&#xff0c;这座年轻人友好的城市也用独特的方式表达着对新生的欢迎与诚意&#xff1a;24辆人才巴士“穿上”欢迎词&#xff1b;60个公交站台向新生“表白”&#xff1b;…

linux一些常用的下载工具-aria2

从断点续传开始 故事的过程是这样的&#xff0c;朋友是搞科研的&#xff0c;需要在一个国外的学术网站下载一个药物的模型压缩包&#xff0c;大概有23g。关键他也不会用别的就wget下载…恩中间还断了…问我有什么方法没有断点续传&#xff0c;而是能不能更快速的下载&#xff…

实用工具JRebel XRebel【2023】配置和使用的详解

&#x1f973;&#x1f973;Welcome Huihuis Code World ! !&#x1f973;&#x1f973; 接下来看看由辉辉所写的关于JRebel & XRebel的相关操作吧 目录 &#x1f973;&#x1f973;Welcome Huihuis Code World ! !&#x1f973;&#x1f973; 一.JRebel 的简介 二.插件的…

03深度学习-目标检测-深度学习方法与传统算法对比

一、目标学习的检测方法变迁及对比 “目标检测“是当前计算机视觉和机器学习领域的研究热点。从Viola-Jones Detector、DPM等冷兵器时代的智慧到当今RCNN、YOLO等深度学习土壤孕育下的GPU暴力美学&#xff0c;整个目标检测的发展可谓是计算机视觉领域的一部浓缩史。整个目标…

【算法与数据结构】236、LeetCode二叉树的最近公共祖先

文章目录 一、题目二、解法三、完整代码 所有的LeetCode题解索引&#xff0c;可以看这篇文章——【算法和数据结构】LeetCode题解。 一、题目 二、解法 思路分析&#xff1a; 根据定义&#xff0c;最近祖先节点需要遍历节点的左右子树&#xff0c;然后才能知道是否为最近祖先节…

【ccf-csp题解】第0次csp认证-第四题-有趣的数-组合数学

题目描述 思路说明 本题涉及组合数学的知识 目的是在n个空位上放置0、1、2、3&#xff0c;问符合题意的放法有多少种 首先注意到一个重要的事实&#xff1a; 只要0和1的位置已经确定&#xff0c;那么2和3的摆放就十分容易了 那么把所有情况分为n-2种&#xff1a; 第一种…

IntelliJ IDEA 配合 Maven 的一些技巧(prifiles)

环境 IntelliJ IDEA 2017.1 Maven 3.3.9 Nexus 3.2.1 学习前提 了解 Maven 配置的基本用法 了解私有仓库&#xff0c;比如 nexus 的一些概念 强烈建议把 Maven 的 settings.xml 文件同时放在&#xff1a;%USER_HOME%/.m2/settings.xml 和 ${maven.home}/conf/settings.xm…

给 Ubuntu 操作系统配置静态 IP

针对 Ubuntu 22.04.3 操作系统的静态 IP 配置 一、查看初始的网络信息 查看网卡名称 ifconfig查看网关信息 route -n二、编辑网络配置文件 编辑文件&#xff0c;配置文件的名称可能不一样&#xff0c;自己去 /etc/netplan/ 目录查看 sudo vim /etc/netplan/01-network-manager-…

51单片机的智能台灯控制系统仿真( proteus仿真+程序+原理图+报告+讲解视频)

51单片机的红外光敏检测智能台灯控制系统仿真 1.主要功能&#xff1a;2.仿真3. 程序代码4. 原理图5. 设计报告6. 设计资料内容清单&&下载链接 51单片机的红外光敏检测智能台灯控制系统仿真( proteus仿真程序原理图报告讲解视频&#xff09; 仿真图proteus7.8及以上 程…

服务器时间正确,Java程序时区不对问题解决

服务器执行date命令显示时间正确 执行timedatectl status命令结果如下&#xff1a; 看起来是Time zone没有设置好&#xff0c;但是登录另外一台正常的服务器&#xff0c;执行timedatectl status也是一样的 直接写一个简单的Java程序TestTimeZone.java&#xff1a; import ja…