Skip to content

Spring Boot 构建指南:从新手到实战 🚀

前言:为什么需要了解 Spring Boot 构建?

想象一下,你刚写完一个精美的 Spring Boot 应用,但是当你想要部署到服务器或者分享给同事时,却发现不知道如何正确地打包和构建。这就像做了一道美味的菜却不知道如何装盘一样令人沮丧。

Spring Boot 的构建系统就是解决这个问题的关键!它不仅能帮你创建可执行的 JAR 包,还能生成各种有用的元数据信息,让你的应用更加专业和可维护。

NOTE

Spring Boot 支持 Maven 和 Gradle 两种主流构建工具,本文将重点介绍 Maven 的使用方式,因为它在企业级开发中更为常见。

1. 生成构建信息:让你的应用有"身份证" 📋

什么是构建信息?

构建信息就像是应用的"身份证",包含了项目的坐标、名称、版本等关键信息。当你的应用运行时,Spring Boot 会自动配置一个 BuildProperties Bean,让你可以在代码中访问这些信息。

Maven 配置方式

xml
<build>
    <plugins>
        <plugin>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-maven-plugin</artifactId>
            <version>3.5.0</version>
            <executions>
                <execution>
                    <goals>
                        <goal>build-info</goal> 
                    </goals>
                </execution>
            </executions>
        </plugin>
    </plugins>
</build>
kotlin
springBoot {
    buildInfo() 
}

在 Kotlin 代码中使用构建信息

kotlin
@RestController
class InfoController(
    private val buildProperties: BuildProperties
) {

    @GetMapping("/info")
    fun getAppInfo(): Map<String, Any> {
        return mapOf(
            "name" to buildProperties.name,           // 应用名称
            "version" to buildProperties.version,     // 版本号
            "group" to buildProperties.group,         // 组织名
            "artifact" to buildProperties.artifact,   // 构件名
            "time" to buildProperties.time           // 构建时间
        )
    }
}

TIP

这个功能在微服务架构中特别有用,可以帮助运维人员快速识别当前运行的应用版本,便于问题排查和版本管理。

2. 生成 Git 信息:追踪代码版本 🔍

为什么需要 Git 信息?

在团队开发中,经常会遇到这样的场景:

  • "这个 Bug 是什么时候引入的?"
  • "当前运行的代码是基于哪个 commit?"
  • "这个版本包含了哪些最新的修改?"

Git 信息生成功能就是为了解决这些问题!

Maven 配置

xml
<build>
    <plugins>
        <plugin>
            <groupId>io.github.git-commit-id</groupId>
            <artifactId>git-commit-id-maven-plugin</artifactId> 
        </plugin>
    </plugins>
</build>

在应用中使用 Git 信息

kotlin
@RestController
class GitInfoController(
    private val gitProperties: GitProperties
) {

    @GetMapping("/git-info")
    fun getGitInfo(): Map<String, Any?> {
        return mapOf(
            "branch" to gitProperties.branch,              // 当前分支
            "commitId" to gitProperties.commitId,          // 提交ID
            "commitTime" to gitProperties.commitTime,      // 提交时间
            "commitMessage" to gitProperties.get("commit.message.short") // 提交信息
        )
    }
}

IMPORTANT

Git 信息中的时间格式必须遵循 yyyy-MM-dd'T'HH:mm:ssZ 格式,这样 Jackson 才能正确序列化为 JSON。

3. 生成 CycloneDX SBOM:软件物料清单 📦

什么是 SBOM?

SBOM(Software Bill of Materials)就像是食品包装上的成分表,详细列出了你的应用使用了哪些第三方库和依赖。在当今网络安全日益重要的环境下,SBOM 帮助组织了解和管理软件供应链风险。

Maven 配置

xml
<build>
    <plugins>
        <plugin>
            <groupId>org.cyclonedx</groupId>
            <artifactId>cyclonedx-maven-plugin</artifactId> 
        </plugin>
    </plugins>
</build>

NOTE

生成的 SBOM 文件通常位于 target 目录下,可以用于安全扫描和合规性检查。

4. 创建可执行 JAR:一键部署的魅力 ✨

传统 JAR vs Fat JAR

让我们通过对比来理解 Fat JAR 的优势:

bash
# 需要手动管理所有依赖
java -cp "app.jar:lib/spring-boot-3.5.0.jar:lib/spring-web-6.1.0.jar:..." com.example.Application
# 😰 依赖管理噩梦!
bash
# 一个命令搞定!
java -jar myapp.jar
# 🎉 所有依赖都打包在内!

Maven 配置

xml
<build>
    <plugins>
        <plugin>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-maven-plugin</artifactId> 
        </plugin>
    </plugins>
</build>

Fat JAR 的内部结构

5. 应用作为依赖:模块化开发 🧩

问题场景

假设你开发了一个用户管理服务,现在想在其他项目中复用其中的用户实体类和工具方法。但是直接依赖可执行 JAR 会遇到问题:

kotlin
// ❌ 这样做会失败!
// 因为可执行 JAR 把类放在了 BOOT-INF/classes 目录下
// 其他项目无法找到这些类

解决方案:生成分类器 JAR

xml
<build>
    <plugins>
        <plugin>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-maven-plugin</artifactId>
            <configuration>
                <classifier>exec</classifier> 
            </configuration>
        </plugin>
    </plugins>
</build>

这样配置后,Maven 会生成两个 JAR 文件:

  • myapp-1.0.jar - 普通 JAR,可作为依赖使用
  • myapp-1.0-exec.jar - 可执行 JAR,用于部署运行

实际使用示例

kotlin
// 在用户管理服务中定义
@Entity
@Table(name = "users")
data class User(
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    val id: Long = 0,

    @Column(unique = true)
    val username: String,

    val email: String
)
kotlin
// 在订单服务中引用用户实体
@Service
class OrderService {

    fun createOrder(userId: Long, items: List<OrderItem>): Order {
        // 可以直接使用 User 实体类
        val user = userRepository.findById(userId)
        // ... 业务逻辑
    }
}

6. 特殊库的处理:解决兼容性问题 🔧

问题背景

某些第三方库(如 JRuby)期望以独立文件的形式存在,而不是嵌套在 JAR 中。这时就需要配置自动解压:

xml
<build>
    <plugins>
        <plugin>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-maven-plugin</artifactId>
            <configuration>
                <requiresUnpack> 
                    <dependency>
                        <groupId>org.jruby</groupId>
                        <artifactId>jruby-complete</artifactId>
                    </dependency>
                </requiresUnpack>
            </configuration>
        </plugin>
    </plugins>
</build>

WARNING

确保操作系统不会在应用运行期间删除临时目录中解压的 JAR 文件,否则可能导致应用异常。

7. 远程调试:开发者的调试利器 🐛

配置远程调试

bash
# 启动应用时添加调试参数
mvn spring-boot:run -Dspring-boot.run.jvmArguments="-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=5005"

调试流程图

8. 实战案例:构建一个完整的微服务应用 🎯

让我们通过一个实际的例子来综合运用这些构建技巧:

完整的 pom.xml 配置示例
xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0">
    <modelVersion>4.0.0</modelVersion>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>3.5.0</version>
        <relativePath/>
    </parent>

    <groupId>com.example</groupId>
    <artifactId>user-service</artifactId>
    <version>1.0.0</version>
    <packaging>jar</packaging>

    <properties>
        <kotlin.version>1.9.10</kotlin.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.jetbrains.kotlin</groupId>
            <artifactId>kotlin-stdlib</artifactId>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <!-- Spring Boot Maven 插件 -->
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <executions>
                    <!-- 生成构建信息 -->
                    <execution>
                        <id>build-info</id>
                        <goals>
                            <goal>build-info</goal>
                        </goals>
                    </execution>
                </executions>
                <configuration>
                    <!-- 生成可依赖的分类器 JAR -->
                    <classifier>exec</classifier>
                </configuration>
            </plugin>

            <!-- Git 信息插件 -->
            <plugin>
                <groupId>io.github.git-commit-id</groupId>
                <artifactId>git-commit-id-maven-plugin</artifactId>
            </plugin>

            <!-- SBOM 生成插件 -->
            <plugin>
                <groupId>org.cyclonedx</groupId>
                <artifactId>cyclonedx-maven-plugin</artifactId>
            </plugin>

            <!-- Kotlin 编译插件 -->
            <plugin>
                <groupId>org.jetbrains.kotlin</groupId>
                <artifactId>kotlin-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
</project>

对应的 Kotlin 应用代码

kotlin
@SpringBootApplication
class UserServiceApplication

fun main(args: Array<String>) {
    runApplication<UserServiceApplication>(*args)
}

@RestController
class UserController(
    private val buildProperties: BuildProperties,
    private val gitProperties: GitProperties
) {

    @GetMapping("/users")
    fun getUsers(): List<User> {
        // 业务逻辑...
        return listOf(
            User(1, "alice", "[email protected]"),
            User(2, "bob", "[email protected]")
        )
    }

    @GetMapping("/actuator/info") 
    fun getAppInfo(): Map<String, Any?> {
        return mapOf(
            "app" to mapOf(
                "name" to buildProperties.name,
                "version" to buildProperties.version,
                "buildTime" to buildProperties.time
            ),
            "git" to mapOf(
                "branch" to gitProperties.branch,
                "commit" to gitProperties.shortCommitId,
                "commitTime" to gitProperties.commitTime
            )
        )
    }
}

data class User(
    val id: Long,
    val username: String,
    val email: String
)

总结:构建系统的最佳实践 🎖️

通过本文的学习,我们掌握了 Spring Boot 构建系统的核心功能:

  1. 构建信息生成 - 让应用有"身份证" ✅
  2. Git 信息追踪 - 代码版本可追溯 ✅
  3. SBOM 生成 - 软件供应链透明化 ✅
  4. 可执行 JAR - 简化部署流程 ✅
  5. 模块化支持 - 代码复用更容易 ✅

最佳实践建议

  • 在生产环境中始终启用构建信息和 Git 信息生成
  • 为需要被其他项目依赖的模块配置分类器
  • 定期检查和更新 SBOM 以确保安全合规
  • 在开发阶段充分利用远程调试功能

Spring Boot 的构建系统不仅仅是打包工具,更是现代 Java 应用开发和运维的重要基础设施。掌握这些技能,将让你在微服务架构和 DevOps 实践中更加得心应手! 🚀