速读#
第一次看 Spring Boot 项目会被目录吓到,但真正先抓两样就够:pom.xml 和标准目录。「约定优于配置」省的不是几行配置,而是每换一个项目都重新认路的成本;starter 则把一类问题的依赖全家桶收成一行,版本搭配由父 POM 仲裁。
抓两样:pom.xml 和标准目录#
pom.xml:项目说明书#
管依赖和构建。最常打交道的是 <dependencies>。Spring Boot 的 starter 是关键设计——一个 starter-web 把 Web 全家桶(Tomcat、JSON、MVC)带进来,不用自己拼版本。
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId></dependency>注意这段里没有 <version>。版本统一由 spring-boot-starter-parent 仲裁——我声明「要什么」,父 POM 管「这组东西按哪些版本搭配是验证过兼容的」。我第一次想手动指定某个库的版本时才意识到:大多数时候这件事根本不该我管,乱管反而把验证过的搭配拆散。
对比 Python 手动 pip install 一堆零散包、还可能版本打架:starter 是把「这一类问题需要的全家桶」收成依赖级的一行。差别不在锁得多死——requirements.txt 也能把版本钉到小数点后——而在有没有人替你验证过搭配:requirements 锁的是「我装过哪些」,starter + parent 锁的是「这一组按什么版本搭配是验证过能协同工作的」。前者是快照,后者是背书。
标准目录:照摆就行#
| 路径 | 放什么 |
|---|---|
src/main/java | 源码 |
src/main/resources | 配置 |
src/test | 测试 |
Maven 讲「约定优于配置」:目录不用你声明,照约定摆。构建动作也是约定好的动词:
./mvnw clean package # 清理 + 编译 + 测试 + 打包./mvnw spring-boot:run # 本地起服务package 不是孤立的一步,是把校验、编译、测试一路跑到打包的生命周期——前面的阶段没过,后面的不会发生。测试挂了就打不出包,这个顺序本身就是一道质量门,不用我记「先跑测试再打包」。
mvnw 是项目自带的 Maven wrapper:机器上没装 Maven 也能构建,版本由项目锁定。「换台机器构建结果不一样」这类环境漂移,它先替你挡掉一半——挡不掉的另一半是 JDK 版本,wrapper 不管这个,得靠项目文档或容器把它也钉住。工具锁了什么、没锁什么,说清楚才算数。
约定优于配置,省的是认路#
这句口号一开始当废话。后来想通它省的是决策成本:
没有约定 → 每个项目目录、配置名自己定 → 换项目重新认路。 有约定 → 任何 Maven 项目都知道去哪找代码、去哪找配置。
省的不是配置本身,是「重新认路」的认知开销。 这份开销平时不显眼,接手别人项目的第一天最显眼——目录结构能不能秒懂,决定那一天是在读代码,还是在猜结构。
和 FastAPI「接口定义即文档」同源:把不变的固定下来,人只管变的部分。
便利不是黑箱#
@SpringBootApplicationpublic class DemoApplication { public static void main(String[] args) { SpringApplication.run(DemoApplication.class, args); }}一个注解顶三个:自动配置、组件扫描、配置类声明。开箱即用很大程度靠它。其中「自动配置」也不是魔法,机制说穿了很朴素:看 classpath 上有什么,就配什么——见到 starter-web 带进来的 Tomcat 和 Spring MVC,就把内嵌容器和请求分发装配好;换一组依赖,装配随之改变。我引了什么依赖,就是在告诉它配什么,这条因果链是可以追的。
边界要清醒:便利 ≠ 黑箱。 它替我装配了什么,至少要知道个大概,不然出问题无从下手——别落成「能跑就不求甚解」。我给自己的底线是:每引入一个 starter,至少说得出它带进来哪几样大件(starter-web = 内嵌 Tomcat + Spring MVC + Jackson),这样出问题才知道往哪一层看。
Java 生态的「繁文缛节」多,但每一层往往在替你省某一类麻烦。看得见它在替我省什么,脚手架就不再是迷宫。
