面试题:写过 Spring Boot Starter 吗?从作用到自定义实现
面试题:写过 Spring Boot Starter 吗?从作用到自定义实现
面试指北 · 去哪儿
题意
面试官问“写过 Spring Boot Starter 吗”,通常不是只想听一句“写过”或“没写过”,而是在考察你对 Spring Boot 自动配置体系的理解:Starter 到底是什么、和自动配置类是什么关系、条件装配怎么做、配置属性如何绑定、自动配置类如何被 Spring Boot 发现、用户如何覆盖默认行为,以及不同 Spring Boot 版本下注册方式有什么区别。
回答时可以先给结论,再按“是什么—怎么写—怎么验证—有哪些坑”展开,避免只背步骤。
结论
是的,Spring Boot Starter 是很常见的实践,特别适合封装通用功能、第三方库适配或内部基础组件。它的核心不是“写一个 jar 包”这么简单,而是把依赖聚合和自动配置结合起来:用户引入 Starter 后,Spring Boot 能根据类路径、配置项等条件自动注册需要的 Bean,从而减少手工配置。
一个自定义 Starter 通常包含几件事:
- 依赖管理:把所需依赖聚合进来;
- 自动配置类:用
@Configuration或@AutoConfiguration注册 Bean; - 条件装配:用
@ConditionalOnClass、@ConditionalOnMissingBean、@ConditionalOnProperty等控制是否生效; - 配置属性:用
@ConfigurationProperties绑定application.yml/properties; - 注册文件:Spring Boot 2.x 常用
META-INF/spring.factories,Spring Boot 3.x 使用META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports。
面试中建议先讲清楚:Starter 负责“把依赖带进来”,自动配置负责“把 Bean 配出来”。官方也推荐把两者拆成两个模块:xxx-spring-boot-autoconfigure 放自动配置代码,xxx-spring-boot-starter 只做依赖聚合。简单场景可以合在一个模块里,但回答时最好知道这个工程化区别。
要点展开
1. Spring Boot Starter 的作用
Spring Boot Starter 是一组预定义的依赖和自动配置逻辑,目的是快速集成特定功能模块。
- 官方 Starter:如
spring-boot-starter-web、spring-boot-starter-data-jpa等; - 自定义 Starter:通常用于封装内部工具、第三方库、业务逻辑模块等。
它的价值主要体现在两点:
- 依赖收敛:用户不需要自己逐个找依赖、对版本;
- 自动配置:用户引入依赖后,只要少量配置甚至零配置就能用。
但要区分一个常见误区:Starter 本身更像“依赖描述符”,真正让 Bean 自动注册的是自动配置类。面试时说清楚这层关系,会比单纯背“加 spring.factories”更扎实。
2. 自定义 Starter 的结构
一个典型的 Starter 项目包含以下内容:
- 依赖管理:提供所需的依赖,如第三方库或其他 Starter;
- 自动配置:通过
@Configuration或@AutoConfiguration注解类,注册需要的 Bean; - 条件装配:通过
@ConditionalOn...注解,控制配置的加载条件; - 默认配置:定义配置属性的默认值,用户可通过
application.properties/yml覆盖; - 注册文件:把自动配置类注册给 Spring Boot;
- 可选:配置元数据:通过
spring-boot-configuration-processor生成配置提示,提升使用体验。
命名上也有一点约定:官方 Starter 通常叫 spring-boot-starter-xxx,第三方或自定义 Starter 更常见的是 xxx-spring-boot-starter。原文示例中的 my-spring-boot-starter 就是类似思路。
3. 如何编写一个自定义 Starter
3.1 创建 Maven/Gradle 项目
可以使用 Spring Initializr 或手动创建一个 Maven 项目,通常命名为 my-starter-name-starter。
Maven 示例:
<groupId>com.example</groupId>
<artifactId>my-spring-boot-starter</artifactId>
<version>1.0.0</version>
<packaging>jar</packaging>
如果按工程化方式拆分,可以建两个模块:
my-spring-boot-autoconfigure // 放自动配置类、Properties、注册文件
my-spring-boot-starter // 只依赖 autoconfigure 和第三方库
这样用户引入 Starter 时,依赖关系更清晰,也避免把自动配置代码和依赖聚合混在一起。
3.2 添加必要的依赖
至少需要 Spring Boot 自动配置依赖和测试依赖。版本通常交给 spring-boot-dependencies 或父 POM 管理,不建议在 Starter 里随便写死版本。
- Spring Boot 自动配置依赖:
xml
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-autoconfigure</artifactId>
</dependency>
- 测试依赖:
xml
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
如果 Starter 封装的是某个第三方库,而用户不一定需要这个库,可以把第三方依赖声明为 <optional>true</optional>,再配合 @ConditionalOnClass 做条件装配。这样不会强行把依赖传递给所有使用者。
3.3 编写自动配置类
创建一个自动配置类,用于定义 Starter 的核心逻辑和需要注入的 Bean。
示例:MyStarterAutoConfiguration.java
@Configuration
@ConditionalOnClass(MyService.class) // 当类路径下存在 MyService 时才生效
@EnableConfigurationProperties(MyStarterProperties.class) // 绑定配置文件
public class MyStarterAutoConfiguration {
private final MyStarterProperties properties;
public MyStarterAutoConfiguration(MyStarterProperties properties) {
this.properties = properties;
}
@Bean
@ConditionalOnMissingBean // 如果用户没有自定义 Bean,则提供默认 Bean
public MyService myService() {
return new MyService(properties.getName(), properties.getAge());
}
}
为了让示例完整,MyService 可以是一个很简单的服务类:
public class MyService {
private final String name;
private final int age;
public MyService(String name, int age) {
this.name = name;
this.age = age;
}
public String getInfo() {
return "My name is " + name + ", and I am " + age + " years old.";
}
}
这里有两个点面试中常被追问:
@ConditionalOnMissingBean表示用户如果自己声明了MyService,Starter 就不再注册默认 Bean,给用户留出扩展空间;@ConditionalOnClass在真实项目里更适合判断“某个第三方库是否存在”,例如@ConditionalOnClass(RedisClient.class)。示例里用MyService.class主要是演示语法;如果MyService本来就是 Starter 自己必然提供的类,这个条件意义不大。
如果希望 Starter 可以通过开关关闭,还可以加条件属性:
@ConditionalOnProperty(
prefix = "my.starter",
name = "enabled",
havingValue = "true",
matchIfMissing = true
)
matchIfMissing = true 表示用户不配置时默认开启。
3.4 添加配置属性类
创建一个 @ConfigurationProperties 类,用于绑定配置文件中的属性。
示例:MyStarterProperties.java
@ConfigurationProperties(prefix = "my.starter")
public class MyStarterProperties {
private String name = "DefaultName"; // 默认值
private int age = 25;
// Getters and Setters
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
这里的默认值直接写在字段上,用户配置 my.starter.name、my.starter.age 后就会覆盖默认值。配置前缀建议使用小写字母和短横线风格,例如 my.starter、demo.client,避免使用驼峰或大写前缀。
@ConfigurationProperties 类要被生效,常见方式有三种:
- 在自动配置类上使用
@EnableConfigurationProperties(MyStarterProperties.class); - 在配置类上使用
@ConfigurationPropertiesScan; - 把 Properties 类声明为 Spring Bean,例如加
@Component。
Starter 场景下更推荐第一种,由自动配置类显式启用,边界更清楚。
3.5 注册自动配置类
在 resources/META-INF/spring.factories 中,将自动配置类注册到 Spring Boot。
示例:spring.factories
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.example.mystarter.MyStarterAutoConfiguration
这是 Spring Boot 2.x 常见写法。到了 Spring Boot 3.x,自动配置注册文件变成了:
src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
文件内容每行一个自动配置类全限定名,例如:
com.example.mystarter.MyStarterAutoConfiguration
Spring Boot 2.7 属于过渡版本,两种方式都能看到;Spring Boot 3.x 新项目应使用 AutoConfiguration.imports。另外,Spring Boot 2.7+ 也推荐自动配置类使用 @AutoConfiguration 替代普通 @Configuration,它本质上是带 proxyBeanMethods = false 的配置类,更适合自动配置场景。
4. 验证 Starter
创建一个新的 Spring Boot 项目,引入自定义 Starter,并验证其功能。
4.1 引入 Starter
在主项目的 pom.xml 中添加依赖:
<dependency>
<groupId>com.example</groupId>
<artifactId>my-spring-boot-starter</artifactId>
<version>1.0.0</version>
</dependency>
4.2 测试 Starter 的功能
在主项目的 application.properties 文件中配置:
my.starter.name=MyCustomName
my.starter.age=30
然后在项目中注入并使用 MyService:
@RestController
public class MyController {
private final MyService myService;
public MyController(MyService myService) {
this.myService = myService;
}
@GetMapping("/info")
public String getInfo() {
return myService.getInfo();
}
}
结果: 访问 /info,输出:
My name is MyCustomName, and I am 30 years old.
除了启动项目手工验证,更推荐用 ApplicationContextRunner 做自动配置测试,重点验证两个行为:默认 Bean 是否注册、用户自定义 Bean 时默认 Bean 是否退让。
class MyStarterAutoConfigurationTest {
private final ApplicationContextRunner contextRunner = new ApplicationContextRunner()
.withConfiguration(AutoConfigurations.of(MyStarterAutoConfiguration.class));
@Test
void shouldRegisterDefaultBean() {
contextRunner.run(context -> {
assertThat(context).hasSingleBean(MyService.class);
assertThat(context.getBean(MyService.class).getInfo())
.contains("DefaultName");
});
}
@Test
void shouldBackOffWhenUserDefinesBean() {
contextRunner
.withBean(MyService.class, () -> new MyService("user", 18))
.run(context -> {
assertThat(context).hasSingleBean(MyService.class);
assertThat(context.getBean(MyService.class).getInfo())
.contains("user");
});
}
}
如果自动配置没有生效,可以启动时加 --debug,查看 Spring Boot 的自动配置报告,确认是“未匹配条件”“被用户 Bean 覆盖”还是“注册文件没读到”。
5. Spring Boot Starter 的最佳实践
- 设计通用性:Starter 应尽量高内聚、低耦合,避免把具体业务逻辑直接写进去;
- 提供合理默认值:在配置属性类字段上设置默认值,降低用户配置难度;
- 允许用户扩展:通过
@ConditionalOnMissingBean提供扩展能力,让用户可以自定义 Bean; - 文档说明:为 Starter 提供清晰的使用说明、配置项说明和示例;
- 模块拆分:自动配置代码放
autoconfigure模块,Starter 模块只聚合依赖; - 不要依赖组件扫描:自动配置类应通过注册文件被加载,不要靠
@ComponentScan扫描,也不要在自动配置类里随便扫业务包; - 条件尽量精确:不要滥用
@ConditionalOnMissingBean、@ConditionalOnBean,避免 Bean 注册顺序和条件判断变得难以推理; - 补充配置元数据:引入
spring-boot-configuration-processor,让 IDE 能提示配置项; - 注意版本兼容:Spring Boot 2.x 和 3.x 的自动配置注册文件不同,面试中能主动提出来是加分项。
易错点与对比
Starter 与自动配置不是一回事
- Starter:更像依赖描述符,负责把相关依赖带进来;
- 自动配置:负责根据条件注册 Bean。
官方推荐拆成 xxx-spring-boot-autoconfigure 和 xxx-spring-boot-starter 两个模块。简单项目合成一个模块也能跑,但面试回答时最好说清楚这个区别。
spring.factories 与 AutoConfiguration.imports
- Spring Boot 2.x 常见:
META-INF/spring.factories中配置org.springframework.boot.autoconfigure.EnableAutoConfiguration; - Spring Boot 2.7+ 开始支持:
META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports; - Spring Boot 3.x:新项目应使用
AutoConfiguration.imports。
只写自动配置类、不写注册文件,是 Starter 不生效的高频原因。
默认配置不要随便放进 Starter 的 application.properties
原文提到“定义 application.properties/yml 的默认配置,用户可覆盖”,这个说法要谨慎。Starter 里再放一个 application.properties 容易和用户配置、其他 jar 中的配置发生冲突,也不利于排查。
更推荐的做法是:把默认值写在 @ConfigurationProperties 字段上,用户配置后自然覆盖;需要给 IDE 提示时,再补充配置元数据。这样边界更清楚。
@ConditionalOnMissingBean 与 @ConditionalOnBean
@ConditionalOnMissingBean 常用于“用户没定义时给默认实现”,让用户自定义 Bean 优先。它适合放在自动配置类里,不适合在普通业务配置中滥用。@ConditionalOnBean 则依赖 Bean 的注册顺序,条件判断容易变得隐晦,能不用就不用。
@ConditionalOnClass 判断谁
@ConditionalOnClass 的典型用途是判断某个第三方库是否在类路径上,而不是判断 Starter 自己必然存在的类。示例中写 MyService.class 是为了演示语法;真实场景可以写成 @ConditionalOnClass(RedisClient.class) 这类第三方类。
自动配置类不要靠 @ComponentScan 加载
自动配置类应该通过 spring.factories 或 AutoConfiguration.imports 注册,由 Spring Boot 的自动配置机制加载。不要指望用户启动类扫描到你的包,也不要在自动配置类上写 @ComponentScan 去扫业务包,否则