" />
文章

面试题:写过 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 通常包含几件事:

  1. 依赖管理:把所需依赖聚合进来;
  2. 自动配置类:用 @Configuration 或 @AutoConfiguration 注册 Bean;
  3. 条件装配:用 @ConditionalOnClass、@ConditionalOnMissingBean、@ConditionalOnProperty 等控制是否生效;
  4. 配置属性:用 @ConfigurationProperties 绑定 application.yml/properties;
  5. 注册文件: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:通常用于封装内部工具、第三方库、业务逻辑模块等。

它的价值主要体现在两点:

  1. 依赖收敛:用户不需要自己逐个找依赖、对版本;
  2. 自动配置:用户引入依赖后,只要少量配置甚至零配置就能用。

但要区分一个常见误区:Starter 本身更像“依赖描述符”,真正让 Bean 自动注册的是自动配置类。面试时说清楚这层关系,会比单纯背“加 spring.factories”更扎实。

2. 自定义 Starter 的结构

一个典型的 Starter 项目包含以下内容:

  1. 依赖管理:提供所需的依赖,如第三方库或其他 Starter;
  2. 自动配置:通过 @Configuration 或 @AutoConfiguration 注解类,注册需要的 Bean;
  3. 条件装配:通过 @ConditionalOn... 注解,控制配置的加载条件;
  4. 默认配置:定义配置属性的默认值,用户可通过 application.properties/yml 覆盖;
  5. 注册文件:把自动配置类注册给 Spring Boot;
  6. 可选:配置元数据:通过 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 里随便写死版本。

  1. Spring Boot 自动配置依赖:

xml <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-autoconfigure</artifactId> </dependency>

  1. 测试依赖:

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 类要被生效,常见方式有三种:

  1. 在自动配置类上使用 @EnableConfigurationProperties(MyStarterProperties.class);
  2. 在配置类上使用 @ConfigurationPropertiesScan;
  3. 把 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 的最佳实践

  1. 设计通用性:Starter 应尽量高内聚、低耦合,避免把具体业务逻辑直接写进去;
  2. 提供合理默认值:在配置属性类字段上设置默认值,降低用户配置难度;
  3. 允许用户扩展:通过 @ConditionalOnMissingBean 提供扩展能力,让用户可以自定义 Bean;
  4. 文档说明:为 Starter 提供清晰的使用说明、配置项说明和示例;
  5. 模块拆分:自动配置代码放 autoconfigure 模块,Starter 模块只聚合依赖;
  6. 不要依赖组件扫描:自动配置类应通过注册文件被加载,不要靠 @ComponentScan 扫描,也不要在自动配置类里随便扫业务包;
  7. 条件尽量精确:不要滥用 @ConditionalOnMissingBean、@ConditionalOnBean,避免 Bean 注册顺序和条件判断变得难以推理;
  8. 补充配置元数据:引入 spring-boot-configuration-processor,让 IDE 能提示配置项;
  9. 注意版本兼容: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 去扫业务包,否则

License:  CC BY 4.0