第八节 条件装配案例讲解

一、条件装配的作用是什么

条件装配是 Spring 框架中一个强大的特性,使得开发者能够创建更加灵活和可维护的应用程序。在 Spring Boot 中,这个特性被大量用于自动配置,极大地简化了基于 Spring 的应用开发。

二、条件装配注解

<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-autoconfigure</artifactId><version>${version}</version>
</dependency>

下面以版本2.1.13.RELEASE为例子。 下面是包路径

org.springframework.boot.autoconfigure.condition.XXX

2.1 条件注解

2.2 条件注解说明

下面是相关注解的简单说明,标注颜色的可以重点关注

注解

作用

@ConditionalOnBean

当容器中存在指定的Bean时,满足条件。

@ConditionalOnMissingBean

当容器中不存在指定的Bean时,满足条件。

@ConditionalOnClass

当类路径上存在指定的类时,满足条件。

@ConditionalOnMissingClass

当类路径上缺少指定的类时,满足条件。

@ConditionalOnProperty

当存在指定的配置属性,并且属性值与给定的值相匹配时,满足条件。

@ConditionalOnResource

当指定的资源存在于类路径上时,满足条件。

@ConditionalOnExpression

基于SpEL表达式的结果,当表达式为true时,满足条件。

@ConditionalOnJava

当Java的版本匹配指定的条件时,满足条件。

@ConditionalOnWebApplication

当应用程序是一个Web应用时,满足条件。

@ConditionalOnNotWebApplication

当应用程序不是一个Web应用时,满足条件。

@ConditionalOnJndi

当指定的JNDI存在时,满足条件。

@ConditionalOnSingleCandidate

当容器中只存在一个指定的Bean,或者即使有多个也有一个被标记为首选时,满足条件。

前面5个可以重点关注,它是最常用的。在第三节中,我将分别针对上面的几个案例进行举例说明。

三、条件装配举例说明

用代码的方式来展示条件装配的作用,以及在实际生活中到底如何使用。

3.1 灵活性举例

场景描述:

假设我们正在开发一个多环境支持的应用程序,该应用在开发环境中使用基于内存的数据库(如 H2),而在生产环境中使用更稳定和持久的关系数据库(如 PostgreSQL)。

实现方式:

不使用条件装配时,我们可能需要在应用程序的不同版本中手动更改数据源配置,或者在代码中进行环境检查来决定使用哪个数据库。这样做会增加维护工作,也容易出错。

使用条件装配,我们可以利用 注解来根据环境自动配置不同的数据源:

@Configuration
@ConditionalOnProperty(name = "use-in-memory-db", havingValue = "true")
public class H2DatabaseConfig {// Configuration for H2 Database
}@Configuration
@ConditionalOnProperty(name = "use-in-memory-db", havingValue = "false")
public class PostgresDatabaseConfig {// Configuration for PostgreSQL Database
}

例子解释:

在这个例子中,我们有两个配置类,分别为 H2DatabaseConfig 和 PostgresDatabaseConfig。它们都使用了 @ConditionalOnProperty 注解来判断一个名为 use-in-memory-db 的属性的值。

  • 如果 use-in-memory-db 属性设置为 true,Spring Boot 会实例化 H2DatabaseConfig 类并使用 H2 数据库配置。
  • 如果 use-in-memory-db 属性设置为 false,Spring Boot 会实例化 PostgresDatabaseConfig 类并使用 PostgreSQL 数据库配置

提示:案例演示了 @ConditionalOnProperty 使用方式。

3.2 自动装配案例

根据配置,选择使用不通的缓存实现类。

简化实现

public interface CacheService {void put(String key, Object value);Object get(String key);
}

Redis 缓存服务实现:

@Configuration
@ConditionalOnClass(name = "redis.clients.jedis.Jedis")
public class RedisCacheConfig {@Beanpublic CacheService redisCacheService() {return new RedisCacheService();}// RedisCacheService 实现了 CacheService 接口// 使用 Redis 客户端进行具体的缓存操作
}

内存缓存服务实现:

@Configuration
@ConditionalOnMissingClass(value = "redis.clients.jedis.Jedis")
public class InMemoryCacheConfig {@Beanpublic CacheService inMemoryCacheService() {return new InMemoryCacheService();}// InMemoryCacheService 实现了 CacheService 接口// 使用 Java Map 进行缓存操作
}

在这个例子中,我们有两个配置类 RedisCacheConfig 和 InMemoryCacheConfig,它们都定义了一个用于条件装配的 CacheService Bean。

@ConditionalOnClass(name = "redis.clients.jedis.Jedis") 注解在 RedisCacheConfig 类上,表明如果类路径包含 Redis 的 Jedis 客户端类,则创建和注册 RedisCacheService 为 CacheService 的实现。

相对地,@ConditionalOnMissingClass(value = "redis.clients.jedis.Jedis") 注解在 InMemoryCacheConfig 类上,表明如果类路径不包含 Redis 的 Jedis 客户端类,则创建和注册 InMemoryCacheService 为 CacheService 的实现。

在这个条件装配的例子中,Spring Boot 应用会自动检查 Redis 客户端库是否存在,并基于检查结果来决定使用哪个缓存实现。这个过程完全自动化,无需开发者介入,也无需修改配置文件。如果你将 Redis 客户端库添加到项目的依赖中,Spring Boot 将自动配置使用 RedisCacheService;如果移除了相关依赖,则会回退到使用 InMemoryCacheService。

提示:案例演示了 @ConditionalOnClassConditionalOnClass的使用方式。

3.4 避免条件冲突举例

假设我们的应用程序中既可以使用JPA来访问数据库,也可以使用MyBatis。但是,我们希望这两个持久层框架不要同时生效,以免它们尝试操作同一个数据库实体时产生冲突

JPA配置类:

@Configuration
@ConditionalOnClass(name = "org.springframework.data.jpa.repository.JpaRepository")
public class JpaConfig {// 这里配置JPA相关的Bean,例如EntityManagerFactory, TransactionManager等
}

MyBatis配置类:

@Configuration
@ConditionalOnClass(name = "org.mybatis.spring.SqlSessionFactoryBean")
@ConditionalOnMissingBean(type = "org.springframework.data.jpa.repository.JpaRepository")
public class MyBatisConfig {// 这里配置MyBatis相关的Bean,例如SqlSessionFactory, DataSource等
}

例子解释:

@ConditionalOnClass 注解检查类路径上是否存在指定的类。在这个例子中,JpaConfig 类上的 @ConditionalOnClass 注解会检查JPA的 JpaRepository 是否存在于类路径上,如果存在,那么JPA的相关配置就会被注册。

MyBatisConfig 类上同时使用了 @ConditionalOnClass 和 @ConditionalOnMissingBean 注解。@ConditionalOnClass 注解会检查类路径上是否存在MyBatis的 SqlSessionFactoryBean 类;而 @ConditionalOnMissingBean 注解则确保只有在不存在 JpaRepository 类型的Bean时,MyBatis的配置才会生效

避免Bean冲突:

这种配置方法确保了当两种持久层技术的类都存在于类路径上时,只有JPA的配置会生效,因为 JpaConfig 没有额外的限制条件。而MyBatis的配置则只有在JPA不可用(即类路径上没有 JpaRepository)时才会生效,从而避免了同时注册JPA和MyBatis持久层可能引起的Bean冲突。

举例说明,实际案例。以 tomcat 容器为例子。

请思考这样一个场景,为什么,我们的 SpringBoot 项目,不再关注 tomcat、jetty 容器了呢;他们的加载装配是如何实现的呢?

当Spring Boot在类路径上发现tomcat-embedded.jar时,它会自动配置Tomcat作为应用程序的默认服务器。同样,如果它发现jetty-embedded.jar,而没有发现Tomcat,那么它将配置Jetty服务器。

以下是简化后的Tomcat和Jetty自动配置的源码示例,用于解释Spring Boot是如何使用条件注解来实现自动配置的。

Tomcat自动配置:

@Configuration(proxyBeanMethods = false)
@ConditionalOnClass({Servlet.class, Tomcat.class, UpgradeProtocol.class})
@ConditionalOnMissingBean(value = ServletWebServerFactory.class, search = SearchStrategy.CURRENT)
public class TomcatServletWebServerFactoryConfiguration {@Beanpublic TomcatServletWebServerFactory tomcatServletWebServerFactory() {return new TomcatServletWebServerFactory();}}

上面的配置类使用了@ConditionalOnClass注解,它意味着只有当Servlet、Tomcat和UpgradeProtocol这三个类都存在于类路径上时,该配置类才会被考虑。此外,@ConditionalOnMissingBean注解确保只有在当前上下文中不存在ServletWebServerFactory类型的Bean时,才会创建TomcatServletWebServerFactory的Bean。

3.5 提升性能

避免加载和初始化不需要的组件,可以提升应用程序的启动速度和运行时性能

Spring Boot 条件装配通过确保只有在需要时才创建和注册特定的 Beans,从而帮助提高应用程序的启动速度和运行时性能。这将避免浪费资源去加载和初始化那些在当前应用程序环境中不需要的组件。

下面是一个代码示例,它展示了如何使用 Spring Boot 条件装配来决定是否配置一个用于性能监控的组件。

场景描述:

假设我们的应用程序包含一个性能监控组件,这个组件在生产环境中是有用的,但在开发和测试环境中可能不需要它,因为它可能会降低应用的性能和增加额外的资源消耗.

@Configuration
@ConditionalOnProperty(name = "app.monitoring.enable", havingValue = "true")
public class PerformanceMonitoringConfig {@Beanpublic PerformanceMonitor performanceMonitor() {// 初始化性能监控组件return new PerformanceMonitor();}// 其它与性能监控相关的Beans...
}

在这个例子中,我们创建了一个配置类 PerformanceMonitoringConfig,它包含了创建 PerformanceMonitor Bean 的方法。类上的 @ConditionalOnProperty 注解告诉 Spring Boot,只有当 app.monitoring.enable 属性的值设置为 true 时,才创建 PerformanceMonitor Bean。

应用配置文件:

开发或测试环境 application-dev.properties 或 application-test.properties:

# 在开发和测试环境中,禁用性能监控
app.monitoring.enable=false

生产环境 :

# 在生产环境中,启用性能监控
app.monitoring.enable=true

提升性能的效果:

通过上述条件装配的方式,Spring Boot应用会根据配置文件中的属性值决定是否加载和初始化性能监控组件。这种机制确保了在开发和测试环境中,不会因为性能监控组件的存在而造成不必要的性能开销;而在生产环境中,性能监控组件才会被激活,为我们提供关键的性能数据。

这样的条件装配策略有助于在不同的环境中优化资源使用和应用性能,因为只有真正需要的组件才会被加载,从而加快了应用程序的启动时间,并在运行时提高了性能。

3.6 Profile(补充)

根据环境不同(开发环境和生产环境)来使用不同的邮件服务实现。在生产环境中,我们使用真实的邮件服务,而在开发环境中,我们使用一个模拟的邮件服务以避免发送真实的邮件。

public interface EmailService {void sendEmail(String to, String subject, String content);
}
  1. 定义邮件服务接口:

除去上面的注解外,在 Spring 中也提供了 Profile 来实现一定的条件装配。

生产环境的邮件服务实现:

@Profile("prod")
@Service
public class SmtpEmailService implements EmailService {// SMTP邮件服务相关的配置和方法public void sendEmail(String to, String subject, String content) {// 实际的邮件发送逻辑}
}

开发环境的邮件服务模拟实现:

@Profile("dev")
@Service
public class MockEmailService implements EmailService {// 模拟邮件服务的相关配置和方法public void sendEmail(String to, String subject, String content) {System.out.println("Mock email sent to " + to + " with subject " + subject);// 其他模拟操作}
}

在这个例子中,我们定义了一个邮件服务的接口 EmailService 和两个实现 SmtpEmailService 以及 MockEmailService。每个实现类上都使用了 Spring 的 @Profile 注解来指定它应该在哪个应用配置文件激活的情况下被注册。

  • @Profile("prod") 表示 SmtpEmailService 将仅在应用程序的 prod 配置文件激活时注册到 Spring 容器中。
  • @Profile("dev") 表示 MockEmailService 将仅在应用程序的 dev 配置文件激活时注册到 Spring 容器中。

在不同的环境中,通过设置 spring.profiles.active 属性,我们可以决定激活哪个配置文件。

3.7 更多场景

还可以实现模块化的加载,不仅仅是停留在单独的一个类上面。

Spring Boot的条件装配可以支持模块化开发,模块化是指将一个应用程序分解成一组可以独立开发、测试和部署的模块。条件装配允许开发者根据应用程序的不同部分是否存在或者某个条件是否满足来实现模块的动态加载与集成。

特别适合多云场景; 比如当前,有很多信创项目,完全可以使用条件装配来加载不通的实现类。

作者曾经,使用条件装配改造过登录模块。

  1. 使用扫描登录
  2. 使用密码登录
  3. 使用 OAUTH 登录

......

除去上面案例,还可以进行改造。比如 mq 的改造。不同环境使用不同的 mq 进行改造等。

  1. 信创环境使用 activimq
  2. 云上使用 rocketmq

3.....

还有数据库的适配,mysql、pg等。

四、本章小结

系统通过上面的几个案例,让你感受到了条件装配它的作用和好处。 方便以后在工作项目中如何合适地使用它们。

已同步发布到公众号:面汤放盐 第八节 条件装配案例讲解 (qq.com)

掘金账号:​​​​​​​第八节 条件装配案例讲解 - 掘金 (juejin.cn)

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

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

相关文章

Function Calling 介绍与实战

functions 是 Chat Completion API 中的可选参数&#xff0c;用于提供函数定义。其目的是使 GPT 模型能够生成符合所提供定义的函数参数。请注意&#xff0c;API不会实际执行任何函数调用。开发人员需要使用GPT 模型输出来执行函数调用。 如果提供了functions参数&#xff0c;…

AIGC:AI整活!万物皆可建筑设计

在过去的一年里 AI设计爆火 各行业纷纷将之用于工作中 同时不少网友也在借助它整活 万物皆可设计 甲方骂我方案像屎一样 于是我就回馈他屎一样的方案 他有点惊喜&#xff0c;但是没话 不是吧&#xff0c;随便找了个充电头图片 也能生成建筑设计&#xff01;这都能行 鸟…

【spring】@CrossOrigin注解学习

CrossOrigin介绍 CrossOrigin 是 Spring Framework 中的一个注解&#xff0c;用于处理跨域资源共享&#xff08;CORS&#xff09;问题。CORS 是一种机制&#xff0c;它使用额外的 HTTP 头来告诉浏览器&#xff0c;让运行在一个 origin (domain) 上的Web应用被准许访问来自不同…

虹科Pico汽车示波器 | 免拆诊断案例 | 2017款奔驰E300L车行驶中发动机偶尔无法加速

故障现象 一辆2017款奔驰E300L车&#xff0c;搭载274 920发动机&#xff0c;累计行驶里程约为21万km。车主反映&#xff0c;该车行驶中发动机偶尔无法加速&#xff0c;且车辆发闯。 故障诊断 用故障检测仪检测&#xff0c;发动机控制单元&#xff08;N3/10&#xff09;中存储…

Labelme自定义数据集COCO格式【实例分割】

参考博客 labelme标注自定义数据集COCO类型_labelme标注coco-CSDN博客 LabelMe使用_labelme中所有的create的作用解释-CSDN博客 1制作自己的数据集 1.1labelme安装 自己的数据和上面数据的区别就在于没有.json标签文件&#xff0c;所以训练自己的数据关键步骤就是获取标签文…

Web3探索加密世界:空投常见类型有哪些?附操作教程

每种空投类型都有独特的特征和目的&#xff0c;我们需要了解不同类型的加密空投。本文给大家介绍的是流行的加密货币空投类型&#xff0c;以及一般空投是如何做的。感兴趣的话请看下去。 一、空投常见类型 1、持有者空投 持有者空投向钱包中持有一定数量数字货币的人免费发放…

2024.05.24 学习记录

1、面经复习&#xff1a; js基础、知识深度、js垃圾回收 2、代码随想录刷题&#xff1a;动态规划 完全背包 all 3、rosebush 完成 Tabs、Icon、Transition组件

基于YOLO系列算法(YOLOv5、YOLOv6、YOLOv8以及YOLOv9)和Streamlit框架的行人头盔检测系统

摘要 本文基于最新的基于深度学习的目标检测算法 (YOLOv5、YOLOv6、YOLOv8)以及YOLOv9) 对头盔数据集进行训练与验证&#xff0c;得到了最好的模型权重文件。使用Streamlit框架来搭建交互式Web应用界面&#xff0c;可以在网页端实现模型对图像、视频和实时摄像头的目标检测功能…

C++中获取int最大与最小值

不知道大家有没有遇到过这种要求&#xff1a;“返回值必须是int&#xff0c;如果整数数超过 32 位有符号整数范围 [−2^31, 2^31 − 1] &#xff0c;需要截断这个整数&#xff0c;使其保持在这个范围内。例如&#xff0c;小于 −2^31 的整数应该被固定为 −2^31 &#xff0c;大…

保护元件-详实的熔断器(保险丝)知识

目录&#xff1a; 一、汽车保险丝设计与选型 1、概述 2、构造及工作原理 1&#xff09;构造 2&#xff09;工作原理 3&#xff09;保险丝熔断及分断时间 4&#xff09;时间/电流特性曲线 5&#xff09;环境温度修正系数 3、熔化热能值I2t★ 4、三种电流模型 1&a…

java技术:spring-secrity实现认证、授权

目录 一、依赖 二、逻辑图 三、代码设计 1、WebSecurityConfigurerAdapter的实现类 2、设计登录接口 config配置&#xff1a; 1&#xff09;UserDetailsService实现类重写&#xff1a; 2&#xff09;书写登录实现类&#xff08;调用authenticationManager、可以与后面的…

Vue学习笔记2——创建一个Vue项目

Vue项目 1、创建一个Vue项目2、Vue项目的目录结构3、模版语法4、属性绑定5、条件渲染 1、创建一个Vue项目 vue官方文档&#xff1a; https://cn.vuejs.org/打开命令行界面&#xff08; “winR"再输入"cmd”&#xff09;&#xff0c;切换位置到指定的位置创建vue项目…

微星打造全球首款DDR5 CAMM2内存主板:这下清爽了

微星预告了新款主板“ Z790 Project Zero Plus”&#xff0c;这也是 全球第一款配备DDR5 CAMM2内存的桌面主板&#xff01; CAMM全称为“Compression Attached Memory Module”&#xff0c;已被JEDEC组织接纳为行业标准&#xff0c;相比传统的SO-DIMM、DIMM内存最大特点就是取消…

MM模块五(采购订单)

创建采购订单&#xff1a;ME21N 参考报价单创建采购订单&#xff1a; 哪个采购部门的哪个采购员为哪个公司代码下的采购订单 点击保存 注&#xff1a;采购订单的数量不能大于采购申请的数量

Milvus 高级特性

引言 Milvus 的高级特性包括分布式架构、安全性和监控与日志管理&#xff0c;这些特性使得 Milvus 能够更好地适应企业级应用的需求。 一、分布式架构 1.1 集群部署 在当今大数据和人工智能的时代&#xff0c;向量数据库以其对高维数据的强大处理能力&#xff0c;成为了许多…

Map遍历、反射、GC

map的遍历 用foreach遍历 HashMap<Character,Integer> map new HashMap<>();map.put(A,2);map.put(B,3);map.put(C,3);for (Map.Entry<Character,Integer> entry: map.entrySet()) {char key entry.getKey();int value entry.getValue();System.out.prin…

影视解说5.0版零基础视频课程

课程简介 现在还能做解说吗、不会写解说文案怎么解决、不会配音怎么解决、如何找到合适的素材资源、如何变现…这是很多想做解说的伙伴最关心的几大问题。比如文案&#xff0c;我们推荐一个网站&#xff0c;10分钟搞定一篇文案&#xff0c;配音可以真人配音也可以软件配音。5.…

【区块链】fisco网络运维之添加节点黑名单

基于已完成的区块链系统与管理平台搭建工作&#xff0c;开展区块链节点的黑名单工作&#xff0c;具体操作如下 以node3为例子 1查看node0节点的连接状态日志&#xff08;现有4个节点连接&#xff09; 注意&#xff1a;如果查询不到连接状态&#xff0c;修改node0的配置文件中…

Flask多线程开发指南

文章目录 1. 什么是多线程&#xff1f;2. Flask中的多线程3. 注意事项结论 在Web应用程序开发中&#xff0c;有时候需要处理一些耗时的任务&#xff0c;例如与数据库交互、发送网络请求或执行计算密集型的操作。为了保持用户体验的流畅性&#xff0c;我们可以使用多线程来处理这…

LoadBalancer

一、手写随机负载均衡 1、引入依赖 <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId> </dependency><!--引入nacos discovery--> <dependency><groupId>com…