Spring Boot 自定义 Starter

#Spring #Bean #Starter

Spring Boot Starter

什么是 Starter

Starter 是 Spring Boot 提供的一种依赖管理机制,它将一组相关的依赖打包在一起,让开发者只需引入一个依赖即可获得完整的功能支持。

以常用的 spring-boot-starter-web 为例,我们只需在 pom.xml 中添加一行依赖声明,即可获得构建 Web 应用所需的全部组件。

Starter 的结构分析

打开本地 Maven 仓库中的 ~/.m2/repository/org/springframework/boot/spring-boot-starter-web/4.0.1,可以看到以下文件结构:

.
├── _remote.repositories
├── spring-boot-starter-web-4.0.1.jar
├── spring-boot-starter-web-4.0.1.jar.sha1
├── spring-boot-starter-web-4.0.1.pom
└── spring-boot-starter-web-4.0.1.pom.sha1

查看其 pom 文件,会发现它实际上聚合了多个子依赖:

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-jackson</artifactId>
        <version>4.0.1</version>
        <scope>compile</scope>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-tomcat</artifactId>
        <version>4.0.1</version>
        <scope>compile</scope>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-http-converter</artifactId>
        <version>4.0.1</version>
        <scope>compile</scope>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-webmvc</artifactId>
        <version>4.0.1</version>
        <scope>compile</scope>
    </dependency>
</dependencies>

解压 JAR 包后,内容仅有:

├── META-INF
│   ├── LICENSE.txt
│   ├── MANIFEST.MF
│   └── NOTICE.txt

这说明 Starter 本身只是一个空壳,它的核心作用是统一管理依赖及其版本,从而简化开发配置流程。

真正的业务逻辑在哪里?

既然 Starter 只是依赖聚合,那实际的功能代码在哪里呢?答案是 Auto-Configure 模块

spring-boot-http-converter 为例,打开其 pom 文件:

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot</artifactId>
        <version>4.0.1</version>
        <scope>compile</scope>
    </dependency>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-web</artifactId>
        <version>7.0.2</version>
        <scope>compile</scope>
    </dependency>
</dependencies>

解压其 JAR 包,可以看到完全不同的结构:

.
├── META-INF
│   ├── additional-spring-configuration-metadata.json
│   ├── LICENSE.txt
│   ├── MANIFEST.MF
│   ├── NOTICE.txt
│   ├── spring
│   │   └── org.springframework.boot.autoconfigure.AutoConfiguration.imports
│   ├── spring-autoconfigure-metadata.properties
│   ├── spring-configuration-metadata.json
│   └── spring.factories                          # 旧版自动配置入口
└── org
    └── springframework
        └── boot
            └── http
                └── converter
                    └── autoconfigure
                        ├── ClientHttpMessageConvertersCustomizer.class
                        ├── DefaultClientHttpMessageConvertersCustomizer.class
                        ├── DefaultServerHttpMessageConvertersCustomizer.class
                        └── Jackson2HttpMessageConvertersConfiguration$...class

其中,org.springframework.boot.autoconfigure.AutoConfiguration.imports 文件指定了自动配置类的路径:

org.springframework.boot.http.converter.autoconfigure.Jackson2HttpMessageConvertersConfiguration$Jackson2JsonMessageConvertersCustomizer

小结:Starter = 依赖聚合(空壳) + Auto-Configure(实际业务逻辑 + 自动装配配置)


如何编写自定义 Starter

通过上述分析,我们可以总结出创建 Starter 的核心步骤:

  1. 编写业务代码:实现所需的功能逻辑
  2. 创建自动配置类:使用 @Configuration + 条件注解
  3. 注册自动配置:在 META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports 中声明配置类路径
  4. 创建 Starter 模块:作为空依赖聚合,引入 auto-configure 和其他第三方依赖

项目结构

按照 Spring Boot 官方规范,一个完整的自定义 Starter 应包含两个模块:

my-starter-parent/
├── pom.xml                                    # 父 POM
├── say-something-spring-boot-autoconfigure/   # 自动配置模块(包含实际代码)
│   ├── pom.xml
│   └── src/main/
│       ├── java/
│       │   └── com/example/autoconfigure/
│       │       ├── SaySomethingAutoConfiguration.java
│       │       └── SaySomethingService.java
│       └── resources/
│           └── META-INF/
│               └── spring/
│                   └── org.springframework.boot.autoconfigure.AutoConfiguration.imports
└── say-something-spring-boot-starter/         # Starter 模块(空壳,仅聚合依赖)
    └── pom.xml

模块一: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>my-starter-parent</artifactId>
        <version>1.0.0</version>
    </parent>

    <artifactId>say-something-spring-boot-autoconfigure</artifactId>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-autoconfigure</artifactId>
        </dependency>
        <!-- 其他业务所需依赖 -->
    </dependencies>
</project>

服务类

package com.example.autoconfigure;

public class SaySomethingService {

    private String prefix = "Hello";

    public String say(String name) {
        return prefix + ", " + name + "!";
    }

    public void setPrefix(String prefix) {
        this.prefix = prefix;
    }
}

自动配置类

package com.example.autoconfigure;

import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
@ConditionalOnMissingBean(SaySomethingService.class)
public class SaySomethingAutoConfiguration {

    @Bean
    public SaySomethingService saySomethingService() {
        return new SaySomethingService();
    }
}

注册自动配置

src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports

com.example.autoconfigure.SaySomethingAutoConfiguration

模块二:starter(依赖聚合模块)

这是一个 纯空壳模块,不包含任何代码,只有一个 pom.xml 用于聚合依赖。

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>my-starter-parent</artifactId>
        <version>1.0.0</version>
    </parent>

    <artifactId>say-something-spring-boot-starter</artifactId>

    <dependencies>
        <!-- 引入自动配置模块 -->
        <dependency>
            <groupId>com.example</groupId>
            <artifactId>say-something-spring-boot-autoconfigure</artifactId>
            <version>${project.version}</version>
        </dependency>

        <!-- 引入其他第三方依赖(如有需要) -->
        <!--
        <dependency>
            <groupId>com.google.guava</groupId>
            <artifactId>guava</artifactId>
            <version>32.1.2-jre</version>
        </dependency>
        -->
    </dependencies>
</project>

⚠️ 注意:Starter 模块没有 src 目录,打包后的 JAR 只包含 META-INF 基础信息,与官方 Starter 结构完全一致。


使用自定义 Starter

在其他项目中,只需引入 Starter 模块即可:

<dependency>
    <groupId>com.example</groupId>
    <artifactId>say-something-spring-boot-starter</artifactId>
    <version>1.0.0</version>
</dependency>

然后直接注入使用:

@RestController
public class HelloController {

    @Autowired
    private SaySomethingService saySomethingService;

    @GetMapping("/hello")
    public String hello(@RequestParam String name) {
        return saySomethingService.say(name);
    }
}

验证配置是否生效

application.properties 中开启调试模式:

debug=true

启动应用后,查看控制台日志,如果出现以下信息则说明自动配置成功:

SaySomethingAutoConfiguration matched:
   - @ConditionalOnMissingBean (types: com.example.autoconfigure.SaySomethingService; SearchStrategy: all) did not find any beans (OnBeanCondition)

总结

模块 职责 包含内容
starter 依赖聚合入口 pom.xml,无代码
autoconfigure 自动配置逻辑 业务代码 + 配置类 + imports 文件

用户只需引入 starter 一个依赖,即可自动获得 autoconfigure 及其所有传递依赖,实现开箱即用。


Reference