Spring Boot中自定义Starter开发全流程与最佳实践

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.propertiesapplication.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.hostspring.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。例如,如果用户自己定义了一个 JedisPool Bean,那么自动配置的 JedisPool Bean 将不会被创建。

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 文件中,配置发布信息,包括 groupIdartifactIdversionnamedescriptionurllicensesdevelopersscm 等信息。

<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-pluginmaven-source-pluginmaven-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 仓库的配置信息,包括 usernamepassword

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.propertiesapplication.yml 文件中配置 spring.redis.hostspring.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 成功的关键。希望今天的讲解对你有所帮助!

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注