Spring Boot(二)— 自定义Spring Boot Starter

 在Spring Boot中,自定义Spring Boot Starter是一个常见且强大的功能,它允许开发者为特定的功能或库创建自己的自动配置,从而简化集成过程。

1 前置知识

Spring Boot的事件为应用的启动和关闭提供了详细的上下文信息,使得开发者能够根据不同阶段执行特定的逻辑。

Spring Boot的外部化配置是一种允许开发者将应用程序的配置信息从代码中分离出来,存储在外部文件或系统中的机制。支持包括Java属性文件、YAML文件、环境变量和命令行参数等多种外部配置源。

1.1 事件与监听器

ApplicationStartingEvent

程序刚启动,但没进行任何实际工作,除了监听器和初始化器的注册。

ApplicationEnvironmentPreparedEvent

应用程序的环境(例如配置属性、命令行参数等)已准备好,但上下文还没创建。

ApplicationPreparedEvent

属性之前,bean定义加载之后。(刷新指上下文的初始化过程,包括加载bean定义等)

ContextRefreshedEvent

刷新之后,ApplicationStartedEvent之前。

WebServerInitializedEvent

当基于Servlet的Web服务器(如tomcat)初始化完成后。(ApplicationStartedEvent之前

ApplicationStartedEvent

刷新之后,任何启动任务或命令行指令被调用之前。

ApplicationReadyEvent

任何应用程序和命令行运行器调用之后执行。

ApplicationFailedEvent

启动时发生异常。

表 Spring Boot 定义的事件

上面大多数事件都是在bean之前创建的,因此如果将监听器设置为bean,可能没法正常的监听,可以通过application.addListeners方法来添加监听器。

1.2 外部化配置

属性值可以通过多种方式注入到应用程序中,常见的方式有:1)使用@Value注解将属性值直接注入到Bean中。2)使用@ConfigurationProperties可以将配置属性绑定到结构化对象上。

属性值类型可以是数组、Map、Java对象等复杂的类型。还可以对属性值进行校验,需要添加依赖:

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
@ConfigurationProperties("student")
@Data
@Validated
public class Student {
    @NotBlank(message = "姓名不能为空")
    private String name;
    private Integer age;
    private List<Address> addressList;
    private List<String> role;
    private Map<String,Object> map;
    @DurationUnit(ChronoUnit.HOURS)
    private Duration duration1;
    private Duration duration2;
    private Period period1;
    private Period period2;
    private Date date;
    private String username;

    @Data
    private static class Address {
        private String province;
        private String city;
    }
}
student:
  name: 黄先生
  age: 27
  role:
    - 爸爸
    - 丈夫
    - 员工
  map:
    info: hello
    username: hmf
  addressList:
    - province: 广东省
      city: 深圳市
  duration1: 900
  duration2: 15m
  period1: p3D
  period2: p20M
  date: 2023/03/02 12:00
  username: hmf

2 自动配置

Spring Boot的自动配置是其核心特性之一,大大简化了Spring应用的初始搭建以及开发过程。基于“约定优于配置”的思想,通过默认配置来减少开发者需要手动进行的配置工作。

2.1 Bean 创建的条件约束

在自动配置过程中,可以使用一个或多个约束来控制bean的创建。

@ConditionalOnClass

指定类在运行时才会创建bean,(注意引用类在编译时可能存在,但是运行时不一定会存在。)

@ConditionalOnBean

当指定的bean存在时才会创建bean。注意指定的bean 一般为业务代码中的bean,而非依赖包中的bean。这个会受到bean创建顺序的影响,依赖包中的bean一般后于业务代码中的bean创建。

@ConditionalOnMissingBean

当这个bean不存在时才会被创建。

@ConditionalOnProperty

当对于的属性名存在特定值时bean才会被创建。

表 常用的Bean条件约束

2.2 自定义Starter

创建自定义的Starter,步骤如下:

  1. 创建一个Maven项目,该项目包含了核心功能。
  2. 创建一个自动配置Maven模块,命名规则一般为:项目名-spring-boot-autoconfigure。该模块的作用为:创建用于封装配置项的配置类,可以使用@ConfigurationProperties注解来指定配置项的前缀;创建自动配置类xxxGenerateAutoConfiguration,该类包含了根据条件自动创建Bean的逻辑;配置META- INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports 文件,该文件包含了自定配置类的全限定名,Spring Boot在启动时能自动加载。
  3. 创建一个空Maven模块,命名规则一般为:项目名-spring-boot-starter,该模块的作用为在pom文件中,引入核心模块、自动配置模块等需要的依赖。
  4. 打包部署。

2.2.1 自动配置模块中的pom.xml

在自动配置模块中,引入依赖时,一般会加上<optional>true</optional>,这意味着依赖不会被传递性地引入到使用这个库到其他项目中,有以下的好处:

  1. 避免依赖冲突。如果两个不同的Starter都依赖了不同版本的同一库,而且没有将其声明为可选依赖,那么构建过程中就可能会遇到依赖版本冲突的问题。
  2. 控制依赖范围。有时只想让Starter负责自动配置功能,而不希望它强制引入一些可能不需要的运行时依赖。通过将依赖声明为可选,允许最终用户根据自己的需求选择是否引入这些额外的依赖。
  3. 模块化和解耦。让项目不会因为引入Starter而间接引入不必要的依赖、减少最终构建的应用程序的大小,有助于优化应用程序的启动时间和性能。

2.2.2 自动配置模块中外部化配置的元数据描述

Spring Boot启动时,它会扫描类路径下的META-INF目录来查找spring-autoconfigure-metadata.properties 文件,该文件会被加载并用于生成关于自动配置类的元数据信息(属性的描述、类型、默认值、是否必须等信息)。

这个文件,一般可以通过添加spring-autoconfigure-metadata.properties依赖来自动生成,或者可以创建additional-spring-configuration-metadata.json 文件来自定义(这种方式不建议,还是推荐自动生成的形式)。

@Configuration
@EnableConfigurationProperties({CustomProperties.class,CustomSellerProperties.class})
public class CustomAutoConfiguration {

    private final CustomProperties customProperties;
    private final CustomSellerProperties customSellerProperties;

    public CustomAutoConfiguration(CustomProperties customProperties, CustomSellerProperties customSellerProperties) {
        this.customProperties = customProperties;
        this.customSellerProperties = customSellerProperties;
    }

    @Bean
    @ConditionalOnMissingBean
    public Shop shop() {
        Shop shop = new Shop();
        shop.setManagerName(customProperties.getManagerName());
        shop.setName(customProperties.getName());
        shop.setCreateDate(customProperties.getCreateDate());
        shop.setGoodsList(customProperties.getGoodsList());
        shop.setStateOwned(customProperties.getStateOwned());
        return shop;
    }

    @Bean
    @ConditionalOnMissingBean
    public Seller seller() {
        Seller seller = new Seller();
        seller.setName(customSellerProperties.getName());
        seller.setAge(customSellerProperties.getAge());
        return seller;
    }

    @Bean
    @ConditionalOnBean(name = "myBean")
    public DataSource1 dataSource1() {
        return new DataSource1();
    }

    @Bean
    @ConditionalOnProperty(name = "custom-seller.age", havingValue = "30")
    public DataSource2 dataSource2() {
        return new DataSource2();
    }
}

@Configuration
@ConditionalOnClass(CustomDataSource.class)
public class CustomDataAutoConfiguration {

    public CustomDataAutoConfiguration() {
        System.out.println("CustomDataAutoConfiguration 被创建");
    }

    @Bean
    @ConditionalOnMissingBean
    public CustomDataSource customDataSource() {
        return new CustomDataSource();
    }

}

@ConfigurationProperties(prefix = "custom")
public class CustomProperties {

    private String managerName;

    private String name;

    private Date createDate;

    private List<String> goodsList;

    private Boolean stateOwned;

    public String getManagerName() {
        return managerName;
    }

    public void setManagerName(String managerName) {
        this.managerName = managerName;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Date getCreateDate() {
        return createDate;
    }

    public void setCreateDate(Date createDate) {
        this.createDate = createDate;
    }

    public List<String> getGoodsList() {
        return goodsList;
    }

    public void setGoodsList(List<String> goodsList) {
        this.goodsList = goodsList;
    }

    public Boolean getStateOwned() {
        return stateOwned;
    }

    public void setStateOwned(Boolean stateOwned) {
        this.stateOwned = stateOwned;
    }
}

@ConfigurationProperties(prefix = "custom-seller")
public class CustomSellerProperties {

    /**
     * 商家姓名哦
     */
    private String name = "黄sire";

    private Integer age = 18;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }
}

META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports

com.huangmingfu.autoconfigure.CustomAutoConfiguration
com.huangmingfu.autoconfigure.CustomDataAutoConfiguration

自动配置模块中的pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <artifactId>spring-boot-starter-parent</artifactId>
        <groupId>org.springframework.boot</groupId>
        <version>3.1.2</version>
    </parent>

    <groupId>com.huangmingfu</groupId>
    <artifactId>custom-spring-boot-autoconfigure</artifactId>
    <version>1.0-SNAPSHOT</version>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-autoconfigure</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-configuration-processor</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>com.huangmingfu</groupId>
            <artifactId>custom-core</artifactId>
            <optional>true</optional>
            <version>1.0-SNAPSHOT</version>
        </dependency>
        <dependency>
            <groupId>com.huangmingfu</groupId>
            <artifactId>custom-data</artifactId>
            <version>1.0-SNAPSHOT</version>
            <optional>true</optional>
        </dependency>
    </dependencies>

</project>

图 自动配置模块依赖中自动生成的元数据描述文件 

图 使用这个starter依赖的主服务的yml 配置文件

注意:对于配置属性的默认值,在元数据描述文件中定义的默认值并不会生效,可以在这个配置类中定义默认值(自动生成元数据描述文件时,默认值也取自这)。

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

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

相关文章

OSI七层网络模型 —— 筑梦之路

在信息技术领域&#xff0c;OSI七层模型是一个经典的网络通信框架&#xff0c;它将网络通信分为七个层次&#xff0c;每一层都有其独特的功能和作用。为了帮助记忆这七个层次&#xff0c;有一个巧妙的方法&#xff1a;将每个层次的英文单词首字母组合起来&#xff0c;形成了一句…

腾讯云优惠券详细介绍及领券步骤详解

随着云计算技术的不断发展和普及&#xff0c;越来越多的企业和个人开始选择使用云服务来满足自身的需求。腾讯云作为国内领先的云服务提供商&#xff0c;以其稳定、高效、安全的服务赢得了广大用户的信赖。为了回馈广大用户&#xff0c;腾讯云经常推出各种优惠活动&#xff0c;…

【JS】数组交换位置

公式 arr.splice(oldIndex, delCount, ...arr.splice(newIndex, delCount, arr[oldIndex])) arr - 操作的数组delCount - 删除的数组个数oldIndex - 交换位置的数组下标1newIndex - 交换位置的数组下标2...arr - 提取数组里的元素 splice删除元素时&#xff0c;返回一个数组&a…

利用计算机视觉算法提取裂纹相关特征参数信息

ABCnutter/CrackFeature: &#x1f680;使用计算机视觉相关算法提取裂缝的骨架&#xff08;矢量化&#xff09;、轮廓【支持提前修复断裂裂缝】&#xff0c;以及几何特征参数&#xff08;长度、宽度、面积和主要方向&#xff09;【欢迎Star】。主要流程以及相关算法如下&#x…

异构超图嵌入的图分类 笔记

1 Title Heterogeneous Hypergraph Embedding for Graph Classification&#xff08;Xiangguo Sun , PictureHongzhi Yin , PictureBo Liu , PictureHongxu Chen , PictureJiuxin Cao , PictureYingxia Shao , PictureNguyen Quoc Viet Hung&#xff09;【WSDM 2021】 2 Co…

1038: 顺序表中重复数据的删除

解法&#xff1a; #include<iostream> #include<vector> #include<algorithm> using namespace std; int main() {int n, k;cin >> n;vector<int> arr(n);for (auto& x : arr) cin >> x;cin >> k;int sum 0;for (auto x : arr…

句柄ros::NodeHandle nh(“~“)与nh对launch文件参数配置(param)的影响

ros::NodeHandle nh("~"); 改为&#xff1a; ros::NodeHandle nh; 即可 /*************************分割线 ************************/ 如果原本是&#xff1a; ros::NodeHandle nh;可以改成&#xff1a; ros::NodeHandle nh("~"); 试试

反射与动态代理

一、反射 什么是反射? 反射允许对成员变量&#xff0c;成员方法和构造方法的信息进行编程访问 1.获取class对象的三种方式 Class这个类里面的静态方法forName&#xff08;“全类名”&#xff09;&#xff08;最常用&#xff09; 通过class属性获取 通过对象获取字节码文件对…

浏览器原理---事件循环

浏览器原理 学习浏览器原理对于我们开发是很有必要的 我们可以了解到浏览器内部工作原理对自己的代码进行优化 进程线程 首先了解进程和线程 进程就就是内存在正在进行的应用程序 在内存中独占一个内存空间 并且进程之间是隔离的 可以看到每个应用都有一个进程 占用空间内存…

【300套】基于Springboot+Vue的Java毕业设计项目(附源码+演示视频+LW)

大家好&#xff01;我是程序猿老A&#xff0c;感谢您阅读本文&#xff0c;欢迎一键三连哦。 &#x1f9e1;今天给大家分享300的Java毕业设计&#xff0c;基于Springbootvue框架&#xff0c;这些项目都经过精心挑选&#xff0c;涵盖了不同的实战主题和用例&#xff0c;可做毕业…

【C++进阶】RAII思想&智能指针

智能指针 一&#xff0c;为什么要用智能指针&#xff08;内存泄漏问题&#xff09;内存泄漏 二&#xff0c;智能指针的原理2.1 RAII思想2.2 C智能指针发展历史 三&#xff0c;更靠谱的shared_ptr3.1 引用计数3.2 循环引用3.3 定制删除器 四&#xff0c;总结 上一节我们在讲抛异…

Vulnhub靶机 DC-1渗透详细过程

Vulnhub靶机:DC-1渗透详细过程 目录 Vulnhub靶机:DC-1渗透详细过程一、将靶机导入到虚拟机当中二、攻击方式主机发现端口扫描web渗透利用msf反弹shell数据库信息web管理员密码提权 一、将靶机导入到虚拟机当中 靶机地址&#xff1a; https://www.vulnhub.com/entry/dc-1-1,29…

Open CASCADE学习|BRepOffsetAPI_DraftAngle

BRepOffsetAPI_DraftAngle 是 Open CASCADE Technology (OCCT) 中用于创建带有草图斜面的几何体的类。草图斜面是一种在零件设计中常见的特征&#xff0c;它可以在零件的表面上创建一个倾斜的面&#xff0c;通常用于便于零件的脱模或是增加零件的强度。 本例演示了如何创建一个…

启动nginx时报错:signal process started

解决方案&#xff0c;直接使用该命令启动&#xff0c;指向nginx.conf配置文件&#xff1a; nginx -c /www/wdlinux/nginx/conf/nginx.conf 启动成功&#xff1a;

C语言高质量编程之assert()和const

目录 编程中常见的错误 assert() const 编程中常见的错误 在编程中我们通常会遇到三种错误形式&#xff0c;分别是&#xff1a;编译型错误&#xff0c;链接型错误&#xff0c;运行时错误。 编译型错误&#xff1a; 在编译阶段发生的错误&#xff0c;绝大多数情况是由语法错误…

全栈的自我修养 ———— react实现滑动验证

实现滑动验证 展示依赖实现不借助create-puzzle借助create-puzzle 展示 依赖 npm install rc-slider-captcha npm install create-puzzleapi地址 实现 不借助create-puzzle 需要准备两张图片一个是核验图形&#xff0c;一个是原图------> 这个方法小编试了后感觉比较麻烦…

初探vercel托管项目

文章目录 第一步、注册与登录第二步、本地部署 在个人网站部署的助手vercel&#xff0c;支持 Github部署&#xff0c;只需简单操作&#xff0c;即可发布&#xff0c;方便快捷&#xff01; 第一步、注册与登录 进入vercel【官网】&#xff0c;在右上角 login on&#xff0c;可登…

图解JDK 8 HashMap

HashMap-简介 HashMap 主要用来存放键值对&#xff0c;它基于哈希表的 Map 接口实现&#xff0c;是常用的 Java 集合之一&#xff0c;是非线程安全的。 HashMap 可以存储 null 的 key 和 value&#xff0c;但 null 作为键只能有一个&#xff0c;null 作为值可以有多个 JDK1.8…

Macbook M1 Pro使用brew安装Docker并安装Nacos【超详细图解】

目录 一、安装 Docker 二、修改 Docker 镜像地址 三、拉取镜像-举例 Nacos 1.拉取镜像 2.查看本地镜像 3.删除镜像 四、启动容器 1.启动 Nacos 容器&#xff1a; I.方式一【推荐】 II.方式二【懒人推荐】 2.访问 Nacos Web 控制台 3.进入容器和退出容器 五、配置…

labview中的同步定时结构

单帧定时循环定时比较精确&#xff0c;最常用的功能还是它的定时循环功能&#xff0c;定时循环允许不连接“循环条件”端子&#xff0c;可以连接定时循环“结构名称”端子&#xff0c;通过定时结构停止函数停止循环。 例子在附件中。