解密Spring Boot:深入理解条件装配与条件注解

在这里插入图片描述

文章目录

    • 一、条件装配概述
      • 1.1 条件装配的基本原理
      • 1.2 条件装配的作用
    • 二、常用注解
      • 2.1 @ConditionalOnClass
      • 2.2 @ConditionalOnBean
      • 2.3 @ConditionalOnProperty
      • 2.4 @ConditionalOnExpression
      • 2.5 @ConditionalOnMissingBean
    • 三、条件装配的实现原理
    • 四、实际案例

一、条件装配概述

1.1 条件装配的基本原理

条件装配的基本原理是根据特定的条件来决定是否应用特定的配置或组件。在 Spring Boot 中,条件装配是通过条件注解来实现的。

条件注解是一种特殊的注解,用于标记在配置类、组件类或方法上。它们根据某些条件的结果来决定是否应用相应的配置或组件。

条件注解的基本原理

  1. 条件判断:Spring 在处理配置类或组件时,会对标记了条件注解的类或方法进行条件判断。
  2. 条件匹配:条件注解中定义的条件匹配器会根据特定的条件,如类路径是否存在、Bean 是否存在、属性是否被设置等,对环境进行判断,如果条件满足则返回 true,否则返回 false。
  3. 条件注解处理器:Spring 容器会使用条件注解处理器来处理条件注解,根据条件匹配的结果来决定是否应用相应的配置或组件。
  4. 应用配置或组件:根据条件注解的处理结果,Spring 容器会决定是否应用相应的配置或组件。如果条件满足,则进行相应的配置或组件的注册和初始化;如果条件不满足,则忽略该配置或组件。

1.2 条件装配的作用

条件装配的作用在于根据特定的条件来决定是否应用特定的配置或组件,从而实现灵活性和可配置性。

条件装配实现的作用

  1. 环境适配:通过条件装配,可以根据当前的运行环境(如开发环境、测试环境、生产环境)或者配置(如不同的数据库、不同的服务提供商)来动态地选择合适的配置或组件,从而使应用程序适应不同的环境。
  2. 可插拔性:条件装配可以根据应用程序的需求动态地选择性地应用不同的配置或组件,使得应用程序的功能可以根据需求进行扩展或者替换,从而增强了应用程序的可插拔性和可扩展性。
  3. 简化配置:通过条件装配,可以根据特定的条件自动地应用相应的配置或组件,而无需手动配置或编写复杂的条件判断逻辑,从而简化了配置过程,提高了配置的易用性和可维护性。
  4. 优化性能:通过条件装配,可以根据特定的条件选择性地应用相应的配置或组件,避免不必要的资源消耗,从而优化了应用程序的性能和资源利用率。

二、常用注解

2.1 @ConditionalOnClass

@ConditionalOnClass 是 Spring Boot 中的一个条件注解,用于在类路径中存在指定的类时才会应用相应的配置。

定义了一个灵活的条件注解 ConditionalOnClass,它可以根据类路径中特定类的存在与否来决定是否应用相应的配置或组件。
在这里插入图片描述

示例和用法说明

/*** 只有当应用程序的类路径中存在 RedisTemplate 类时,RedisConfiguration 类中定义的 redisTemplate() 方法才会被注册为 Bean,并被 Spring 容器管理* 如果类路径中不存在 RedisTemplate 类,则该配置类中的 Bean 将被忽略*/
@Configuration
@ConditionalOnClass({org.springframework.data.redis.core.RedisTemplate.class})
public class RedisConfiguration {@Beanpublic RedisTemplate<String, Object> redisTemplate() {RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();// 配置 RedisTemplate 的相关属性return redisTemplate;}
}

2.2 @ConditionalOnBean

@ConditionalOnBean 是 Spring Framework 中的一个条件注解,它的作用是在容器中存在指定的 Bean 时,才会应用相应的配置或组件。如果指定的 Bean 不存在,则该配置或组件将被忽略。

定义了一个具有多个属性的注解 ConditionalOnBean,可以用于指定条件判断所依赖的类、名称、注解等信息,以及搜索依赖 Bean 的策略和泛型容器中的参数化类型。

在这里插入图片描述

示例和用法说明

  1. 基本用法
/*** MyService 类被标记为 @ConditionalOnBean(MyBean.class),这意味着只有当容器中存在 MyBean 类型的 Bean 时,MyService 才会被创建并添加到容器中*/
@Configuration
public class MyConfiguration {@Beanpublic MyBean myBean() {return new MyBean();}@ConditionalOnBean(MyBean.class)@Beanpublic MyService myService() {return new MyService();}
}
  1. 多个 Bean 的情况
/*** MyService 类被标记为 @ConditionalOnBean({MyBean.class, AnotherBean.class}),这意味着只有当容器中同时存在 MyBean 和 AnotherBean 类型的 Bean 时,MyService 才会被创建并添加到容器中*/
@Configuration
public class MyConfiguration {@Beanpublic MyBean myBean() {return new MyBean();}@Beanpublic AnotherBean anotherBean() {return new AnotherBean();}@ConditionalOnBean({MyBean.class, AnotherBean.class})@Beanpublic MyService myService() {return new MyService();}
}
  1. 使用名称来指定 Bean
/*** MyService 类被标记为 @ConditionalOnBean(name = {"myBean", "anotherBean"}),这意味着只有当容器中同时存在名称为 "myBean" 和 "anotherBean" 的 Bean 时,MyService 才会被创建并添加到容器中*/
@Configuration
public class MyConfiguration {@Bean(name = "myBean")public MyBean myBean() {return new MyBean();}@Bean(name = "anotherBean")public AnotherBean anotherBean() {return new AnotherBean();}@ConditionalOnBean(name = {"myBean", "anotherBean"})@Beanpublic MyService myService() {return new MyService();}
}

2.3 @ConditionalOnProperty

@ConditionalOnProperty 注解是 Spring Framework 中的条件注解之一,用于基于配置属性的存在与否来决定是否应用某个配置。

定义了一个具有多个属性的注解 ConditionalOnProperty,它可以用于根据配置文件中的属性值来决定是否应用某个配置。

在这里插入图片描述

示例和说明

/*** @ConditionalOnProperty 注解指定了一个名为myapp.feature.enabled 的属性,当这个属性存在并且其值为"true"时,MyFeatureConfiguration 配置类中的配置会生效* havingValue 参数指定了期望的属性值,如果没有指定havingValue,则默认匹配任何非空值* matchIfMissing 参数指定了当配置文件中未设置该属性时,是否应该匹配。如果设置为 true,则表示当属性不存在时也匹配,这样可以设置默认行为*/
@Configuration
@ConditionalOnProperty(name = "myapp.feature.enabled",havingValue = "true",matchIfMissing = true
)
public class MyFeatureConfiguration {}
myapp.feature.enabled=true

2.4 @ConditionalOnExpression

@ConditionalOnExpression 是 Spring 框架中的一个条件注解,在应用配置时根据 SpEL表达式的结果来决定是否进行配置。它允许我们使用更灵活的表达式来控制配置的条件。

定义了一个具有一个属性的注解 ConditionalOnExpression,它可以根据 SpEL 表达式的结果来决定是否应用某个配置。

在这里插入图片描述

示例和说明:

/*** 检查配置文件中的 my.config.enabled 属性是否等于 'true'* 如果等于 'true',则表达式结果为 true`,MyBean 实例将会被创建* 否则,表达式结果为 false,配置将被忽略,不会创建 MyBean 实例*/
@Configuration
public class MyConfig {@Bean@ConditionalOnExpression("#{environment.getProperty('my.config.enabled') == 'true'}")public MyBean myBean() {// 配置生效时创建 MyBean 实例return new MyBean();}
}

2.5 @ConditionalOnMissingBean

@ConditionalOnMissingBean 是一个 Spring Boot 中常用的条件注解,它的作用是:当容器中不存在指定的 Bean 时,才会进行配置。

定义了一个具有多个属性的注解 ConditionalOnMissingBean,用于根据存在或缺少特定类型的 bean 来决定是否应用某个配置。

在这里插入图片描述

示例和说明:

/*** 使用 @ConditionalOnMissingBean 注解来判断容器中是否已经存在了 MyService 类型的 Bean* 如果不存在,则创建一个 MyServiceImpl 实例并返回* 否则,不进行任何操作。*/
@Configuration
public class MyConfiguration {@Bean@ConditionalOnMissingBean(MyService.class)public MyService myService() {return new MyServiceImpl();}
}

三、条件装配的实现原理

条件装配的实现原理主要基于Spring的IoC容器和@Conditional注解。

在Spring的IoC容器中,BeanFactoryPostProcessor和BeanPostProcessor是两个核心的接口,它们允许我们在bean的创建和配置过程中添加额外的逻辑。(想要了解源码,读者可以查看我前面的博文)

条件装配的实现原理

  1. @Conditional注解:这个注解可以标记在类、方法或注解上,用于指定在特定的条件满足时才创建和配置bean。@Conditional注解需要一个Class类型的参数,这个参数需要实现Condition接口。
  2. Condition接口:这是一个函数式接口,它定义了一个matches(ConditionContext context, AnnotatedTypeMetadata metadata)方法。
    • 这个方法返回一个boolean值,表示条件是否满足。如果返回true,则Spring容器会创建和配置相应的bean;如果返回false,则不会创建和配置。
    • 两个参数提供了关于Spring容器和当前正在评估的bean的元数据信息。
  3. 自动配置:在Spring Boot中,条件装配被广泛应用于自动配置。
    • Spring Boot会根据我们在pom.xml文件中引入的依赖,自动配置相应的bean。
    • 这是通过一系列的AutoConfiguration类实现的,这些类上通常会使用@ConditionalOnClass@ConditionalOnMissingBean等注解来指定条件。

四、实际案例

假设正在开发一个在线商城的 Spring Boot 应用程序,其中包含了用户管理和订单管理两个模块。现在,希望在用户注册时发送一封欢迎邮件,但是如果用户已经在系统中存在,则不发送邮件。

ps:使用条件注解 @ConditionalOnMissingBean 来实现这一定制化功能。

  1. 创建一个邮件服务接口 EmailService 和实现类 WelcomeEmailService
/*** 邮件服务接口*/
public interface EmailService {void sendWelcomeEmail(String email);
}/*** 发送欢迎邮件*/
@Service
public class EmailServiceImpl implements EmailService {@Overridepublic void sendWelcomeEmail(String email) {// 发送欢迎邮件的逻辑System.out.println("Sending welcome email to: " + email);}
}
  1. 创建一个用户服务类 UserService,在用户注册时调用邮件服务发送欢迎邮件。
public interface UserService {public void registerUser(String email);
}/*** 在用户注册时检查是否已经存在该用户,如果不存在则发送欢迎邮件*/
@Service
public class UserServiceImpl implements UserService {private final UserMapper userMapper;private final EmailService emailService;@Autowiredpublic UserServiceImpl(UserMapper userMapper, EmailService emailService) {this.userMapper = userMapper;this.emailService = emailService;}@Overridepublic void registerUser(String email) {if(!userMapper.existsByEmail(email)) {userMapper.save(email);emailService.sendWelcomeEmail(email);}else {throw new IllegalArgumentException("Email already exists");}}
}
  1. 创建一个 UserRepository实现,它使用HashSet来模拟存储用户信息。
/*** 不想使用数据库,直接使用HashSet来模拟存储用户信息的email* 使用一个HashSet来存储注册过的email,HashSet不允许存储重复的元素* @author LEK*/
@Repository
public class UserMapper {private final Set<String> registeredEmails = new HashSet<>();public boolean existsByEmail(String email) {return registeredEmails.contains(email);}public void save(String email) {if (Objects.nonNull(email) && !email.isEmpty()) {registeredEmails.add(email);}}
}
  1. 使用 @ConditionalOnMissingBean 注解来确保只有在容器中不存在 EmailService 的实现类时才会注入 WelcomeEmailService。这样,如果用户在系统中已经存在,就不会发送欢迎邮件。
@Configuration
public class EmailConfig {/*** 邮件配置* */@Bean@ConditionalOnMissingBean(EmailService.class)public EmailServiceImpl email() {return new EmailServiceImpl();}
}
  1. 新建UserServiceImplTest测试类,由于是使用HashSet来模拟运行,每次启动都是不存在的,然后手动一下。
@SpringBootTest
public class UserServiceImplTest {@Autowiredprivate UserService userService;@Testpublic void testRegisterExistingUser() {String existingEmail = "existing@example.com";userService.registerUser(existingEmail);// 注册已存在的用户,预期会抛出 IllegalArgumentExceptionuserService.registerUser(existingEmail);}
}
  1. 运行效果。

在这里插入图片描述

哪儿有勤奋,哪儿就有成功

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

C++进阶:继承

文章目录 继承的概念继承的定义方式继承关系和访问限定符基类和派生类对象的赋值转换继承中的作用域派生类中的默认成员函数构造函数拷贝构造函数赋值拷贝函数析构函数 总结 继承的概念 继承(inheritance)机制是面向对象程序设计使代码可以复用的最重要的手段&#xff0c;它允…

【全开源】Java无人共享棋牌室茶室台球室系统JAVA版本支持微信小程序+微信公众号

无人共享棋牌室系统——棋牌娱乐新体验 &#x1f3b2;引言 随着科技的不断发展&#xff0c;传统棋牌室正逐渐迈向智能化、无人化。今天&#xff0c;我要为大家介绍的就是这款引领潮流的“无人共享棋牌室系统”。它不仅为棋牌爱好者提供了全新的娱乐体验&#xff0c;更在便捷性…

【Python】数据处理:SQLite操作

使用 Python 与 SQLite 进行交互非常方便。SQLite 是一个轻量级的关系数据库&#xff0c;Python 标准库中包含一个名为 sqlite3 的模块&#xff0c;可以直接使用。 import sqlite3数据库连接和管理 连接到 SQLite 数据库。如果数据库文件不存在&#xff0c;则创建一个新数据库…

Conda安装

conda可以做到不同项目就用不同虚拟环境,这样就能做到每个项目的依赖包都是相互独立 一、windows Download Success | Anaconda 环境变量 二、nano 本次安装Archiconda的外部python版本为python3.7.1

Vue基础知识:异步DOM更新是什么?$nextTick是什么?到底应该如何使用。什么是同步?什么是异步?

要先了解异步dom更新是什么就必须先了解&#xff0c;什么是同步&#xff1f;什么是异步&#xff1f; 1.什么是同步&#xff1f;什么是异步&#xff1f; 同步&#xff08;Synchronous&#xff09;&#xff1a; 同步操作是按照代码的顺序执行的&#xff0c;每个操作都必须等待上…

【数学建模】MATLAB入门教程:插值与拟合(下)

前言 插值与拟合在数据处理和科学计算中扮演着非常重要的角色&#xff0c;它们用于估算未知数据点的值&#xff0c;帮助我们理解和预测数据趋势 一、一维插值 1、一维插值定义 已知n1个节点(,)(j0,1,...,n,其中互不相同&#xff0c;不妨设a<<...<b),求任一插值点(…

swift5 在当前控制器先dismiss后pop

如下图需要在present当前控制器时用全局变量firmwareUpgradePresentingVC先引用上一个控制器&#xff08;下面的代码亲测有效&#xff09; func dismissAndPop() {self.dismiss(animated: false) {firmwareUpgradePresentingVC.navigationController!.popViewController(animat…

Java基础面试重点-3

41. 简述线程生命周期(状态) 其它参考《多线程重点》中的说法。三种阻塞&#xff1a; 等待阻塞&#xff1a; 运行的线程执行o.wait()方法&#xff08;该线程已经持有锁&#xff09;&#xff0c;JVM会把该线程放入等待队列中。同步阻塞&#xff1a; 运行的线程在获取对象的同步…

Stable Diffusion 如何写出更优雅的 Prompt

在看了前面的课程后&#xff0c; 相信很多人都会有一个困惑&#xff0c;这个 prompt 咋写… 为什么我写的时候只能憋出来了一个 a girl, a boy, beautify … 再也想不到其他的了&#xff0c; 总感觉是吃了没文化的亏&#xff1f; 这一节课我们就来讲一讲 如何写好 prompt …

跟着AI学AI_08 NumPy 介绍

NumPy&#xff08;Numerical Python&#xff09;是一个用于科学计算的基础库&#xff0c;它为 Python 提供了支持大规模多维数组和矩阵 NumPy 介绍 NumPy&#xff08;Numerical Python&#xff09;是一个用于科学计算的基础库&#xff0c;它为 Python 提供了支持大规模多维数…

如何快速搭建自己的进销存系统?

什么是进销存系统&#xff1f; 进销存&#xff0c;是指企业管理过程中采购&#xff08;进&#xff09;—入库&#xff08;存&#xff09;—销售&#xff08;销&#xff09;的动态管理过程。进&#xff1a;指询价、采购到入库与付款的过程。进销存管理系统是对企业生产经营中物…

【Python】已完美解决:(Python键盘中断报错问题) KeyboardInterrupt

文章目录 一、问题背景二、可能出错的原因三、错误代码示例四、正确代码示例&#xff08;结合实战场景&#xff09;五、注意事项 已解决&#xff1a;Python中处理KeyboardInterrupt&#xff08;键盘中断&#xff09;报错问题 一、问题背景 在Python编程中&#xff0c;当我们运…

css系列:音频播放效果-波纹律动

介绍 语音播放的律动效果&#xff0c;通俗来说就是一个带动画的特殊样式的进度条&#xff0c;播放的部分带有上下律动的动画&#xff0c;未播放的部分是普通的灰色竖状条。 实现中夹带了less变量、继承和循环遍历&#xff0c;可以顺带学习一下。 结果展示 大致效果如图所示…

继承深度剖析

前言 从继承开始就开始C进阶了&#xff0c; 这一块需要好好学习&#xff0c;这块知识很重要&#xff0c; 坑有点多&#xff0c;所以是面试笔试的常客。 基本概念 继承(inheritance)机制是面向对象程序设计使代码可以复用的最重要的手段&#xff0c; 它允许程序员在保持原有…

k8s学习--kubernetes服务自动伸缩之水平伸缩(pod副本伸缩)HPA详细解释与案例应用

文章目录 前言HPA简介简单理解详细解释HPA 的工作原理监控系统负载模式HPA 的优势使用 HPA 的注意事项应用类型 应用环境1.metircs-server部署2.HPA演示示例&#xff08;1&#xff09;部署一个服务&#xff08;2&#xff09;创建HPA对象&#xff08;3&#xff09;执行压测 前言…

记一次 .NET某工厂报警监控设置 崩溃分析

一&#xff1a;背景 1. 讲故事 前些天有位朋友在微信上丢了一个崩溃的dump给我&#xff0c;让我帮忙看下为什么出现了崩溃&#xff0c;在 Windows 的事件查看器上显示的是经典的 访问违例 &#xff0c;即 c0000005 错误码&#xff0c;不管怎么说有dump就可以上windbg开干了。…

4090显卡 安装cuda 11.3 版本

文章目录 cuda 安装安装过程中会要求选择安装的内容更改cuda地址到你安装的地方 cuda 安装 cuda官网寻找cuda11.3 版本 https://developer.nvidia.com/cuda-11.3.0-download-archive?target_osLinux&target_archx86_64&DistributionUbuntu&target_version20.04&…

setOptMode -holdTargetSlack与-holdSlackFixingThreshod

我正在「拾陆楼」和朋友们讨论有趣的话题&#xff0c;你⼀起来吧&#xff1f; 拾陆楼知识星球入口 -holdTargetSlack与-holdSlackFixingThreshod这两个option都是针对hold slack的&#xff0c;前者限制slack的目标&#xff0c;默认是0&#xff0c;也就是说工具尽可能会收敛时序…

解决electron设置透明背景后,引入element-plus样式问题

首先给当前窗口设置自定义窗口以及背景色。 const mainWindow new BrowserWindow({width: 900,height: 670,show: false,autoHideMenuBar: true,...(process.platform linux ? { icon } : {}),webPreferences: {preload: join(__dirname, ../preload/index.js),sandbox: fal…

操作系统入门系列-MIT6.828(操作系统工程)学习笔记(七)---- 系统调用函数与GDB(Lab: system calls)

系列文章目录 操作系统入门系列-MIT6.828&#xff08;操作系统工程&#xff09;学习笔记&#xff08;一&#xff09;---- 操作系统介绍与接口示例 操作系统入门系列-MIT6.828&#xff08;操作系统工程&#xff09;学习笔记&#xff08;二&#xff09;---- 课程实验环境搭建&am…