在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,步骤如下:
- 创建一个Maven项目,该项目包含了核心功能。
- 创建一个自动配置Maven模块,命名规则一般为:项目名-spring-boot-autoconfigure。该模块的作用为:创建用于封装配置项的配置类,可以使用@ConfigurationProperties注解来指定配置项的前缀;创建自动配置类xxxGenerateAutoConfiguration,该类包含了根据条件自动创建Bean的逻辑;配置META- INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports 文件,该文件包含了自定配置类的全限定名,Spring Boot在启动时能自动加载。
- 创建一个空Maven模块,命名规则一般为:项目名-spring-boot-starter,该模块的作用为在pom文件中,引入核心模块、自动配置模块等需要的依赖。
- 打包部署。
2.2.1 自动配置模块中的pom.xml
在自动配置模块中,引入依赖时,一般会加上<optional>true</optional>,这意味着依赖不会被传递性地引入到使用这个库到其他项目中,有以下的好处:
- 避免依赖冲突。如果两个不同的Starter都依赖了不同版本的同一库,而且没有将其声明为可选依赖,那么构建过程中就可能会遇到依赖版本冲突的问题。
- 控制依赖范围。有时只想让Starter负责自动配置功能,而不希望它强制引入一些可能不需要的运行时依赖。通过将依赖声明为可选,允许最终用户根据自己的需求选择是否引入这些额外的依赖。
- 模块化和解耦。让项目不会因为引入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 配置文件
注意:对于配置属性的默认值,在元数据描述文件中定义的默认值并不会生效,可以在这个配置类中定义默认值(自动生成元数据描述文件时,默认值也取自这)。