解密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 {

    @Bean
    public 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 {

    @Bean
    public MyBean myBean() {
        return new MyBean();
    }

    @ConditionalOnBean(MyBean.class)
    @Bean
    public MyService myService() {
        return new MyService();
    }
}
  1. 多个 Bean 的情况
/**
 * MyService 类被标记为 @ConditionalOnBean({MyBean.class, AnotherBean.class}),这意味着只有当容器中同时存在 MyBean 和 AnotherBean 类型的 Bean 时,MyService 才会被创建并添加到容器中
 */
@Configuration
public class MyConfiguration {

    @Bean
    public MyBean myBean() {
        return new MyBean();
    }

    @Bean
    public AnotherBean anotherBean() {
        return new AnotherBean();
    }

    @ConditionalOnBean({MyBean.class, AnotherBean.class})
    @Bean
    public 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"})
    @Bean
    public 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 {
    @Override
    public 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;

    @Autowired
    public UserServiceImpl(UserMapper userMapper, EmailService emailService) {
        this.userMapper = userMapper;
        this.emailService = emailService;
    }

    @Override
    public 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 {

    @Autowired
    private UserService userService;

    @Test
    public void testRegisterExistingUser() {
        String existingEmail = "existing@example.com";

        userService.registerUser(existingEmail);
        // 注册已存在的用户,预期会抛出 IllegalArgumentException
        userService.registerUser(existingEmail);
    }
}

  1. 运行效果。

在这里插入图片描述

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

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

办公风云颜值背后的职场正能量

办公风云&#xff1a;颜值背后的职场正能量当我们提到职场&#xff0c;脑海中浮现的往往是严肃的面孔、忙碌的身影和堆积如山的文件。但在这个看似单调的舞台上&#xff0c;总有一些人&#xff0c;用他们的颜值和才华&#xff0c;为我们上演了一场场别开生面的“大戏”。今天&a…

68. UE5 RPG 处理多个角色后续bug

我们现在已经有了四个敌人角色&#xff0c;接下来&#xff0c;处理一下在战斗中遇到的问题。 处理角色死亡后还会攻击的问题 因为我们有角色溶解的效果&#xff0c;角色在死亡以后的5秒钟才会被销毁掉。所以在这五秒钟之内&#xff0c;角色其实还是会攻击。主要时因为AI行为树…

Gh-ost让MySQL在线表结构变更不再是难题

Gh-ost&#xff1a;无缝迁移&#xff0c;效率与安全并行- 精选真开源&#xff0c;释放新价值。 概览 gh-ost是由GitHub团队精心打造的在线MySQL表结构迁移工具&#xff0c;它以一种无需触发器的方式&#xff0c;实现了对数据库表结构变更的在线操作。gh-ost的设计初衷是解决现…

NetSuite精益实施 之 系统切换作业标准化

这个题目为近日所思&#xff0c;一直没有落笔。今天是端午假日&#xff0c;得空卸货。 标准化是精益实施的三个基础之一&#xff0c;在我们的项目实践中没有须臾忘记。在此我们不再赘述标准化为啥这么重要&#xff0c;更多来分享如何标准化。 在项目实施的各阶段中&#xff0…

汇编语言作业(六)

目录 一、实验目的 二、实验内容 三、实验步骤以及结果 四、实验结果与分析 五、实验总结 一、实验目的 掌握加减法运算指令对各状态标志位的影响及测试方法掌握汇编语言长整数的加法的操作方法 二、实验内容 对于以下几组数&#xff0c; 087H和034H 0C2H和5FH 0F3H和0F3H&am…

数据中心智能化运维发展研究报告(2023)解读

数据中心智能化运维发展研究报告&#xff08;2023&#xff09;解读 《数据中心智能化运维发展研究报告&#xff08;2023&#xff09;》探讨了数据中心智能化运维的概念、核心内容、实际应用和发展建议。报告指出&#xff0c;通过人工智能、大数据等新一代信息技术的深度应用&a…

代码随想录 -数组

1.二分算法 边界开闭 左闭右闭 原则 这里的&#xff0c;middle不是要找的值。那么nums【middle】>tager 我们要更新右边界为middle-1 &#xff08;因为要左区间 所以更新右边界&#xff09; 在这里插入图片描述 class Solution { public:int search(vector<int>&a…

探索基于订阅式的电视App:Android TV 端强大的开源视频播放器

探索基于订阅式的电视App&#xff1a;Android TV 端强大的开源视频播放器 在智能电视和流媒体日益普及的今天&#xff0c;一款强大的视频播放器是家庭娱乐的重要组成部分。正是这样一款为Android TV设计的开源视频播放器。本文将深入探讨电视盒子OSC的技术特点、使用方法以及其…

transformer 位置编码源码解读

import torch import mathdef get_positional_encoding(max_len, d_model):"""计算位置编码参数&#xff1a;max_len -- 序列的最大长度d_model -- 位置编码的维度返回&#xff1a;一个形状为 (max_len, d_model) 的位置编码张量"""positional_e…

C++ | Leetcode C++题解之第135题分发糖果

题目&#xff1a; 题解&#xff1a; class Solution { public:int candy(vector<int>& ratings) {int n ratings.size();int ret 1;int inc 1, dec 0, pre 1;for (int i 1; i < n; i) {if (ratings[i] > ratings[i - 1]) {dec 0;pre ratings[i] rati…

【JAVASE】String 的应用案例

本篇文章旨在讲解String的应用&#xff0c;下面讲解两个案例&#xff1a; &#xff08;1&#xff09;完成用户登录 &#xff08;2&#xff09;开发验证码 一&#xff1a;完成用户登录 需求&#xff1a;系统正确的登录名是&#xff1a;csdnhg12&#xff0c;密码是&#xff1a…

WordPress 高级缓存插件 W3 Total Cache 开启支持 Brotli 压缩算法

今天明月给大家分享一下 WordPress 高级缓存插件 W3 Total Cache 开启支持 Brotli 压缩算法的教程&#xff0c;在撰写【WordPress 高级缓存插件 W3 Total Cache Pro 详细配置教程】一文的时候明月就发现 W3 Total Cache 已经支持 Brotli 压缩算法了&#xff0c;可惜的是在安装完…

数字孪生概念、数字孪生技术架构、数字孪生应用场景,深度长文学习

一、数字孪生起源与发展 1.1 数字孪生产生背景 数字孪生的概念最初由Grieves教授于2003年在美国密歇根大学的产品全生命周期管理课程上提出&#xff0c;并被定义为三维模型&#xff0c;包括实体产品、虚拟产品以及二者间的连接&#xff0c;如下图所示&#xff1a; 2011年&…

【C++进阶】深入STL之 栈与队列:数据结构探索之旅

&#x1f4dd;个人主页&#x1f339;&#xff1a;Eternity._ ⏩收录专栏⏪&#xff1a;C “ 登神长阶 ” &#x1f921;往期回顾&#x1f921;&#xff1a;模拟实现list与迭代器 &#x1f339;&#x1f339;期待您的关注 &#x1f339;&#x1f339; ❀stack和queue &#x1f4…

storage存储模块-vuex持久化处理

1&#xff1a;存储登录用户信息到vuex中 在store文件夹下面&#xff0c;创建modules文件夹在文件夹下创建user.js文件 user.js文件 const state {userInfo: {userId: ,token: } } const mutations {setUserInfo (state, obj) {console.info(obj)state.userInfo.userId obj…

论文研读 A Comparison of TCP Automatic Tuning Techniques for Distributed Computing

论文《分布式计算中TCP自动调优技术的比较》由Eric Weigle和Wu-chun Feng撰写&#xff0c;探讨了自动调整TCP缓冲区大小以提升分布式应用性能的不同方法。文章首先讨论了手动优化TCP缓冲区大小的局限性&#xff0c;并介绍了研究人员提出的各种自动调优技术来应对这些挑战。 作者…

iOS调整collectionViewCell顺序

效果图 原理 就是设置collectionView调整顺序的代理方法&#xff0c;这里要注意一点 调整过代理方法之后&#xff0c;一定要修改数据源&#xff0c;否则导致错乱。 还有就是在collectionView上面添加一个长按手势&#xff0c;在长按手势的不同阶段&#xff0c;调用collectionV…

LeetCode1318或运算的最小翻转次数

题目描述 给你三个正整数 a、b 和 c。你可以对 a 和 b 的二进制表示进行位翻转操作&#xff0c;返回能够使按位或运算 a OR b c 成立的最小翻转次数。「位翻转操作」是指将一个数的二进制表示任何单个位上的 1 变成 0 或者 0 变成 1 。 解析 这一题就按位依次比较就行了。取这…

大话设计模式解读02-策略模式

本篇文章&#xff0c;来解读《大话设计模式》的第2章——策略模式。并通过Qt和C代码实现实例代码的功能。 1 策略模式 策略模式作为一种软件设计模式&#xff0c;指对象有某个行为&#xff0c;但是在不同的场景中&#xff0c;该行为有不同的实现算法。 策略模式的特点&#…

【设计模式】创建型设计模式之 工厂模式

一、介绍 工厂模式可以分为 3 个小类 简单工厂模式工厂方法模式抽象工厂模式 工厂模式的工厂类&#xff0c;并不一定以 Factory 结尾&#xff0c;例如 DataFormat、Calender 他们都是工厂类&#xff0c;通过静态方法来创建实例。 除此之外&#xff0c;创建对象的方法名称一…