带你拿捏SpringBoot自动装配的核心技术?模块装配(@EnableXXX注解+@Import)+ 条件装配(@ConditionalXXX)

文章目录

    • Profile
      • 激活指定配置文件
        • 主配置文件中指定激活的profile
        • 命令行激活
        • 设置虚拟机参数激活
      • profile控制不到的地方
    • Spring原生的条件装配注解@Conditional
      • @Conditional接口讲解
      • 案例讲解
    • Spring Boot封装的条件装配注解@ConditionalXXX
      • 自己实现ConditionalOnBean
      • SpringBoot 源码案例注解

SpringBoot自动装配系列文章
@EnableXXX注解+@Import轻松实现SpringBoot的模块装配
带你拿捏SpringBoot自动装配的核心技术?模块装配(@EnableXXX注解+@Import)+ 条件装配(@ConditionalXXX)
深入探究Spring Boot自动配置原理及SPI机制:实现灵活的插件化开发

在这里插入图片描述

之前我们这篇文章@EnableXXX注解+@Import轻松实现SpringBoot的模块装配 提到了 SpringBoot自动装配的核心技术就是模块装配 + 条件装配!!!

在这篇文章我们完整的学习了模块装配的核心使用方法,通过模块装配,咱可以通过一个注解,一次性导入指定场景中需要的组件和配置。那么只靠模块装配的内容,就可以把这些装配都考虑到位吗?

只要配置类中声明了 @Bean 注解的方法,那这个方法的返回值就一定会被注册到 IOC 容器成为一个 Bean 。

所以,有没有办法解决这个问题呢?当然是有,总共有两种方式:Profile和@ConditionalXXX

先来学习第一种方式:Profile

Profile

通过Profile可以实现一套代码在不同环境启用不同的配置和功能。

@Profile 注解可以标注在组件上,当一个配置属性(并不是文件)激活时,它才会起作用,而激活这个属性的方式有很多种(启动参数、环境变量、web.xml 配置等)。

profile 提供了一种可以理解成“基于环境的配置”:根据当前项目的运行时环境不同,可以动态的注册当前运行环境匹配的组件

例如,我们分别定义开发、测试和生产这3个环境:

  • dev
  • test
  • production

创建某个Bean时,Spring容器可以根据注解@Profile来决定是否创建。我们这里还是拿上一章节的例子导入配置类中的LogBeanConfiguration

@Configuration
public class LogBeanConfiguration {@Bean@Profile("!dev")public MyLog myLog() {return new MyLog();}@Beanpublic LogUtil logUtil() {return new LogUtil();}}

如果当前的Profile设置为testproduction,则Spring容器才会调用myLog()创建MyLog类,而如果是dev环境,则这个类不会被创建

激活指定配置文件

主配置文件中指定激活的profile
spring:profiles:active: dev # 指定激活哪个配置文件
命令行激活

在linux生产环境中直接使用命令行启动项目,启动的同时可以指定激活的profile:

java -jar --spring.profiles.active=dev my-spring-boot-app.1.0.0.jar
设置虚拟机参数激活

在运行程序时,加上JVM参数-Dspring.profiles.active=test就可以指定以test环境启动。

命令行指定的方式和虚拟机参数设置的方式指定,都可以在IDEA的运行设置中进行配置,如下图:

在这里插入图片描述

profile控制不到的地方

使用Profile能根据不同的Profile进行条件装配,但是Profile控制比较糙, profile 控制的是整个项目的运行环境,无法根据单个 Bean 的因素决定是否装配。也是因为这个问题,出现了第二种条件装配的方式:@Conditional 注解

Spring原生的条件装配注解@Conditional

@Conditional接口讲解

要使用@Conditional注解,必须先了解一下Conditiona接口,它与@Conditional注解配合使用,通过源码我们也可以看出,使用@Conditional注解必须要指定实现Conditiona接口的class。

  • @Conditional 注解可以指定匹配条件,而被 @Conditional 注解标注的 组件类 / 配置类 / 组件工厂方法 必须满足 @Conditional 中指定的所有条件,才会被创建 / 解析。
  • @Conditional 是在 SpringFramework 4.0 版本正式推出的,它可以让 Bean 的装载基于一些指定的条件,换句话说,被标注 @Conditional 注解的 Bean 要注册到 IOC 容器时,必须全部满足 @Conditional 上指定的所有条件才可以。
@Target({ElementType.TYPE, ElementType.METHOD})
public @interface Conditional {/*** All {@link Condition Conditions} that must {@linkplain Condition#matches match}* in order for the component to be registered.*/Class<? extends Condition>[] value();
}

Conditiona接口中,只定义了一个方法matches,spring在注册组件时,也正是根据此方法的返回值TRUE/FALSE来决定是否将组件注册到spring容器中

@FunctionalInterface
public interface Condition {/*** Determine if the condition matches.* @param context 条件判断的上下文环境* @param metadata 正在检查的类或方法的注解元数据*/boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata);
}

matches中我们可以获取到ConditionContext接口,根据此接口对象可以获取BeanDefinitionRegistryConfigurableListableBeanFactory等重要对象信息,根据这些对象就可以获取和检查spring容器初始化时所包含的所有信息,再结合业务需求,就可以实现组件注册时的自定义条件判断。

案例讲解

LogBeanConfiguration 我们将之前的@Profile("!dev") 改为@Conditional(OnMyLogCondition.class)

@Configuration
public class LogBeanConfiguration {@Bean@Conditional(OnMyLogCondition.class)public MyLog myLog() {return new MyLog();}@Beanpublic LogUtil logUtil() {return new LogUtil();}}

OnMyLogCondition里面从ConditionContext获取ConfigurableListableBeanFactory,从而去判断需要有LogUtil Bean定义信息才会去创建MyLog类

public class OnMyLogCondition implements Condition {public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {return context.getBeanFactory().containsBeanDefinition(LogUtil.class.getName());}
}

Spring Boot封装的条件装配注解@ConditionalXXX

Spring本身提供了条件装配@Conditional,但是要自己编写比较复杂的Condition来做判断,比较麻烦。Spring Boot则为我们准备好了几个非常有用的条件:

  • @ConditionalOnProperty:如果有指定的配置,条件生效;
  • @ConditionalOnBean:如果有指定的Bean,条件生效;
  • @ConditionalOnMissingBean:如果没有指定的Bean,条件生效;
  • @ConditionalOnMissingClass:如果没有指定的Class,条件生效;
  • @ConditionalOnWebApplication:在Web环境中条件生效;
  • @ConditionalOnExpression:根据表达式判断条件是否生效。

我们以比较常用的@ConditionalOnBean为例,之前@Conditional(OnMyLogCondition.class)是使用@Conditional 直接传入Condition接口的实现类进行判断是否要创建MyLog,现在使用@ConditionalOnBean 可以直接传入LogUtil.class ,它会帮我们实现这个判断!

@Configuration
public class LogBeanConfiguration {@Bean@ConditionalOnBean(LogUtil.class)//@Conditional(OnMyLogCondition.class)  原来的是传入自定义Conditional实现类public MyLog myLog() {return new MyLog();}@Beanpublic LogUtil logUtil() {return new LogUtil();}}

自己实现ConditionalOnBean

以刚刚的ConditionalOnBean为例,我们自己动手造轮子实现一下,这个注解,看下他是怎么实现的呢!

首先自定义注解ConditionalOnBean,定义有默认的Class 数组类型的value以及String数组类型的beanNames

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD})
@Conditional(OnMyBeanCondition.class)
public @interface ConditionalOnBean {Class<?>[] value() default {};String[] beanNames() default {};
}

接着就是实现这个OnMyBeanCondition类

public class OnMyBeanCondition implements Condition {@Overridepublic boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {Map<String, Object> attributes = metadata.getAnnotationAttributes(ConditionalOnBean.class.getName());// 匹配类型Class<?>[] classes = (Class<?>[]) attributes.get("value");for (Class<?> clazz : classes) {if (!context.getBeanFactory().containsBeanDefinition(clazz.getName())) {return false;}}// 匹配beanNameString[] beanNames = (String[]) attributes.get("beanNames");for (String beanName : beanNames) {if (!context.getBeanFactory().containsBeanDefinition(beanName)) {return false;}}return true;}
}

使用的时候就只需要传入对应的.class即可,原来的是直接传入Condition接口的实现类,现在这个ConditionalOnBean注解相当于封装了一层

@Configuration
public class LogBeanConfiguration {@Bean@ConditionalOnBean(LogUtil.class)//@Conditional(OnMyLogCondition.class)  原来的是传入自定义Conditional实现类public MyLog myLog() {return new MyLog();}@Beanpublic LogUtil logUtil() {return new LogUtil();}}

发现是不是其实springboot帮我们做的东西也不难,只是封装套了一层

SpringBoot 源码案例注解

@ConditionalOnBean({DataSource.class})
@ConditionalOnClass({JpaRepository.class})
@ConditionalOnMissingBean({JpaRepositoryFactoryBean.class, JpaRepositoryConfigExtension.class})
@ConditionalOnProperty(prefix = "spring.data.jpa.repositories",name = {"enabled"},havingValue = "true",matchIfMissing = true
)
public class JpaRepositoriesAutoConfiguration {public JpaRepositoriesAutoConfiguration() {}
}

这段代码是一个基于Spring框架的Java代码片段,它定义了一个名为JpaRepositoriesAutoConfiguration的类,并使用了多个条件注解来控制这个类的自动配置。

@ConditionalOnBean({DataSource.class})注解表示只有在Spring容器中存在DataSource的Bean时,才会启用这个自动配置类。DataSource通常用于配置数据库连接。

@ConditionalOnClass({JpaRepository.class})注解表示只有在类路径中存在JpaRepository类时,才会启用这个自动配置类。JpaRepository是Spring Data JPA提供的接口,用于简化数据库访问的操作。

@ConditionalOnMissingBean({JpaRepositoryFactoryBean.class, JpaRepositoryConfigExtension.class})注解表示只有在容器中不存在JpaRepositoryFactoryBeanJpaRepositoryConfigExtension这两个Bean时,才会启用这个自动配置类。

@ConditionalOnProperty注解表示只有当指定的属性满足特定条件时,才会启用这个自动配置类。在这里,属性spring.data.jpa.repositories.enabled的值必须为true,或者如果该属性不存在时,也会启用这个自动配置类。

总结起来,这段代码定义了一个自动配置类JpaRepositoriesAutoConfiguration,它会根据一系列条件来判断是否要应用该自动配置。这些条件包括是否存在DataSource的Bean、是否存在JpaRepository类、是否缺少JpaRepositoryFactoryBeanJpaRepositoryConfigExtension这两个Bean,以及是否满足指定的属性条件。根据条件的不同,这个自动配置类可能会在Spring容器中自动配置一些与JPA相关的Bean。

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

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

相关文章

自制数据库空洞率清理工具-C版-03-EasyClean-V1.2(支持南大通用数据库Gbase8a)

目录 一、环境信息 二、简述 三、升级点 四、支持功能 五、空洞率 六、工具流程图 1、流程描述 2、注意点 &#xff08;1&#xff09;方法一 &#xff08;2&#xff09;方法二 七、清理空洞率流程图 八、安装包下载地址 九、参数介绍 1、命令模板 2、命令样例 3…

【集合大练习】---------------简易学生管理系统

目标&#xff1a; 实现学生对象新增&#xff0c;删除&#xff0c;查看&#xff0c;对象信息修改 整体实现思路&#xff1a; 1.定义学生类-------------创建学生对象 2.管理界面代码编写-------------命令提示面板 3.添加学生的代码编写---------add功能实现 4.查看学生信…

4、C语言:指针与数组

数组与指针 指针与地址指针与函数参数指针与数组地址算数运算字符指针与函数指针数组以及指向指针的指针多维数组命令行参数指向函数的指针复杂声明 指针是一种保存变量地址的变量。C语言中&#xff0c;指针的使用非常广泛&#xff0c;原因之一是&#xff0c;指针常常是表达某个…

智能合约笔记

前言&#xff1a; 首先了解下为什么会出现智能合约&#xff0c;打个比方现在有两个人A和B打赌明天会不会下雨&#xff0c;每个人赌注100元&#xff0c;如果第二天下雨则A拿走200元&#xff0c;否则B拿走200元&#xff0c;这样就有一个问题&#xff0c;赌注要到第二天才能见效&…

Python 中的字符串分割函数 split() 详解

更多Python学习内容&#xff1a;ipengtao.com 在 Python 编程中&#xff0c;处理字符串是一项常见的任务。字符串分割是其中的一个常见操作&#xff0c;而 Python 提供了强大的 split() 函数&#xff0c;用于将字符串拆分成多个部分。本文将详细介绍 split() 函数的用法、参数和…

Linux 转换文字编码与换行符 nkf命令

参考资料 【 nkf 】コマンド――文字コードと改行コードを変換するnkfコマンドでファイルの文字コードと改行コードを統一する 目录 一. 前期准备二. 乱码现象与分析三. nkf命令3.1 nkf --guess 查看文件编码3.2 nkf -w8 UTF-8(BOM)编码显示3.3 nkf -wd --overwrite 覆盖源文件…

MySQL核心SQL

一.结构化查询语言 SQL是结构化查询语言&#xff08;Structure Query Language&#xff09;&#xff0c;它是关系型数据库的通用语言。 SQL 主要可以划分为以下 3 个类别&#xff1a; DDL&#xff08;Data Definition Languages&#xff09;语句 数据定义语言&#xff0c;这…

C#无标题栏窗体拖动代码

文章目录 一、概念二、案例三、常见问题四、链接 一、概念 C#&#xff08;C Sharp&#xff09;是由微软公司开发的一种面向对象的编程语言。它是从C和C语言演化而来的&#xff0c;并结合了Java和其他编程语言的特性。C#是微软.NET平台的一部分&#xff0c;允许开发人员创建各种…

EMC VNXe / Unity存储系统如何找回密码

开始之前&#xff0c;先简单说说&#xff0c;EMC的VNXe存储之间的关系。 EMC的VNXe和Unity存储的操作系统OS是一样的&#xff0c;当然不是完全一样&#xff0c;但是架构是一样的&#xff0c;先推出的产品是VNXe&#xff0c;然后在这个基础上演进到了Unity&#xff0c;Unity XT…

港大谷歌提出GO-NeRF:在NeRF中生成协调且高质量的3D对象

尽管在3D生成方面取得了进展&#xff0c;但在作为NeRF表示的现有3D场景中直接创建3D对象仍然是未经探索的。这个过程不仅需要高质量的3D对象生成&#xff0c;还需要将生成的3D内容无缝地合成到现有的NeRF中。为此&#xff0c;作者提出了一种新方法&#xff0c;GO-NeRF&#xff…

电脑定时关机应用

这是一个Python应用。家里卧室装了新电视&#xff0c;HDMI连接笔记本追剧还是很愉快的。可是经常睡着&#xff0c;自然忘了关机。搜了一大圈&#xff0c;都是用命令行或者bat解决。商店里的应用也不好用&#xff0c;有些还收费。于是萌生了自己写一个定时关机应用的想法。利用N…

监控平台zabbix介绍与部署

1. 完整的项目 业务架构&#xff1a;客户端 -> 防火墙 -> 负载均衡&#xff08;四层、七层&#xff09;-> Web缓存/应用 -> 业务逻辑&#xff08;动态应用&#xff09;-> 数据缓存 -> 数据持久 运维架构&#xff1a;运维客户端 -> 堡垒机/跳板机&#x…

TLC Nand Flash 存储单元的读取原理

我们知道Nand Flash使用浮栅晶体管作为存储单元&#xff08;memory cell&#xff09;来存储数据&#xff0c;浮栅晶体管物理结构如图1所示&#xff1a; 图1 浮栅晶体管 对于普通的晶体管&#xff08;去掉浮栅晶体管中的浮栅层&#xff0c;floating gate&#xff09;&#xff0…

基于Docker的Nginx的安装与配置

基于Docker的Nginx的安装与配置 1 为Nginx创建一个容器1.1 学习docker run1.2 通过docker run为Nginx创建并启动一个容器 2 配置Nginx2.1 学习docker的bind mount技术2.2 在Nginx容器中找到想修改的文件所在的目录2.2.1 认识nginx.conf文件2.2.2 访问Nginx服务&#xff0c;默认…

【MATLAB】VMD_LSTM神经网络时序预测算法

有意向获取代码&#xff0c;请转文末观看代码获取方式~也可转原文链接获取~ 1 基本定义 变分模态分解&#xff08;Variational Mode Decomposition&#xff0c;VMD&#xff09;和LSTM&#xff08;Long Short-Term Memory&#xff09;神经网络结合的算法是一种用于处理时间序列…

超详细的嵌入式cJSON使用注意事项,持续补充中......

文章目录 一、堆内存不足1.1 问题描述1.2 解决办法 二、内存泄露2.1 忘记Delete2.2 忘记Free2.3 串口数据接收缺少部分字符导致的内存泄露(自己的问题)问题分析 2.4 内存泄露在Cortex-M3内核会发生什么&#xff1f; cJSON开源库地址&#xff1a; cJSON 一、堆内存不足 1.1 问…

ssm基于Web的课堂管理系统设计与实现论文

目 录 目 录 I 摘 要 III ABSTRACT IV 1 绪论 1 1.1 课题背景 1 1.2 研究现状 1 1.3 研究内容 2 2 系统开发环境 3 2.1 vue技术 3 2.2 JAVA技术 3 2.3 MYSQL数据库 3 2.4 B/S结构 4 2.5 SSM框架技术 4 3 系统分析 5 3.1 可行性分析 5 3.1.1 技术可行性 5 3.1.2 操作可行性 5 3…

【GitHub项目推荐--开源的坦克大战】【转载】

坦克大战当年红遍大江南北&#xff0c;很多和我一样的九零后应该都有着对这个游戏的记忆。现在显示器分辨率越来越高&#xff0c;使用矢量图来实现像素风格游戏&#xff0c;可以获得非常高的展现质量。 这个项目是作者肥超花了很长时间折腾的复刻版本&#xff0c;所有元素都使…

余弦相似度的计算以及公式

公式&#xff1a; 思想&#xff1a;余弦相似度的思想是通过计算两个向量之间的余弦值来衡量它们的相似程度。如果两个向量之间的夹角越小&#xff0c;它们的余弦值就越接近1&#xff0c;也就意味着它们越相似。而如果它们的夹角越大&#xff0c;余弦值就越接近0&#xff0c;也就…

ES的文档操作

一&#xff0c;DSL语句 1&#xff0c;新增文档&#xff1a; 2&#xff0c;查询文档和删除文档&#xff1a; ,3修改文档 修改有两种方式&#xff1a; 全量修改&#xff1a;直接覆盖原来的文档 增量修改&#xff1a;修改文档中的部分字段 全量修改 全量修改是覆盖原来的文档…