带你拿捏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();
    }
    
    @Bean
    public 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();
    }
    
    @Bean
    public 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();
    }
    
    @Bean
    public 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 {

    @Override
    public 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;
            }
        }
        // 匹配beanName
        String[] 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();
    }
    
    @Bean
    public 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。

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

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

相关文章

NLP论文阅读记录 - WOS | 2022 使用语言特征空间的抽象文本摘要的神经注意模型

文章目录 前言0、论文摘要一、Introduction1.1目标问题1.2相关的尝试1.3本文贡献 二.相关工作三.本文方法3.1 总结为两阶段学习3.1.1 基础系统 3.2 重构文本摘要 四 实验效果4.1数据集4.2 对比模型4.3实施细节4.4评估指标4.5 实验结果4.6 细粒度分析 五 总结思考 前言 Neural A…

熊猫电竞赏金电竞系统源码 APP+H5双端 附搭建教程 支持运营级搭建

简介: 熊猫电竞赏金电竞系统源码 APP+H5双端 附搭建教程 支持运营级搭建 可搭建!运营级!首次公开! 赏金赛源码,用户通过平台打比赛,赢了获得奖金奖励, 金币赛、赏金赛、vip赛等种赛事 可开王者荣耀、和平精英比赛 支持1v1、单排、双排组、战队排等多种比赛模式 …

【Kafka-3.x-教程】-【六】Kafka 外部系统集成 【Flume、Flink、SpringBoot、Spark】

【Kafka-3.x-教程】专栏&#xff1a; 【Kafka-3.x-教程】-【一】Kafka 概述、Kafka 快速入门 【Kafka-3.x-教程】-【二】Kafka-生产者-Producer 【Kafka-3.x-教程】-【三】Kafka-Broker、Kafka-Kraft 【Kafka-3.x-教程】-【四】Kafka-消费者-Consumer 【Kafka-3.x-教程】-【五…

坚持刷题|翻转二叉树

坚持刷题&#xff0c;老年痴呆追不上我&#xff0c;今天先刷个简单的&#xff1a;翻转二叉树 题目 226.翻转二叉树 考察点 翻转二叉树又称为镜像二叉树&#xff0c;使用Java实现翻转二叉树通常是为了考察对二叉树的基本操作和递归的理解能力 递归的理解&#xff1a; 能够理解…

TongLINKQ(1):TongLINKQ概述

1 TongLINKQ简介 TongLinkQ 是面向分布式应用的消息中间件产品&#xff0c;主要功能是在应用程序之间进行实时、高效和可靠的传递消息&#xff0c;使得消息在不同的网络协议、不同的计算机系统和不同的应用软件之间进行网络传输。 TongLinkQ 应用程序可灵活地运行在多平台的多…

Vulnhub靶机:driftingblues 2

一、介绍 运行环境&#xff1a;Virtualbox 攻击机&#xff1a;kali&#xff08;10.0.2.15&#xff09; 靶机&#xff1a;driftingblues2&#xff08;10.0.2.18&#xff09; 目标&#xff1a;获取靶机root权限和flag 靶机下载地址&#xff1a;https://www.vulnhub.com/entr…

Http协议简述

目录 HTTP-概述 2.1.1 介绍 2.2.2 特点 2.2 HTTP-请求协议 2.3 HTTP-响应协议 2.3.1 格式介绍 2.3.2 响应状态码 HTTP-概述 2.1.1 介绍 HTTP&#xff1a;Hyper Text Transfer Protocol(超文本传输协议)&#xff0c;规定了浏览器与服务器之间数据传输的规则。 http是互联…

React入门 - 06(TodoList 列表数据的新增和删除)

本章内容 目录 一、实践一下 React 的列表渲染二、TodoList 新增功能三、列表循环的 key四、删除 上一节内容我们完成了输入框中可以自由输入内容&#xff0c;这一节我们继续 TodoList功能的完善&#xff1a;列表数据的新增和删除。 在开始之前&#xff0c;我们先介绍一下 Re…

C++力扣题目222--完全二叉树的节点个数

给你一棵 完全二叉树 的根节点 root &#xff0c;求出该树的节点个数。 完全二叉树 的定义如下&#xff1a;在完全二叉树中&#xff0c;除了最底层节点可能没填满外&#xff0c;其余每层节点数都达到最大值&#xff0c;并且最下面一层的节点都集中在该层最左边的若干位置。若最…

目标检测-One Stage-YOLOv7

文章目录 前言一、YOLOv7的不同版本二、YOLOv7的网络结构二、YOLOv7的创新点三、创新点的详细解读ELAN和E-ELANBoF训练技巧计划型重参化卷积辅助训练模块标签分配Lead head guided label assignerCoarse-to-fine lead head guided label assigner 基于级联模型的复合缩放方法 总…

面试官问,如何在十亿级别用户中检查用户名是否存在?

面试官问&#xff0c;如何在十亿级别用户中检查用户名是否存在&#xff1f; 前言 不知道大家有没有留意过&#xff0c;在使用一些app注册的时候&#xff0c;提示你用户名已经被占用了&#xff0c;需要更换一个&#xff0c;这是如何实现的呢&#xff1f;你可能想这不是很简单吗…

java获取已经发送谷歌邮件的打开状态

1.前言 现在网上的方案都是在邮件里面插入一张图片的地址&#xff0c;当收件人打开之后&#xff0c;就会发送请求到指定路径的服务器上&#xff0c;然后在请求的controller里面处理邮件的状态&#xff0c;这个方案确实是行得通的&#xff0c;本文章只是给大家避个坑&#xff0…

从传统训练到预训练和微调的训练策略

目录 前言1 使用基础模型训练手段的传统训练策略1.1 随机初始化为模型提供初始点1.2 目标函数设定是优化性能的关键 2 BERT微调策略: 适应具体任务的精妙调整2.1 利用不同的representation和分类器进行微调2.2 通过fine-tuning适应具体任务 3 T5预训练策略: 统一任务形式以提高…

Vue学习计划-Vue3--核心语法(七)pinia

pinia案例gitee地址 1. pinia 准备一个效果 【搭建 pinia 环境】 安装pinia: npm install pinia/yarn add pinia第二步&#xff1a;操作src/main.ts import { createApp } from vue import App from ./App.vue/* 引入createPinia&#xff0c;用于创建pinia */ import { crea…

阿里云优惠券介绍、种类、领取入口及使用教程

阿里云优惠券是阿里云提供的一种优惠活动&#xff0c;旨在帮助用户节省购买云服务产品的费用。本文将为大家详细介绍阿里云优惠券的相关信息&#xff0c;包括优惠券的介绍、种类、领取入口以及使用教程。 一、阿里云优惠券介绍 阿里云优惠券是阿里云提供给用户的一种优惠凭证&…

vue前端开发自学,异步加载组件,提升用户端的客户体验度

vue前端开发自学,异步加载组件,提升用户端的客户体验度&#xff01;现实项目开发时&#xff0c;组件的数量非常庞大&#xff0c;如果都是一口气加载完&#xff0c;对手机用户来说&#xff0c;体验度会很差。因此&#xff0c;非常有必要使用异步加载。 那就是&#xff0c;用到了…

Neo4j知识图谱(2)创建与删除

Neo4j - CQL简介_w3cschoolhttps://www.w3cschool.cn/neo4j/neo4j_cql_introduction.html一、创建节点 create(n:Person{name:何仙鸟,age:21}) create就是创建&#xff0c;无论是点还是边都是用create来创建 n相当于一个别名&#xff0c;比如创建一个Person&#xff0c;而Pe…

React16源码: React中的schedule调度整体流程

schedule调度的整体流程 React Fiber Scheduler 是 react16 最核心的一部分&#xff0c;这块在 react-reconciler 这个包中这个包的核心是 fiber reconciler&#xff0c;也即是 fiber 结构fiber 的结构帮助我们把react整个树的应用&#xff0c;更新的流程&#xff0c;能够拆成…

JVM基础(10)——老年代调优

作者简介&#xff1a;大家好&#xff0c;我是smart哥&#xff0c;前中兴通讯、美团架构师&#xff0c;现某互联网公司CTO 联系qq&#xff1a;184480602&#xff0c;加我进群&#xff0c;大家一起学习&#xff0c;一起进步&#xff0c;一起对抗互联网寒冬 学习必须往深处挖&…

[ACM算法学习] 诱导排序与 SA-IS算法

学习自诱导排序与SA-IS算法 - riteme.site 为了简化一些操作&#xff0c;定义 # 是字典序最小的字符&#xff0c;其字典序小于字母集里任意字符&#xff0c;并且将其默认作为每个字符串的最后一个字符&#xff0c;即作 S[|S|] SA-IS 算法 SA-IS 算法是基于诱导排序这种思想。…