第一次真正用上 Spring Boot,我记得最清楚的并不是某个功能,而是它解决了一个在 Java 后端里特别常见的问题:项目还没开始写业务逻辑,搭建过程已经耗掉半天。最初接触的几年,我一直以为每个项目都得手动复制结构、拉扯依赖版本、配置打包插件,直到用上 Spring Initializr 之后才意识到,脚手架这件事本来就该交给工具。Spring Boot 3.x 出来以后,Java 17 成了硬门槛,很多旧项目的依赖组合也开始失效,这时候重新理解 Spring Initializr 就显得很有必要。
这篇文章主要面向两类人:一类是刚接触 Spring Boot,想知道从哪里开始的人;另一类是已经写过 Spring Boot 2.x,想顺手摸清 3.x 脚手架选型和落地细节的人。我会从创建思路讲到页面上的每个选项,再带你把一个生成后的项目真的跑起来,最后补一版我踩过的版本冲突复盘。整个过程不复杂,但把这些细节看明白,能帮你少走很多弯路。
1. 为什么我不建议纯手工创建 Spring Boot 项目
1.1 手动创建背后要默默承受的工作量
很多人第一次尝试 Spring Boot 时,都会经历这样的路径:打开 IDE,新建一个 Maven 项目,然后开始复制一个看起来能用的pom.xml。Spring Boot 确实能帮你省掉 Spring MVC 时代大量的配置烦恼,但它并不是说项目骨架本身就不用搭了。骨架搭得对不对,直接决定第一次启动能不能顺利看到日志。
手动创建一个可靠的 Spring Boot 项目,你至少要自己确认下面这些内容:Spring Boot 版本号、Spring Framework 版本号、Java 编译器级别、起步依赖的坐标、Maven 打包插件、主类的位置、包名的规划、配置文件的位置。这些内容看起来不多,实际上每一项都容易翻车。比如你把spring-boot-starter-web写错成spring-web,或者某个依赖版本和 Boot 3.x 不匹配,启动时报的错往往不是告诉你依赖不对,而是一堆让你摸不着头脑的类加载异常。
1.2 我在手动搭项目时真实遇到的三个问题
第一个问题出在依赖版本。Spring Boot 的版本管理机制是靠spring-boot-dependencies这个 BOM 来统一控制次要依赖版本的。手动搭项目时,如果你图省事直接拷贝网上某篇文章里的坐标,很容易拿到一个旧版本或者根本不是同一体系的东西。
第二个问题出在主类位置。Spring Boot 启动时要扫描主类所在包以及子包下的组件,主类位置偏一步,@RestController、@Service这些注解就全部失效。我见过有人把主类放在com.xxx.demo下,Controller 放在com.xxx.controller下,启动一点错都不报,但接口始终 404。这种问题排查起来特别烦。
第三个问题出在插件。Spring Boot 的可执行 jar 需要spring-boot-maven-plugin来处理。少了这个插件,代码能编译,但java -jar跑不起来,每次只能靠 IDE 运行。要是没有经验,根本想不到是少了一个插件。
1.3 Spring Initializr 到底帮你做了什么事
用一句话概括:Spring Initializr 就是把上面这些容易出错的逻辑变成标准流程,按你需要的方式生成一个最小可运行骨架。它会自动配好 BOM、自动选择兼容的起步依赖、自动写好主类位置和结构、自动带上 Maven 或 Gradle 的 Wrapper 文件。你拿到手里的不是一段残缺的代码,而是一个开箱即跑的项目。
对我个人而言,更重要的其实是它带来的秩序感。团队里每个新项目都从 Initializr 生成,结构一致性会高很多,后来的人接手不需要猜这个项目当初是怎么组织包名的。这也是为什么即使我自认熟悉 Spring Boot,还是习惯用 Initializr 创建项目的基础部分。
2. start.spring.io 选型:每一项点下去之前先想明白
浏览器打开start.spring.io时,你看到的是一个比较长的表单。如果不了解每个选项的意义,多半会随便点默认值。但实际上这里有几个选项决定了项目的长期走向,值得在动手前认真想一下。
2.1 项目类型:Maven 还是 Gradle
Initializr 默认给你 Maven,我大多数项目也会选 Maven。原因很简单,Maven 在 Java 后端团队里生态最成熟,依赖管理方式直观,IDEA 对它的支持也很完善。启动一个 Spring Boot 项目,Maven 的那一套生命周期模型足够覆盖日常需求,不需要额外学习 Groovy 或 Kotlin DSL。
Gradle 也没有问题,它的增量构建和灵活度在大型项目里确实有优势。但如果你是第一次接触 Spring Boot,我建议不要在这里增加学习成本。后续如果团队确实需要切换到 Gradle,再从生成的 Maven 结构迁移也不算难,毕竟业务代码和目录结构是基本一致。
2.2 语言与 Spring Boot 版本:注意看稳定版本
语言选择默认是 Java,目前大多数团队的实际选择也是 Java。Kotlin 和 Groovy 也有各自的用户群,但它们更适合在确定团队语言路线后再使用。Initializr 生成的项目本质上就是一种起步模板,换语言的影响远大于换构建工具,所以这里不建议随意尝鲜。
版本列表里除了数字还有一个隐藏知识:SNAPSHOT 表示某个开发中的快照版,M 开头表示里程碑版,RC 表示候选发布版,真正的稳定版通常不带这些后缀。对于生产项目,请直接选最后一个 Release。Spring Boot 3.x 这条线上目前已经有不少稳定版本,我在日常选择时会更倾向于已经发布一段时间的版本,而不是刚发布的最新版。
Spring Boot 3.x 和 2.x 最大的区别之一,就是它基于 Java 17 构建,最低也要 Java 17 才能跑。如果你的机器上还装的是 Java 8 或 Java 11,那生成后第一关就过不了。这里连带决定了下一个选项的取值。
2.3 项目元数据:这些名字后面很难再改
Group、Artifact、Name、Description、Package name,这几个字段看起来只是取名,其实影响深远。Group 通常是公司域名的倒写,比如com.example,Artifact 是项目名,一般用小写字母和连字符。Name 会变成项目显示名,Package name 则直接决定主类所在的包路径。
我特别想强调 Package name 的选择。它不是简单写一个字符串就完事,而是会体现在目录结构里,成为你所有业务包的基础。一旦项目开始写代码,再改包名就是一场大迁移。与其纠结“以后可能要分开几个微服务怎么办”,不如先按现有业务域选一个清晰、不带年份和临时代号的包名。
Java 版本选项通常会根据你选择的 Boot 版本自动给出候选值。Spring Boot 3.x 默认 Java 17,如果团队环境已经统一到 21,也可以直接选 21。这里的原则是整个团队保持一致,别出现有人本地编译用 17、CI 里用 21 的情况。
Packaging 通常选 Jar。Spring Boot 项目最常见的形态就是可执行 Jar,内部通过内嵌 Web 服务器对外提供服务。只有当你必须部署到传统外部 Servlet 容器时,才需要考虑 War。
2.4 依赖栏:先武装三四件,不要一口气全选
依赖选择是新手最容易上头的地方。看到一堆 Spring Web、Spring Data JPA、Security、Validation、DevTools,恨不得全部点上。我的建议是先冷静,只勾选当前阶段明确需要的东西。
理由很简单:依赖是会互相牵连的。Boot 的 BOM 虽然能缓解版本冲突,但相对的,你加进来的每个起步依赖都会扩展自动配置的范围。比如你勾了 Spring Data JPA,Boot 就会尝试寻找数据源;没有配置数据源之前启动会直接失败,除非你额外排除或提供相关配置。勾得太多的结果往往是:项目生成后第一次启动,就爆出一堆你暂时用不上的错误,网络上一搜,又是一堆让你困惑的解释。
比较稳妥的第一批依赖组合大概是:Spring Web、Validation、DevTools。要做数据库操作再补 Spring Data JPA 和对应的数据库驱动;要做接口鉴权再补 Spring Security。依赖并不是越多越好,而是你能解释清楚每一个为什么存在时才加上去。
3. 生成项目之后:从 zip 包到第一条启动日志
3.1 点击 Generate 后拿到的是什么
点击 Generate 后下载的是一个 zip 压缩包。解压后你会看到一个标准项目结构,里面最主要的是这几个部分:Maven 或 Gradle 的 Wrapper 文件、pom.xml、src/main/java、src/main/resources、src/test。
这里有一个别忽略的细节:生成的 zip 里带有.mvn/wrapper/maven-wrapper.properties或类似文件。它相当于把 Maven 本身也纳入版本管理,本地即使没有安装 Maven,也可以直接用./mvnw命令构建。很多第一次接触的人不知道这一点,还会专门去装一个全局 Maven,其实没有必要。
3.2 导入 IDE 时的两个要点
在 IDEA 里我习惯用这种方式:直接选择 "Open" 或 "导入项目",定位到解压出来的目录,选择pom.xml或整个项目根目录。IDEA 会自动识别这是一个 Maven 项目,然后开始下载依赖。首次下载依赖会花一点时间,取决于网络环境和本地 Maven 缓存。
导入完成后,建议先打开pom.xml看一眼依赖树,确认你勾选的内容真的进来了。不要嫌这一步多余,后面排查问题时你会感谢自己知道项目里到底有哪些依赖。
3.3 第一次启动:看日志而不是看心情
生成的主类通常是一个带有@SpringBootApplication注解和main方法的 Java 类。右键运行它,如果你的依赖和环境没有问题,日志里会出现一行类似于 "Tomcat started on port(s): 8080" 的输出。这行日志是第一个关键信号:项目已经完成启动,可以进入调试阶段。
第一次启动如果报错,也别慌。绝大多数情况下错误分为两类:一类是版本环境问题,比如 JDK 版本不对;另一类是配置依赖问题,比如勾选了 JPA 但没配置数据库。看到堆栈先别急着粘贴到搜索引擎,往上面翻日志的最前面几行,真正的根因一般都写在前面。
3.4 目录结构速认:先知道代码应该放哪
生成的骨架里,src/main/java下是 Java 源码,包名和你填写的 Package name 一致。resources目录下会有application.properties或application.yml,配置都会集中在这里。templates和static目录在你要写服务端渲染页面或放静态资源时才用得上,单纯做 REST API 可以先不管。
关键是主类的位置。项目里所有带注解的组件类,都应该放在主类所在包或其子包下面。歪一个层级,Spring 自动扫描就可能扫不到,后面调试会非常痛苦。
4. 把骨架变成服务:第一个接口与第一次验证
4.1 先写一个验证用的 REST 接口
项目能启动之后,你面临的最实际问题是“怎么知道它真的能用”。答案很简单:写一个最小的接口,用浏览器或命令行访问它。
在src/main/java你的包目录下面新建一个controller包,然后创建一个HelloController.java。代码大概是下面这样:
package com.example.demo.controller; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @RestController @RequestMapping("/api") public class HelloController { @GetMapping("/hello") public String hello() { return "Hello from Spring Boot 3.x"; } }这里@RestController表示这个类是一个能直接返回数据的接口类,@GetMapping("/hello")表示当浏览器访问/api/hello时,会调用这个方法。写完后重启应用,然后打开浏览器访问http://localhost:8080/api/hello。如果你能看到返回文本,恭喜,整个链路通了。
4.2 用 curl 验证比想象中更高效
浏览器能访问,说明 GET 接口没问题了。但后续你要调试的服务可能不只是 GET,所以我更推荐养成使用 curl 的习惯。比如:
curl http://localhost:8080/api/hello这个命令会直接返回接口产生的文本。如果以后接口需要 POST 请求、需要带请求体,curl 都能比浏览器操作给出更直接的反馈。
4.3 真正常用的一小步:改端口和加前缀
项目跑起来后,你可能很快就会遇到端口被占用的情况。在application.properties里加上server.port=8081,重启后端口就会变成 8081。如果你希望所有接口都有一个统一的前缀,可以设置server.servlet.context-path=/demo。那么上面的接口地址就变成了http://localhost:8081/demo/api/hello。
这些配置尽可能写在 YAML 或 properties 里,而不是硬编码到代码里。因为同一个项目在不同环境里,端口、数据库地址、密钥这些值都不同,把它们放在配置文件中,是后续部署和运维的重要基础。
5. 版本边界的隐藏约束:一次依赖冲突的真实复盘
5.1 Spring Boot 3.x 对 Java 环境和依赖生态的重新定义
如果你是从 2.x 升级过来的,应该对那几个变化印象很深:javax.*包名换成了jakarta.*,Java 基线上限直接提到 17,很多第三方库也开始为 BC 兼容发布新版本。这些变化的后果就是,你在网上看到的很多“旧写法”在 3.x 里可能启动即失败。
这个问题在 Initializr 生成的项目里一开始并不会暴露,因为初版依赖是 Boot 官方帮你组合的。但只要你后续手动加依赖,就可能踩进版本冲突里。所以说,版本选择不只是一个“选最新”就可以交差的问题,它背后绑定着 Java、Jakarta、Spring Framework、第三方库四个维度。
5.2 我在生成项目后踩过的一个实际案例
某次我需要把一个数据库驱动加到生成的 Boot 3.2 项目里。当时的项目用 MySQL,我凭记忆搜索到了一个坐标:mysql:mysql-connector-java:8.0.x,加进pom.xml后项目启动时开始报错。日志里有大量类初始化失败的信息,第一眼看起来根本不知道问题出在哪。
后来我仔细看了pom.xml的依赖项,发现 Boot 3.x 里 MySQL 驱动的推荐坐标已经变了。新的坐标应该是com.mysql:mysql-connector-j,旧坐标虽然编译期不报错,但驱动类名和包结构跟新版本 Spring Boot 的加载机制不对付,导致后续一连串问题。
这个问题的解法其实很简单:先把旧坐标从pom.xml里清掉,然后利用 Initializr 的依赖目录重新确认当前 Boot 版本对应的驱动坐标,或者直接去依赖元数据里看这个版本默认管理的版本号。不要靠记忆写坐标,这是我反复提醒自己的原则。
5.3 检查依赖关系的一套固定动作
遇到依赖相关的诡异报错,我一般按这个顺序做:先看完整堆栈顶部,确认报错类属于哪个产物;再打开终端跑一次依赖树,看清它是哪个依赖带进来的。
./mvnw dependency:tree这条命令会列出所有依赖的版本,包括传递依赖。它能帮你回答一个关键问题:某个版本不满意的依赖,到底是直接引入的还是被别的东西间接带进来的。看到版本后,再用排除依赖或统一 manage 版本的方式处理,思路就会清晰很多。
5.4 别让本地方便破坏项目的可复现性
依赖问题还有一个隐性来源:本地 IDE 里有一些库缓存,或者手动安装的 jar 包。为了快速验证功能,有人会把一个 jar 直接加进项目路径,短期看是方便了,但其他同事 pull 下来根本无法构建。
生成项目时,Initializr 已经帮你管理了绝大多数依赖的版本。如果非要加第三方库,尽量使用 Maven 中央仓库存在的公开坐标,并交给 Boot 的 BOM 去管版本。如果必须指定版本,就写在<properties>里集中管理,这样至少可维护性高一些。
6. 把 Initializr 用熟之后的个人工作流
6.1 常量组合与模板化思维
用熟 Initializr 之后,我会把常用的依赖组合牢记于心,而不是每次临时搜索。后端 API 项目基本固定就是 Web、Validation、DevTools,数据分析写 JPA 再凭项目需求加数据库驱动。热度一上来,从打开网页到拿到 zip 几乎半分钟就能结束。
如果你嫌每次都手动勾选麻烦,也可以直接用 Initializr 提供的 URL 写请求参数。比如在浏览器地址栏里,你可以把依赖、包名、构建方式都拼进 URL,然后直接下载。这样你甚至可以给团队做一个固定的入口链接,让每个人拿到完全一致的生成结果。
6.2 拿到生成项目后的第一轮清理
生成的项目里带着.gitignore、.mvn、Wrapper 这些文件,其实可以直接进入版本管理。我在第一次提交代码前会做几件事:删除无用描述注释、确认.gitignore里包含target、确认本地 IDE 的配置目录没有被提交。多花这几分钟,可以避免后面出现一堆无意义的文件变更。
6.3 把 Initializr 当成起点而不是终点
Initializr 能给你一个漂亮的骨架,却没法替你把项目变复杂。当项目需要模块化拆分、多环境配置、安全策略时,还是要回到 spring Boot 内部机制来理解。我会把 Initializr 生成的代码当作“标准答案”之一,通过阅读它的结构来反推项目设计思路,这也是我快速熟悉一个新版本方式的最有效路径。
6.4 一个小习惯:把版本升级也当作重新生成
Spring Boot 小版本升级时,与其手动改pom.xml里的版本号,不如先在本地用 Initializr 生成一个相同配置的新项目,再对照差异,把需要的改动搬过来。这样能很清楚地看到新版本引入了哪些默认变化。这个方法在跨版本升级时尤其有效。个人经验是,升级过程中的大多数麻烦,都是因为“以为不变”的默认项发生了变化。
Spring Initializr 本身一点都不神秘,就是个替你兜底项目基础的脚手架。真正有价值的是在理解它生成内容的前提下,把后续的调整做顺。希望这篇文章能帮你从第一次创建项目开始就建立清晰的节奏,少一点不必要的折腾。