Spring Boot 自定义 Starter 开发全流程与最佳实践
大家好!今天我们一起来探讨 Spring Boot 中自定义 Starter 的开发。Starter 旨在简化 Spring Boot 应用的依赖管理和自动配置,让我们能够以更少的配置,更快地搭建和运行应用。接下来,我将从需求分析、项目结构、自动配置、测试、发布等多个方面,深入讲解如何开发一个高质量的自定义 Starter。
1. 需求分析:定义 Starter 的目标
在开始编写代码之前,最重要的一步是明确我们的 Starter 要解决什么问题。例如,我们想要开发一个用于集成某种第三方服务的 Starter,或者简化我们自己的通用业务组件的使用。
假设我们现在需要开发一个 Starter,用于简化 Redis 的使用,并且提供一个默认的 Redis 配置,方便其他应用快速接入 Redis。这个 Starter 的主要目标如下:
- 提供一个 Redis 连接池的自动配置。
- 允许用户通过配置文件覆盖默认配置。
- 提供一个简单的 Redis 客户端工具类,方便用户操作 Redis。
2. 项目结构:构建清晰的模块化结构
一个典型的 Spring Boot Starter 项目包含以下几个模块:
[starter-name]-spring-boot-autoconfigure: 包含自动配置逻辑和配置类。[starter-name]-spring-boot-starter: 依赖于autoconfigure模块,提供一个方便的依赖入口。[starter-name]-spring-boot-starter-test: (可选)包含用于测试autoconfigure模块的测试代码.
根据我们的需求,项目结构如下:
redis-spring-boot-starter
├── redis-spring-boot-autoconfigure
│ ├── src/main/java
│ │ └── com/example/redis
│ │ ├── RedisAutoConfiguration.java // 自动配置类
│ │ ├── RedisProperties.java // 配置属性类
│ │ └── RedisClient.java // Redis 客户端工具类
│ │ └── resources
│ │ └── META-INF
│ │ └── spring.factories // Spring Factories 文件
│ └── pom.xml
├── redis-spring-boot-starter
│ └── pom.xml
└── redis-spring-boot-starter-test
└── pom.xml
接下来,我们逐个模块进行详细讲解。
3. redis-spring-boot-autoconfigure 模块:核心自动配置逻辑
这是 Starter 的核心模块,负责自动配置所需的 Bean 和组件。
3.1 定义配置属性类 (RedisProperties.java)
首先,我们需要定义一个配置属性类,用于接收用户在 application.properties 或 application.yml 中配置的属性。
package com.example.redis;
import org.springframework.boot.context.properties.ConfigurationProperties;
@ConfigurationProperties("spring.redis")
public class RedisProperties {
private String host = "localhost";
private int port = 6379;
private String password;
private int database = 0;
public String getHost() {
return host;
}
public void setHost(String host) {
this.host = host;
}
public int getPort() {
return port;
}
public void setPort(int port) {
this.port = port;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public int getDatabase() {
return database;
}
public void setDatabase(int database) {
this.database = database;
}
}
@ConfigurationProperties("spring.redis") 注解指定了配置属性的前缀,用户需要在配置文件中使用 spring.redis.host、spring.redis.port 等属性来配置 Redis。
3.2 定义自动配置类 (RedisAutoConfiguration.java)
接下来,我们需要创建一个自动配置类,用于根据配置属性创建 Redis 连接池和 Redis 客户端。
package com.example.redis;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;
@Configuration
@ConditionalOnClass(JedisPool.class) // 只有在 classpath 中存在 JedisPool 类时才生效
@EnableConfigurationProperties(RedisProperties.class) // 启用 RedisProperties 配置类
public class RedisAutoConfiguration {
private final RedisProperties redisProperties;
public RedisAutoConfiguration(RedisProperties redisProperties) {
this.redisProperties = redisProperties;
}
@Bean
@ConditionalOnMissingBean // 只有在容器中不存在 JedisPool 时才创建
public JedisPool jedisPool() {
JedisPoolConfig poolConfig = new JedisPoolConfig();
// 可以根据需要设置 poolConfig 的参数,例如最大连接数、最大空闲连接数等
return new JedisPool(poolConfig, redisProperties.getHost(), redisProperties.getPort(),
redisProperties.getPassword() == null ? null : redisProperties.getPassword(), redisProperties.getDatabase());
}
@Bean
@ConditionalOnMissingBean
public RedisClient redisClient(JedisPool jedisPool) {
return new RedisClient(jedisPool);
}
}
@Configuration: 表明这是一个配置类。@ConditionalOnClass(JedisPool.class): 这是一个条件注解,只有在 classpath 中存在JedisPool类时,这个配置类才会生效。这确保了只有在引入了 Redis 依赖时,才会进行 Redis 的自动配置。@EnableConfigurationProperties(RedisProperties.class): 启用RedisProperties配置类,并将配置属性注入到RedisProperties对象中。@ConditionalOnMissingBean: 这是一个条件注解,只有在 Spring 容器中不存在指定类型的 Bean 时,才会创建该 Bean。例如,如果用户自己定义了一个JedisPoolBean,那么自动配置的JedisPoolBean 将不会被创建。
3.3 定义 Redis 客户端工具类 (RedisClient.java)
package com.example.redis;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
public class RedisClient {
private final JedisPool jedisPool;
public RedisClient(JedisPool jedisPool) {
this.jedisPool = jedisPool;
}
public String get(String key) {
try (Jedis jedis = jedisPool.getResource()) {
return jedis.get(key);
}
}
public void set(String key, String value) {
try (Jedis jedis = jedisPool.getResource()) {
jedis.set(key, value);
}
}
// 其他 Redis 操作方法...
}
这个类封装了常用的 Redis 操作,用户可以通过 RedisClient 对象来操作 Redis。
3.4 配置 Spring Factories 文件 (src/main/resources/META-INF/spring.factories)
Spring Boot 使用 Spring Factories 机制来自动发现配置类。我们需要在 spring.factories 文件中指定自动配置类。
org.springframework.boot.autoconfigure.EnableAutoConfiguration=
com.example.redis.RedisAutoConfiguration
这个文件告诉 Spring Boot,com.example.redis.RedisAutoConfiguration 是一个自动配置类,需要在应用启动时加载。
3.5 redis-spring-boot-autoconfigure 模块的 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>
<groupId>com.example</groupId>
<artifactId>redis-spring-boot-starter</artifactId>
<version>1.0.0-SNAPSHOT</version>
</parent>
<artifactId>redis-spring-boot-autoconfigure</artifactId>
<packaging>jar</packaging>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-autoconfigure</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
</dependency>
</dependencies>
</project>
spring-boot-autoconfigure: 提供了自动配置的基础设施。spring-boot-configuration-processor: 在编译时生成配置属性的元数据,方便 IDE 提供自动补全和文档提示。optional=true表示这个依赖不是必需的,即使没有这个依赖,应用也能正常运行。jedis: Redis 客户端。
4. redis-spring-boot-starter 模块:提供便捷的依赖
这个模块的作用是提供一个方便的依赖入口,简化用户的使用。
4.1 redis-spring-boot-starter 模块的 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>
<groupId>com.example</groupId>
<artifactId>redis-spring-boot-starter</artifactId>
<version>1.0.0-SNAPSHOT</version>
</parent>
<artifactId>redis-spring-boot-starter</artifactId>
<packaging>jar</packaging>
<dependencies>
<dependency>
<groupId>com.example</groupId>
<artifactId>redis-spring-boot-autoconfigure</artifactId>
<version>${project.version}</version>
</dependency>
</dependencies>
</project>
这个模块只有一个依赖,就是 redis-spring-boot-autoconfigure 模块。用户只需要引入 redis-spring-boot-starter 依赖,就可以自动配置 Redis。
5. redis-spring-boot-starter-test 模块(可选):测试自动配置
这个模块用于测试自动配置是否生效。
5.1 redis-spring-boot-starter-test 模块的 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>
<groupId>com.example</groupId>
<artifactId>redis-spring-boot-starter</artifactId>
<version>1.0.0-SNAPSHOT</version>
</parent>
<artifactId>redis-spring-boot-starter-test</artifactId>
<packaging>jar</packaging>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.example</groupId>
<artifactId>redis-spring-boot-autoconfigure</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
</project>
5.2 编写测试用例 (RedisAutoConfigurationTest.java)
package com.example.redis;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.AutoConfigurations;
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import redis.clients.jedis.JedisPool;
import static org.assertj.core.api.Assertions.assertThat;
public class RedisAutoConfigurationTest {
private final ApplicationContextRunner contextRunner = new ApplicationContextRunner()
.withConfiguration(AutoConfigurations.of(RedisAutoConfiguration.class));
@Test
public void testRedisAutoConfiguration() {
this.contextRunner
.withPropertyValues("spring.redis.host=localhost", "spring.redis.port=6379")
.run((context) -> {
assertThat(context).hasBean("jedisPool");
assertThat(context).hasBean("redisClient");
JedisPool jedisPool = context.getBean(JedisPool.class);
RedisClient redisClient = context.getBean(RedisClient.class);
assertThat(jedisPool.getResource().ping()).isEqualTo("PONG");
});
}
@Test
public void testRedisAutoConfigurationWithCustomJedisPool() {
this.contextRunner
.withUserConfiguration(CustomJedisPoolConfiguration.class)
.run((context) -> {
assertThat(context).hasBean("customJedisPool");
assertThat(context).doesNotHaveBean("jedisPool");
assertThat(context).hasBean("redisClient");
RedisClient redisClient = context.getBean(RedisClient.class);
JedisPool customJedisPool = context.getBean("customJedisPool", JedisPool.class);
assertThat(customJedisPool.getResource().ping()).isEqualTo("PONG");
});
}
@Configuration
static class CustomJedisPoolConfiguration {
@Bean(name = "customJedisPool")
public JedisPool customJedisPool() {
return new JedisPool("localhost", 6380);
}
}
}
ApplicationContextRunner: 用于创建一个 Spring 上下文,并运行自动配置。withPropertyValues: 用于设置配置属性。withUserConfiguration: 用于添加自定义配置类。assertThat: 用于断言 Bean 是否存在以及 Bean 的行为是否符合预期。
6. 发布 Starter:让更多人受益
开发完成后,我们需要将 Starter 发布到 Maven 中央仓库或其他 Maven 仓库,以便其他开发者可以使用。
6.1 配置 Maven 发布信息
在项目的 pom.xml 文件中,配置发布信息,包括 groupId、artifactId、version、name、description、url、licenses、developers 和 scm 等信息。
<groupId>com.example</groupId>
<artifactId>redis-spring-boot-starter</artifactId>
<version>1.0.0-SNAPSHOT</version>
<name>Redis Spring Boot Starter</name>
<description>A Spring Boot Starter for Redis</description>
<url>https://github.com/your-username/redis-spring-boot-starter</url>
<licenses>
<license>
<name>Apache License, Version 2.0</name>
<url>http://www.apache.org/licenses/LICENSE-2.0.txt</url>
</license>
</licenses>
<developers>
<developer>
<name>Your Name</name>
<email>[email protected]</email>
<organization>Your Organization</organization>
<organizationUrl>https://www.example.com</organizationUrl>
</developer>
</developers>
<scm>
<connection>scm:git:git://github.com/your-username/redis-spring-boot-starter.git</connection>
<developerConnection>scm:git:ssh://github.com/your-username/redis-spring-boot-starter.git</developerConnection>
<url>https://github.com/your-username/redis-spring-boot-starter</url>
</scm>
6.2 配置 Maven 发布插件
在 pom.xml 文件中配置 Maven 发布插件,例如 maven-deploy-plugin、maven-source-plugin 和 maven-javadoc-plugin。
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-deploy-plugin</artifactId>
<version>3.0.0</version>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-source-plugin</artifactId>
<version>3.2.1</version>
<executions>
<execution>
<id>attach-sources</id>
<goals>
<goal>jar</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-javadoc-plugin</artifactId>
<version>3.3.0</version>
<executions>
<execution>
<id>attach-javadocs</id>
<goals>
<goal>jar</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
6.3 使用 Maven 发布命令
使用 Maven 发布命令将 Starter 发布到 Maven 仓库。
mvn clean deploy
在发布之前,你需要配置 Maven 的 settings.xml 文件,添加 Maven 仓库的配置信息,包括 username 和 password。
7. 使用 Starter:体验便捷的自动配置
在其他 Spring Boot 应用中使用我们开发的 Starter,只需要添加 redis-spring-boot-starter 依赖即可。
<dependency>
<groupId>com.example</groupId>
<artifactId>redis-spring-boot-starter</artifactId>
<version>1.0.0-SNAPSHOT</version>
</dependency>
然后,就可以在应用中使用 RedisClient 对象来操作 Redis。
import com.example.redis.RedisClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.stereotype.Component;
@Component
public class MyCommandLineRunner implements CommandLineRunner {
@Autowired
private RedisClient redisClient;
@Override
public void run(String... args) throws Exception {
redisClient.set("mykey", "myvalue");
String value = redisClient.get("mykey");
System.out.println("Value from Redis: " + value);
}
}
如果需要自定义 Redis 配置,可以在 application.properties 或 application.yml 文件中配置 spring.redis.host、spring.redis.port 等属性。
8. 最佳实践:提升 Starter 的质量
- 清晰的命名规范: 使用统一的命名规范,例如
[starter-name]-spring-boot-starter和[starter-name]-spring-boot-autoconfigure。 - 合理的条件注解: 使用
@ConditionalOnClass、@ConditionalOnMissingBean等条件注解,确保自动配置的灵活性。 - 详细的文档: 提供详细的文档,包括 Starter 的使用方法、配置属性说明和示例代码。
- 单元测试: 编写单元测试,确保自动配置的正确性。
- 版本控制: 使用版本控制工具(例如 Git)管理代码,并使用语义化版本号。
- 持续集成: 使用持续集成工具(例如 Jenkins)自动构建、测试和发布 Starter。
9. 常见问题及解决方案
| 问题 | 解决方案 |
|---|---|
| 自动配置没有生效 | 检查 spring.factories 文件是否正确配置,以及是否满足条件注解的要求。 |
| 配置属性没有生效 | 检查配置属性的前缀是否正确,以及配置属性是否被正确注入到配置类中。 |
| 依赖冲突 | 使用 Maven 的依赖管理功能,排除冲突的依赖。 |
| 发布到 Maven 仓库失败 | 检查 Maven 的 settings.xml 文件是否正确配置,以及 Maven 仓库的权限是否正确。 |
| IDE 没有自动补全配置属性 | 确保已经添加了 spring-boot-configuration-processor 依赖,并重新构建项目。 |
核心模块,依赖,测试,发布,使用以及注意事项
通过以上步骤,我们就可以开发出一个高质量的 Spring Boot 自定义 Starter。记住,清晰的项目结构、合理的自动配置、详细的文档和单元测试是 Starter 成功的关键。希望今天的讲解对你有所帮助!