速读#
第一次看 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 讲「约定优于配置」:目录不用你声明,照约定摆。构建动作也是约定好的动词:
./mvnw package # 编译 + 单元测试 + 打包./mvnw verify # 再跑项目配置到 verify 阶段的检查与集成测试./mvnw spring-boot:run # 本地起服务package 不是孤立动作,它会依次经过 validate、compile、test、package 等生命周期阶段;只要测试没有被参数或插件配置跳过,前面失败,后面就不会执行。集成测试常由 Failsafe 等插件挂到 integration-test 和 verify,因此提交前我更偏向跑 verify。clean 属于另一条生命周期,会先删除 target,排查陈旧产物时有用,日常每次都加只会放弃增量构建,没有必要把它当仪式。
mvnw 是项目提交的 Maven Wrapper:脚本按 .mvn/wrapper 配置下载指定 Maven 发行版,机器无需预装 Maven。仓库还应提交并审阅 wrapper 文件,能配置校验和时就校验下载内容。它锁不住 JDK、操作系统、本地 Maven settings、远程仓库内容和外部服务;JDK 可以再用 Maven Toolchains、.java-version、CI 镜像或开发容器约束。工具锁了什么、没锁什么,说清楚才算数。
遇到“到底带进来什么、版本为什么是它”时,不靠猜:
./mvnw dependency:tree./mvnw help:effective-pom前者显示最终依赖图,后者显示继承 parent、profile 和插件配置后的有效 POM;输出很长,但按目标 artifact 搜索比翻教程快。
约定优于配置,省的是认路#
这句口号一开始当废话。后来想通它省的是决策成本:
没有约定 → 每个项目目录、配置名自己定 → 换项目重新认路。 有约定 → 任何 Maven 项目都知道去哪找代码、去哪找配置。
省的不是配置本身,是「重新认路」的认知开销。 这份开销平时不显眼,接手别人项目的第一天最显眼——目录结构能不能秒懂,决定那一天是在读代码,还是在猜结构。
和 FastAPI「接口定义即文档」同源:把不变的固定下来,人只管变的部分。
便利不是黑箱#
@SpringBootApplicationpublic 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 生态的「繁文缛节」多,但每一层往往在替你省某一类麻烦。看得见它在替我省什么,脚手架就不再是迷宫。
