Spring Boot Starter 自定义开发:封装中间件配置
一、为什么需要自定义 Starter?
在 Spring Boot 项目中,我们经常需要集成各种中间件,如 Redis、RocketMQ、第三方 AI 服务等。如果每次集成都要重复编写配置类、创建 Bean、处理异常,不仅效率低,还容易出错。
自定义 Starter 可以:
- 统一配置管理:将中间件的配置封装在一个模块中
- 开箱即用:引入依赖即可使用,无需手动配置
- 代码复用:多个项目共享同一套配置和工具类
- 降低耦合:业务代码与中间件实现解耦
在 AQChat 项目中,我们将 Redis、RocketMQ、Gitee AI 等中间件封装成了独立的 Starter 模块,放在 aq-chat-framework 下统一管理。
二、Starter 的核心机制:AutoConfiguration.imports
Spring Boot 3.x 引入了新的自动配置机制,使用 AutoConfiguration.imports 文件替代了传统的 spring.factories。
2.1 文件位置和格式
在 Starter 模块的 src/main/resources/META-INF/spring/ 目录下创建 org.springframework.boot.autoconfigure.AutoConfiguration.imports 文件:
com.howcode.aqchat.framework.redis.starter.config.RedisConfig
com.howcode.aqchat.framework.redis.starter.RedisCacheHelper
每行一个配置类的全限定名,Spring Boot 启动时会自动加载这些配置类。
2.2 工作原理
- Spring Boot 启动时扫描所有 jar 包中的 META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports 文件
- 读取文件中列出的配置类
- 根据条件装配注解决定是否创建 Bean
- 自动注入到 Spring 容器中
三、实战案例一:Redis Starter 开发
3.1 项目结构
aq-chat-framework-redis-starter/
├── src/main/java/
│ └── com/howcode/aqchat/framework/redis/starter/
│ ├── config/
│ │ └── RedisConfig.java # Redis 配置类
│ └── RedisCacheHelper.java # Redis 工具类
└── src/main/resources/
└── META-INF/spring/
└── org.springframework.boot.autoconfigure.AutoConfiguration.imports
3.2 配置类实现
java
@Configuration
public class RedisConfig {
@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory lettuceConnectionFactory) {
RedisTemplate<String,Object> redisTemplate = new RedisTemplate<>();
redisTemplate.setConnectionFactory(lettuceConnectionFactory);
// 统一序列化配置
redisTemplate.setKeySerializer(RedisSerializer.string());
redisTemplate.setValueSerializer(RedisSerializer.json());
redisTemplate.setHashKeySerializer(RedisSerializer.string());
redisTemplate.setHashValueSerializer(RedisSerializer.json());
redisTemplate.afterPropertiesSet();
return redisTemplate;
}
}
关键点:
- 使用
@Configuration标记为配置类 - 通过
@Bean创建RedisTemplate,统一序列化方式 - 依赖 Spring Boot 的
spring-boot-starter-data-redis提供的RedisConnectionFactory
3.3 工具类封装
为了简化 Redis 操作,我们封装了 RedisCacheHelper:
java
@Component
public class RedisCacheHelper {
@Resource
public RedisTemplate<String,Object> redisTemplate;
// 缓存对象
public <T> void setCacheObject(final String key, final T value) {
redisTemplate.opsForValue().set(key, value);
}
// 带过期时间的缓存
public <T> void setCacheObject(final String key, final T value,
final Long timeout, final TimeUnit timeUnit) {
redisTemplate.opsForValue().set(key, value, timeout, timeUnit);
}
// 获取缓存
public <T> T getCacheObject(final String key, Class<T> tClass) {
Object object = redisTemplate.opsForValue().get(key);
return object != null ? (T) object : null;
}
// Set 操作(用于消息去重)
public <T> BoundSetOperations<String, T> setCacheSet(final String key, final Set<T> dataSet) {
BoundSetOperations<String, T> setOperation =
(BoundSetOperations<String, T>) redisTemplate.boundSetOps(key);
dataSet.forEach(setOperation::add);
return setOperation;
}
// Hash 操作(用于房间信息存储)
public <T> void setCacheMapValue(final String key, final String hKey, final T value) {
redisTemplate.opsForHash().put(key, hKey, value);
}
}
设计思路:
- 提供常用操作的封装方法
- 支持泛型,类型安全
- 统一异常处理和空值处理
3.4 使用方式
在业务模块中引入依赖:
<dependency>
<groupId>com.howcode</groupId>
<artifactId>aq-chat-framework-redis-starter</artifactId>
<version>1.0.0</version>
</dependency>
配置 application.yml:
yaml
spring:
data:
redis:
host: localhost
port: 6379
password: your-password
直接注入使用:
java
@Service
public class UserService {
@Resource
private RedisCacheHelper redisCacheHelper;
public void cacheUser(User user) {
redisCacheHelper.setCacheObject("user:" + user.getId(), user, 30L, TimeUnit.MINUTES);
}
}
四、实战案例二:RocketMQ Starter 开发
4.1 配置属性绑定
使用 @ConfigurationProperties 绑定配置:
java
@Data
@Configuration
@ConfigurationProperties(prefix = "aq-chat.rocketmq")
public class RocketMQConfig {
private Producer producer;
private Consumer consumer;
@Data
public static class Producer {
private String nameSever; // NameServer 地址
private String groupName; // 生产者组名
private int retryTimes; // 重试次数
private int sendTimeOut; // 发送超时时间
}
@Data
public static class Consumer {
private String nameSever; // NameServer 地址
}
}
配置示例:
yaml
aq-chat:
rocketmq:
producer:
name-sever: localhost:9876
group-name: aq-chat-producer-group
retry-times: 3
send-time-out: 3000
4.2 生产者自动配置
java
@Configuration
public class RocketMQProducerConfiguration {
@Value("${spring.application.name}")
private String applicationName;
@Resource
private RocketMQConfig rocketMQConfig;
@Bean
public MQProducer mqProducer() {
// 创建异步发送线程池
ThreadPoolExecutor asyncThreadPoolExecutor = new ThreadPoolExecutor(
100, 150, 3, TimeUnit.MINUTES,
new ArrayBlockingQueue<>(1000),
r -> {
Thread thread = new Thread(r);
thread.setName(applicationName + ":rocketmq-produce-" +
ThreadLocalRandom.current().nextInt(1000));
return thread;
}
);
DefaultMQProducer defaultMQProducer = new DefaultMQProducer();
try {
defaultMQProducer.setNamesrvAddr(rocketMQConfig.getProducer().getNameSever());
defaultMQProducer.setProducerGroup(rocketMQConfig.getProducer().getGroupName());
defaultMQProducer.setRetryTimesWhenSendFailed(rocketMQConfig.getProducer().getRetryTimes());
defaultMQProducer.setRetryTimesWhenSendAsyncFailed(rocketMQConfig.getProducer().getRetryTimes());
defaultMQProducer.setRetryAnotherBrokerWhenNotStoreOK(true);
// 设置异步发送线程池
defaultMQProducer.setAsyncSenderExecutor(asyncThreadPoolExecutor);
defaultMQProducer.start();
System.out.println("RocketMQ 生产者启动成功====> NameSever is " +
rocketMQConfig.getProducer().getNameSever());
} catch (MQClientException e) {
throw new RuntimeException(e);
}
return defaultMQProducer;
}
}
关键点:
- 自动创建并启动 RocketMQ 生产者
- 配置线程池用于异步发送
- 统一异常处理,启动失败抛出运行时异常
五、实战案例三:Gitee AI Starter 开发
5.1 条件装配的使用
使用 @ConditionalOnBean 确保配置存在时才创建 Bean:
java
@Component
@ConditionalOnBean(GiteeAIConfiguration.class)
public class DefaultGiteeAIClient implements GiteeAIClient {
@Resource
private ModelFactory modelFactory;
@Override
public String chat(String message) {
return modelFactory.getChatModel().chat(message);
}
@Override
public void streamChat(String message, List<Message> messages,
MessageHandler<String> handler) {
modelFactory.getChatModel().streamChat(message, messages, handler);
}
}
条件装配注解说明:
@ConditionalOnBean:当指定 Bean 存在时生效@ConditionalOnClass:当指定类在 classpath 中存在时生效@ConditionalOnProperty:当配置属性满足条件时生效@ConditionalOnMissingBean:当指定 Bean 不存在时生效
5.2 配置类的 Builder 模式
java
public class GiteeAIConfiguration {
private final String bearer;
private final String chatModel;
private final String chatModelUrl;
// ... 其他字段
private GiteeAIConfiguration(Builder builder) {
this.bearer = builder.bearer;
this.chatModel = builder.chatModel;
// ... 赋值
}
public static class Builder {
private String bearer;
private String chatModel;
private String chatModelCode;
// ... 其他字段
public Builder setBearer(String bearer) {
this.bearer = bearer;
return this;
}
public Builder setChatModel(String chatModel) {
this.chatModel = chatModel;
return this;
}
public GiteeAIConfiguration build() {
// 构建时进行 URL 拼接和验证
if (this.chatModelCode != null && !this.chatModelCode.isEmpty()) {
String chatModelUrl = AIModel.AI_MODEL_MAP.get(this.chatModel)
.replace(AIModel.CHAT_MODEL_CODE, this.chatModelCode);
this.chatModelUrl = chatModelUrl;
}
return new GiteeAIConfiguration(this);
}
}
}
Builder 模式的优势:
- 支持链式调用,代码更清晰
- 在 build() 方法中进行配置验证和转换
- 保证配置对象的不可变性
六、配置属性的绑定和验证
6.1 使用 @ConfigurationProperties
java
@Data
@Configuration
@ConfigurationProperties(prefix = "aq-chat.rocketmq")
@Validated // 启用验证
public class RocketMQConfig {
@NotNull(message = "生产者配置不能为空")
private Producer producer;
@Data
public static class Producer {
@NotBlank(message = "NameServer 地址不能为空")
private String nameSever;
@NotBlank(message = "生产者组名不能为空")
private String groupName;
@Min(value = 1, message = "重试次数至少为 1")
@Max(value = 10, message = "重试次数最多为 10")
private int retryTimes;
}
}
6.2 配置验证
Spring Boot 会在启动时自动验证配置:
- 如果配置不合法,应用启动失败
- 错误信息会明确指出哪个配置项有问题
6.3 配置提示(IDE 支持)
在 Starter 中添加 spring-configuration-metadata.json 可以提供配置提示:
json
{
"properties": [
{
"name": "aq-chat.rocketmq.producer.name-sever",
"type": "java.lang.String",
"description": "RocketMQ NameServer 地址",
"defaultValue": "localhost:9876"
},
{
"name": "aq-chat.rocketmq.producer.group-name",
"type": "java.lang.String",
"description": "生产者组名"
}
]
}
七、Starter 开发最佳实践
7.1 模块划分
aq-chat-framework/
├── aq-chat-framework-redis-starter/ # Redis Starter
├── aq-chat-framework-mq-starter/ # RocketMQ Starter
└── aq-chat-framework-gitee-ai-starter/ # Gitee AI Starter
每个 Starter 都是独立的模块,可以单独引入。
7.2 依赖管理
在父 POM 中统一管理版本:
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.2.5</version>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
7.3 条件装配策略
- 必须的配置 :使用
@ConditionalOnClass确保依赖存在 - 可选的配置 :使用
@ConditionalOnProperty根据配置决定是否启用 - 避免冲突 :使用
@ConditionalOnMissingBean允许用户自定义 Bean
7.4 异常处理
- 启动时的异常应该抛出 RuntimeException,让应用快速失败
- 运行时异常应该记录日志,避免影响主流程
7.5 日志输出
在 Bean 创建成功后输出日志,方便排查问题:
java
System.out.println("RocketMQ 生产者启动成功====> NameSever is " +
rocketMQConfig.getProducer().getNameSever());
八、总结
通过自定义 Starter,我们实现了:
- 配置统一管理:所有中间件配置集中在 framework 模块
- 开箱即用:引入依赖即可使用,无需手动配置
- 代码复用:多个项目共享同一套配置和工具类
- 易于维护:配置变更只需修改 Starter,所有项目自动生效
关键要点:
- Spring Boot 3.x 使用
AutoConfiguration.imports替代spring.factories - 使用
@ConfigurationProperties进行配置绑定和验证 - 合理使用条件装配注解,提高灵活性
- 封装工具类,简化使用方式
- 统一异常处理和日志输出
适用场景:
- 多项目共享的中间件配置
- 需要统一封装的工具类
- 复杂的第三方服务集成
- 需要条件装配的功能模块
通过这种方式,我们可以将 AQChat 项目中的中间件配置标准化、模块化,提高开发效率和代码质量。