0赞
赏
赞赏
更多好文
去年在XX项目,我自作主张写了个user-service-starter,结果上线后一跑就崩。原因?没搞懂自动配置的底层逻辑,直接照搬别人的代码,结果依赖没加全,启动时疯狂报错。
别被“自动配置”三个字唬住——它不是魔法,是条件判断+配置文件的组合拳。今天不讲理论,只说实战:怎么写出一个靠谱的Starter,顺便避开我踩过的坑。
一、自动配置的真相:它根本不是“自动”,是“条件触发”
很多人以为Spring Boot的自动配置是“自动装好所有依赖”,其实它更像一个智能管家:
“如果用户有Redis依赖,就给我加载Redis配置;如果没Redis,就别瞎折腾。”
关键机制:
@EnableAutoConfiguration:启动时扫描spring.factoriesspring.factories:列出所有自动配置类- 条件注解:决定配置类是否生效(比如
@ConditionalOnClass)
我踩过的坑:
// 错!没加条件,项目没Redis依赖时直接崩溃
@Configuration
public class RedisAutoConfiguration {
@Bean
public RedisTemplate redisTemplate() {
return new RedisTemplate<>();
}
}
修复后:
@Configuration
// 只有存在Redis类时才生效
@ConditionalOnClass(RedisTemplate.class)
public class RedisAutoConfiguration {
@Bean
public RedisTemplate redisTemplate() {
return new RedisTemplate<>();
}
}
教训:条件注解是自动配置的命门,没它就是定时炸弹。
二、自定义Starter实战:从0到1,少走弯路
步骤1:创建Maven模块(别用IDE生成模板)
# 直接手动建目录,避免IDE生成的坑
my-starter/
├── src/main/java/com/example/starter
│ └── RedisAutoConfiguration.java
└── src/main/resources/META-INF
└── spring.factories
步骤2:写配置类(核心!)
// RedisAutoConfiguration.java
package com.example.starter;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.core.RedisTemplate;
@Configuration
// 重点!只有存在RedisTemplate类时才加载
@ConditionalOnClass(RedisTemplate.class)
public class RedisAutoConfiguration {
// 如果用户没自己配置RedisTemplate,才用我的
@Bean
@ConditionalOnMissingBean(RedisTemplate.class)
public RedisTemplate redisTemplate() {
return new RedisTemplate<>();
}
}
为什么加@ConditionalOnMissingBean?
- 避免和用户自定义的配置冲突(比如用户写了
@Bean RedisTemplate) - 这个坑我栽过两次:用户自己配置了Redis,Starter又初始化一遍,结果Bean冲突
步骤3:配置spring.factories
# META-INF/spring.factories
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.example.starter.RedisAutoConfiguration
关键点:
- 路径必须是
META-INF/spring.factories(别写成resources/spring.factories) - 用
\换行(别用逗号),否则打包后可能失效
三、测试:别等打包后才发现问题
错误示范:
写完Starter → 打包 → 拉到主项目里跑 → 报错
正确姿势:
// 在Starter模块里写测试类
@SpringBootTest(classes = {RedisAutoConfiguration.class})
class RedisAutoConfigurationTest {
@Test
void testRedisTemplate() {
// 模拟没有Redis依赖的环境
assertDoesNotThrow(() -> {
new RedisAutoConfiguration().redisTemplate();
});
}
}
为什么重要?
- 在Starter模块里测试,能提前发现条件注解问题
- 避免“打包后才发现没Redis依赖就崩溃”
四、避坑指南:我栽过的3个坑
1. spring.factories路径写错
- 问题:IDE里能看到文件,但打包后找不到
- 解决:手动检查Maven的
target/classes/META-INF/spring.factories - 亲测:我有次写了
src/main/resources/META-INF/spring.factories,结果打包后路径是META-INF/spring.factories,没被扫描到
2. 条件注解漏了@ConditionalOnMissingBean
- 问题:用户自己写了
RedisTemplate,Starter又初始化一个,Bean冲突 - 解决:所有
@Bean都要加@ConditionalOnMissingBean - 例子:
@Bean @ConditionalOnMissingBean(RedisTemplate.class) // 必须加! public RedisTemplate redisTemplate() { ... }
3. 没处理依赖范围
- 问题:Starter依赖了
spring-boot-starter-data-redis,但用户项目没引入,启动报错 - 解决:在Starter的pom.xml里把依赖设为
provided
为什么:用户项目需要自己引入Redis依赖,Starter只负责配置<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> <scope>provided</scope> <!-- 关键! --> </dependency>
五、真实项目中的Starter:怎么用?
用户项目引入Starter:
<!-- 主项目pom.xml -->
<dependency>
<groupId>com.example</groupId>
<artifactId>my-starter</artifactId>
<version>1.0.0</version>
</dependency>
用户代码:
// 无需任何配置,直接用
@Autowired
private RedisTemplate redisTemplate;
效果:
- 用户项目必须引入
spring-boot-starter-data-redis(否则自动配置不生效) - 用户自己没写
RedisTemplate,Starter自动注入 - 用户自己写了
RedisTemplate,Starter跳过
最后一句大实话
写Starter不是为了炫技,而是让团队新人30分钟内集成成功。我见过太多团队:
- 为了“用Starter”硬造一个,结果代码比原生多50%
- 依赖没处理好,线上事故一出,全员加班
记住:
自动配置的精髓是“条件触发,不冲突”。
别写@ConditionalOnClass,直接让项目崩掉;
别写@ConditionalOnMissingBean,让团队重复造轮子。
