跳到正文
看懂一个 Spring Boot 项目的骨架:从「文件夹一堆」到「两样东西」
看懂一个 Spring Boot 项目的骨架:从「文件夹一堆」到「两样东西」

看懂一个 Spring Boot 项目的骨架:从「文件夹一堆」到「两样东西」

第一次打开 Spring Boot 项目,文件夹一堆不知道从哪看起。这篇不讲 Maven 大全,讲我怎么从被结构吓到,到抓住 pom.xml 和标准目录,以及「约定优于配置」到底在替我省什么。

速读#

第一次看 Spring Boot 项目会被目录吓到,但先抓两样就够:pom.xml 和 Maven 标准目录。“约定优于配置”省的不是几行 XML,而是每换一个项目都重新认路的成本;starter 负责聚合常用依赖,Spring Boot 的 parent 或 BOM 负责管理兼容版本,两件事不要混成一件。

抓两样:pom.xml 和标准目录#

pom.xml:项目说明书#

pom.xml 管项目坐标、依赖、插件、属性和构建。最常打交道的是 <dependencies>。Spring Boot 的 starter 是关键设计:一个 Web starter 会带入一组常用依赖,例如 Spring MVC、JSON 支持和默认内嵌服务器,具体组成随 Spring Boot 大版本可能变化,不应靠记忆把传递依赖当永久 API。

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>

注意这段没有 <version>。常见项目继承 spring-boot-starter-parent,由它导入的 dependency management 管理一组经过 Spring Boot 测试的版本;不能继承 parent 的企业项目,也可以在 dependencyManagement 中导入 spring-boot-dependencies BOM。Maven 仍按自己的依赖调解规则解析图,项目也能显式覆盖版本,所以这不是不可突破的锁,而是一组默认兼容基线。没有明确漏洞、功能或兼容需求时,我不随手覆盖其中单个库,避免拆散这组基线。

这里不能拿 starter 和 requirements.txt 直接一一对照。starter 解决的是“为某类能力聚合哪些依赖”,parent/BOM 解决的是“这些依赖默认用什么版本”,而 Python 的 requirements.txt 既可能只是几条宽松顶层依赖,也可能是 pip-tools、uv 等工具生成的完整锁定结果。三个维度分别是依赖选择、版本管理和可重复安装;分开看,才能知道环境漂移到底还剩在哪一层。

标准目录:照摆就行#

路径放什么
src/main/java源码
src/main/resources配置、模板和静态资源等 classpath 资源
src/test/java测试源码
src/test/resources测试专用配置与样本资源

Maven 讲「约定优于配置」:目录不用你声明,照约定摆。构建动作也是约定好的动词:

Terminal window
./mvnw package # 编译 + 单元测试 + 打包
./mvnw verify # 再跑项目配置到 verify 阶段的检查与集成测试
./mvnw spring-boot:run # 本地起服务

package 不是孤立动作,它会依次经过 validate、compile、test、package 等生命周期阶段;只要测试没有被参数或插件配置跳过,前面失败,后面就不会执行。集成测试常由 Failsafe 等插件挂到 integration-testverify,因此提交前我更偏向跑 verifyclean 属于另一条生命周期,会先删除 target,排查陈旧产物时有用,日常每次都加只会放弃增量构建,没有必要把它当仪式。

mvnw 是项目提交的 Maven Wrapper:脚本按 .mvn/wrapper 配置下载指定 Maven 发行版,机器无需预装 Maven。仓库还应提交并审阅 wrapper 文件,能配置校验和时就校验下载内容。它锁不住 JDK、操作系统、本地 Maven settings、远程仓库内容和外部服务;JDK 可以再用 Maven Toolchains、.java-version、CI 镜像或开发容器约束。工具锁了什么、没锁什么,说清楚才算数。

遇到“到底带进来什么、版本为什么是它”时,不靠猜:

Terminal window
./mvnw dependency:tree
./mvnw help:effective-pom

前者显示最终依赖图,后者显示继承 parent、profile 和插件配置后的有效 POM;输出很长,但按目标 artifact 搜索比翻教程快。

约定优于配置,省的是认路#

这句口号一开始当废话。后来想通它省的是决策成本

没有约定 → 每个项目目录、配置名自己定 → 换项目重新认路。 有约定 → 任何 Maven 项目都知道去哪找代码、去哪找配置。

省的不是配置本身,是「重新认路」的认知开销。 这份开销平时不显眼,接手别人项目的第一天最显眼——目录结构能不能秒懂,决定那一天是在读代码,还是在猜结构。

和 FastAPI「接口定义即文档」同源:把不变的固定下来,人只管变的部分。

便利不是黑箱#

@SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}

@SpringBootApplication 组合了 @SpringBootConfiguration@EnableAutoConfiguration@ComponentScan,对应配置类、自动配置和组件扫描。自动配置也不只是“classpath 有什么就配什么”:它还会检查配置属性、Web 应用类型,以及用户是否已经声明某个 Bean,常见条件包括 @ConditionalOnClass@ConditionalOnProperty@ConditionalOnMissingBean。引入 starter 提供候选能力,项目配置与已有 Bean 决定哪些默认装配真正生效;自己明确配置后,很多默认项会退让。

边界要清醒:便利不等于无法观察。 dependency:tree 能看依赖,启动时加 --debug 能看到条件评估报告,Actuator 开启相应端点后也能查看自动配置条件。我的底线不是背下 starter 的全部传递依赖,而是出现冲突时知道用这些入口回答“谁引入了它、哪个条件生效、我的 Bean 为什么没有接管”。这比背一张随版本过期的清单可靠。

Java 生态的「繁文缛节」多,但每一层往往在替你省某一类麻烦。看得见它在替我省什么,脚手架就不再是迷宫。

版权许可

CC BY-NC-SA 4.0 本作品采用知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议进行许可。

相关文章

s1oopX

登录 s1oopX