我第一次用 IDEA 创建 SpringBoot 项目时,心里冒出一个很直接的问题:项目刚创建出来,一行业务代码都没写,为什么点击启动后,控制台马上出现“Tomcat started on port(s): 8080”,浏览器访问 localhost:8080 甚至能出现响应?是谁提前启动了 Tomcat?是谁配置好了 Spring MVC 的核心组件?如果这一切都要自己完成,到底要写多少配置和代码?
后来我才理解,SpringBoot 真正做的事,不是帮你“省掉几行配置”,而是把“把一个 Java Web 项目从零启动到可访问”这件事,从一堆依赖经验、依赖手工决策的重复劳动,压缩成了默认约定加按需覆盖。第一节最该建立的,不是注解清单,不是面试题答案,而是一个心智模型:SpringBoot 到底是谁、它替你做了什么、它为什么值得用。
这个心智模型一旦建立,后面无论是看自动配置源码、处理事务失效、排查循环依赖,还是把项目打包成镜像部署,都有了方向感。
1. SpringBoot 是什么:先搞清楚它不是替代品,而是一个“启动器 + 约定”
很多第一次接触 SpringBoot 的人,会把它理解成“一个新的 Web 框架”。这个理解不算全错,但会干扰后续学习。SpringBoot 没有重新发明一套 Web 开发模型,也没有替代 Spring Framework,它更像是给 Spring 生态装了一个“一键启动器”。
1.1 从“空项目为什么会响应 HTTP 请求”说起
当你创建一个 SpringBoot 项目,引入一个spring-boot-starter-web依赖,写完一个带@SpringBootApplication的启动类,点击运行后,一个内嵌的 Tomcat 就被启动起来了。你甚至不需要去下载 Tomcat 解压包,不需要把它装到 IDE 里,不需要在 web.xml 里配置DispatcherServlet。
对一个刚从 SSM 学习阶段过来的人来说,这几乎像变魔术。但魔术的真相是:SpringBoot 不是没有做这些配置,而是在你看不到的地方,用“自动配置”帮你做了。
它读到了类路径下有spring-webmvc相关类,于是判定“这是一个 Spring MVC 项目”,自动创建了DispatcherServlet;它发现有内嵌 Tomcat 的存在,于是自动启动了 Web 容器。这些动作不是通过你写代码完成的,而是通过一堆条件判断自动装配出来的。
1.2 SpringBoot 与 Spring Framework 的边界
这里需要先把边界说清楚:Spring Framework 是核心,它提供 IoC 容器、AOP、事务抽象、Spring MVC 等基础能力。SpringBoot 不是要替换这些,而是在 Spring Framework 之上,提供一套更快的组装方式。
可以这样理解:Spring Framework 就像一套积木,它提供了零件和接口;SpringBoot 则是预制好的模块组合说明书外加一个自动装配机器人。你依然是在用积木,但不再需要每次从零决定“这个功能需要哪几个零件”“它们怎么拼”。
所以面试里如果问“SpringBoot 和 Spring 是什么关系”,重点不是背定义,而是说清楚:SpringBoot 基于 Spring Framework,它通过自动配置和 starter 依赖,降低了 Spring 项目的搭建成本,但运行时底层仍然是 Spring 容器在发挥作用。
1.3 它真正降低的是决策成本
很多人以为 SpringBoot 的价值是“快”。其实快只是结果,真正被改变的是决策成本。
以前搭一个 Spring MVC 项目,你要决定:
- 用哪个版本的 Spring?
- 用哪个版本的 Tomcat?
- 用哪个 JSON 库?
- 用哪个连接池?
- 各种库之间的版本是否兼容?
- 配置写在 XML 还是注解里?
这些决策每个都不难,但组合起来,就是经验门槛。SpringBoot 通过 starter 和默认配置,把这些决策变成了“大多数场景下一个合理解即可”。这带来的价值,不只是省了几分钟,而是让一个新人也能在短时间内把项目跑起来,让一个团队可以用相对统一的方式管理项目基线。
2. 没有 SpringBoot 之前,“启动一个 Web 项目”需要做哪些事
如果想要真正理解 SpringBoot 做了什么,最好的办法是回到没有它的年代,看看那时启动一个 Web 项目有多麻烦。
2.1 从 web.xml 到 Spring MVC 的“标准动作清单”
以传统的 Servlet 项目为例,当时要写的东西包括:
- 创建 Maven 工程,手动引入
servlet-api、spring-webmvc、jackson-databind、logback等依赖,而且要自己处理版本冲突。 - 编写
web.xml,在里面配置ContextLoaderListener,让 Spring 容器随 Web 应用启动。 - 配置
DispatcherServlet,指定它拦截哪些请求路径。 - 配置 Spring 的 XML 文件:
applicationContext.xml、spring-mvc.xml,以及后来的WebMvcConfigurer实现类。 - 如果需要数据库,要引入连接池驱动、配置数据源、配置事务管理器。
- 如果接 MyBatis,要写
SqlSessionFactoryBean,扫描 Mapper 接口和 XML。 - 最后把项目打包成 war 包,扔进外部 Tomcat 的 webapps 目录里,再重启 Tomcat。
这个过程不是说不能完成,而是每一步都要亲手确认。任何一个第三方库接入,都意味着你要重新理解一遍“它在 Spring 里应该怎么被管理”。这也是为什么以前一个 Web 项目从零到能正常开发,往往要先搭一两天环境。
2.2 每接一个中间件,都要写一层“胶水代码”
如果只做一个简单接口,手工配置还勉强可以接受。一旦加入 Redis、消息队列、定时任务、权限框架,事情就开始失控。
每个中间件都要先找到它的 Spring 整合包,再写一个配置类或者 XML 配置,把它的客户端、连接工厂、模板类注册进 Spring 容器。这些代码本身难度不高,但属于典型的“没有增量价值的工作”——你把它们写一百遍,也不会提升业务能力,只会在第两百遍时感到厌倦。
SpringBoot 的starter就是针对这个问题出现的。它把“接入一个组件”的依赖组合、默认配置、自动装配类打包成一个坐标。你只需要引入一个依赖,SpringBoot 在启动时会检查类路径里的条件,决定要不要装配对应的组件。
2.3 SpringBoot 在动手之前,先替你做完了哪些事
用一个类比来理解自动配置:
你搬进一间新办公室,里面已经装好了桌椅、空调、照明和网口。你不需要知道电是怎么接进来的、网线是从哪个交换机拉过来的,你只需要插上电脑就能办公。等你有了更高要求,比如想加一个工位,再去找管理员申请。
SpringBoot 做的事情,就是装修公司和管理员的结合体:它预先铺好了水电,同时也允许你提出改动需求。
所以你会发现,很多配置不写也能跑。不是因为配置不存在,而是在你还没有写之前,SpringBoot 已经用默认值把它准备好了。等你写了自定义配置,它又会通过条件判断,优先采用你的配置。
3. SpringBoot 真正替你做的三件事:依赖、装配、配置
把 SpringBoot 的能力拆开看,核心就是三件事:依赖管理、自动装配、集中配置。理解了这三件事,第一节的目标就完成一半了。
3.1 starter:把“依赖组合”变成“声明式坐标”
starter是 SpringBoot 提供的一种依赖聚合方式。你不需要一个个去挑选具体依赖版本,只需要引入一个starter,它会帮你把一组相关依赖带进来。
| 常见 starter | 核心作用 | 典型场景 |
|---|---|---|
spring-boot-starter-web | 引入 Spring MVC、内嵌 Tomcat、Jackson 等 | Web API 项目 |
spring-boot-starter-data-redis | 引入 Redis 客户端和 Spring Data Redis | 缓存、分布式锁 |
spring-boot-starter-test | 引入 JUnit、Spring Test、AssertJ 等 | 单元测试、集成测试 |
mybatis-spring-boot-starter | 引入 MyBatis 与 Spring Boot 整合 | 使用 MyBatis 访问数据库 |
spring-boot-starter-security | 引入 Spring Security | 认证与授权 |
需要注意的是,引入starter不等于所有功能都可用。它只是把“依赖和默认装配”带进来了,具体行为还要看类路径和配置。如果某个功能的自动配置和你预期的行为不一致,优先去查对应xxxAutoConfiguration类,而不是怀疑项目坏了。
3.2 自动配置:核心机制是条件装配
自动配置听起来玄,实际底层就是条件判断。
SpringBoot 在启动时,会扫描spring-boot-autoconfigure包里的配置类。这些配置类不是无条件执行的。它们上面通常会有一堆条件注解,比如@ConditionalOnClass:只有当类路径里存在某个类时才装配;@ConditionalOnMissingBean:只有当容器里还没有某个 Bean 时才装配。
这种方式带来一个关键好处:你可以用自己的 Bean 覆盖默认行为。比如 SpringBoot 自动配置了一个数据源,但你在自己的配置类里定义了一个更符合项目需求的数据源,自动配置的@ConditionalOnMissingBean就会放弃执行。
SpringBoot 2.7 之前的自动配置描述文件通常叫spring.factories,之后的版本使用META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports。不同版本路径有差异,但不影响理解基本原理:SpringBoot 扫描到这些描述文件,加载里面声明的自动配置类,再通过条件注解决定哪些生效。
很多人背了“自动装配原理”面试题,却不知道它对自己的意义。实际开发里,你完全不用把所有自动配置类都记住。但当你遇到“我明明没写这个 Bean,为什么它存在”“为什么我自定义配置不生效”这类问题时,知道去自动配置类里找条件注解,才是这个知识点的最大价值。
3.3 集中配置:从 web.xml 散落各处到 application.yml
传统项目里,配置是分散的:web.xml 里配置 Servlet,Spring XML 里配置数据源和事务,日志配置单独一个文件,端口配置可能还要改 Tomcat 的 server.xml。
SpringBoot 把这些集中到了一个位置:application.yml或application.properties。
常见的配置内容包括:
server: port: 8080 servlet: context-path: /demo spring: application: name: example-service datasource: url: jdbc:mysql://localhost:3306/demo username: root password: 123456 redis: host: localhost port: 6379配置项很多,但启动日志里也能看到最终生效的配置组合。遇到“为什么我改了端口没生效”“为什么配置没起作用”时,先去确认配置文件的加载顺序、是否有context-path影响路径、是否命中了正确前缀。
另外提一个有趣的点:SpringBoot 启动时那个 ASCII 字符画叫 banner,它是可以自定义的。把自定义内容放到 resources 目录下,然后在配置里指定spring.banner.location即可。网上有生成器可以做这种小彩蛋,但不建议在上面花太多时间。
4. 第一节的实操:跑通第一个 SpringBoot 项目
知道了理论,还是要亲手跑一次。第一节的实操目标很简单:创建一个最小的 SpringBoot 项目,写一个接口,成功访问。
4.1 环境准备与版本选择
常见开发环境组合是 JDK、Maven、IDE。在创建项目前,先确认本机环境。
从版本兼容角度看,SpringBoot 2.7.x 对应 Java 8 是比较稳妥的组合;如果你的项目基线是 Java 17,那么 SpringBoot 3.x 更合适。SpringBoot 3.x 之后的包名从javax迁移到了jakarta,这会导致很多旧代码在新版本中报编译错误。
所以“SpringBoot 版本太高”不是错觉。它通常意味着项目使用了旧 JDK、旧依赖、旧 API,却强行选择了新版本框架。
注意:如果本机是 JDK 1.8,不建议因为追新而创建 SpringBoot 3.x 项目。先选能稳定运行的版本组合,比选最新版本重要得多。
4.2 创建最小项目并理解目录
创建项目时,可以用 IDEA 的 Spring Initializr,也可以直接访问 Spring Initializr 网站生成压缩包。选择 Java 版本、Maven 构建、SpringBoot 版本,依赖里先只勾选Spring Web。
生成后的项目核心结构:
src/main/java/com/example/demo/ DemoApplication.java src/main/resources/ application.properties 或 application.yml static/ templates/ src/test/java/com/example/demo/ DemoApplicationTests.javaDemoApplication.java是启动类,内容大概是:
package com.example.demo; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication public class DemoApplication { public static void main(String[] args) { SpringApplication.run(DemoApplication.class, args); } }@SpringBootApplication相当于开启了自动配置和组件扫描。启动类的包位置很重要,它决定了组件扫描的根路径。自定义的 Controller、Service 要放在这个包或其子包下,否则扫不到。
4.3 写第一个接口并观察启动日志
在启动类同级或子包下创建 Controller:
package com.example.demo.controller; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; @RestController public class HelloController { @GetMapping("/hello") public String hello() { return "Hello SpringBoot"; } }然后启动项目。启动成功后,日志里会有关键信息:
Tomcat started on port(s): 8080 (http) Started DemoApplication in 1.234 seconds (process running for 1.345)浏览器访问:
http://localhost:8080/hello能看到Hello SpringBoot,说明最小项目跑通了。
注意,这里的“跑通”只是第一步。它能启动,只代表流程没有断;真正理解它,还得知道为什么这个请求能到达hello()方法。这背后是 Spring MVC 的前端控制器、HandlerMapping、参数解析、响应转换等一系列机制在协作。
5. 新手最容易踩的坑,以及一套排查顺序
SpringBoot 降低了入门门槛,但也带来一个副作用:有些人只学会了“能跑”,没有建立起“出了问题怎么排查”的思路。下面几个坑是新手阶段最常见的。
5.1 版本问题:不是越新越好
“SpringBoot 版本太高导致 AOP 失效”“找不到javax包里的类”“MyBatis 整合不了”这类问题,网上经常出现。
这里面大部分不是 SpringBoot 缺陷,而是版本基线不匹配。SpringBoot 3.x 要求 JDK 17 及以上;如果你的项目仍然使用 JDK 8,则适合 SpringBoot 2.7.x。另外,SpringBoot 4.0 如果出现在新版本里,对旧项目来说往往意味着更大的 API 变动。遇到找不到类、切面不生效、依赖爆红时,第一反应不应该是怀疑某个包有问题,而是检查 JDK、SpringBoot、第三方 starter 三者的兼容矩阵。
处理方式建议是:先确定一个稳定的基线版本,再按这个基线去找配套依赖。不要只升级 SpringBoot 而不同步检查第三方 starter 版本。
5.2 IDEA 中 application.yml 不提示,通常不是配置问题
很多新手遇到「IDEA 里写server.port没有提示」的情况,第一反应是配置文件有问题。其实大概率是以下原因:
- Maven 依赖还没导入完整,导致 IDEA 没有识别到 Spring Boot 的配置元数据。
- IDEA 缓存异常,导致提示失效。
- 项目里根本没有引入 SpringBoot 相关依赖。
处理顺序:
- 检查 Maven 依赖是否报红。
mvn clean compile或者让 IDEA 重新刷新 Maven 项目。 - 确认
spring-boot-configuration-processor是否被引入。它不是必需,但能提升 IDE 的配置提示能力。 - 如果还是不提示,尝试 IDEA 的 Invalidate Caches 并重启。
- 最后再检查项目结构是否是标准 Maven 项目。
这不属于复杂问题,但经常会消耗大量时间。先按这个顺序排查,比反复重装配置有效。
5.3 启动成功但访问 404 / 连不上,按这个顺序排查
这是 Web 开发里出现频率最高的场景。项目日志显示启动成功,但浏览器就是访问不到。不要一上来就怀疑自动配置有问题,按下面这个顺序排查:
- 看启动日志里到底有没有
Tomcat started on port(s):如果没有,说明项目可能不是以 Web 应用方式启动,或者在启动过程中切换成了非 Web 模式。 - 看端口:确认访问的端口是不是启动日志里显示的端口。如果配置了
server.port,要看application.yml是否真的生效。 - 看路径:如果设置了
context-path,访问路径必须加上对应前缀。 - 看 Controller 有没有被扫描到:Controller 是否在启动类所在包的子包下?类上是否有
@RestController或@Controller?方法映射是否写对了? - 看静态资源或页面目录:如果访问的是页面而不是接口,检查文件是否放在
resources/static下。 - 看异常日志:有些 Bean 初始化失败,但应用仍然继续运行。要往日志下方找
Error creating bean with name或APPLICATION FAILED TO START这类关键信息。
排查顺序不要反过来。很多人直接跳到“自动配置是不是有问题”,结果折腾半天,最后发现只是 context-path 没算上。
6. 第一节之后,按什么顺序继续学
SpringBoot 的知识面很广,从starter、自动配置,到单元测试、事务、缓存、消息队列、部署,再到微服务。如果第一节学完就急着背注解大全,很容易变成“看的时候都懂,做项目时全都忘”。更合适的方式,是分阶段推进。
6.1 三阶段路径:跑通、理解、工程化
第一阶段,目标是跑通最小项目。会创建项目、理解启动类、写一个接口、修改端口、打包运行。这个阶段不需要深挖自动配置源码,能稳定运行即可。
第二阶段,目标是理解机制。重点看 starter 和自动配置怎么生效,配置文件的优先级,如何用自定义 Bean 覆盖默认行为。这个阶段适合看 SpringBoot 官方文档里关于“Auto-configuration”的部分。
第三阶段,目标是工程化。开始关注单元测试、日志体系、异常处理、事务边界、过滤器与拦截器、接口鉴权、部署方式。到这个阶段,你自然会遇到SpringBoot 事务失效场景、循环依赖、资源映射、大文件上传下载这些更具体的问题,再针对性地逐个解决。
需要说明的是,第三阶段没有终点。技术会遇到业务,业务会遇到边界。SpringBoot 本身只是个启动器,工程化能力要靠项目实践来打磨。
6.2 什么时候值得深入“自动装配源码”
不是所有人一开始都需要读自动装配的源码。读源码的前提是,你已经无法通过配置解决问题,或者你很好奇一个具体行为是怎么发生的。
常见的信号包括:
- 你配置了一个数据源,但系统启动后还是用了自动配置的那个。
- 你想排除某个自动配置类,不知道该看哪个条件注解。
- 你引入了自定义组件,但容器里没有出现预期的 Bean。
- 你想写一个自己的 starter 给团队内部复用。
当这些问题出现时,再去打开自动配置类的源码,你会非常明确自己要找什么。否则,只是把源码读一遍,很快就会忘掉。
6.3 SpringBoot 的适用边界:它解决什么,不解决什么
最后也要把边界说清楚。SpringBoot 适合大多数常见的 Web 应用场景,尤其在中小型项目和微服务基础架构里,它让团队能够快速启动、统一维护、低成本接入常用组件。
但它不解决一切问题:
- 如果项目对性能有极端要求,需要自己控制 IO 线程模型和容器行为,SpringBoot 的默认封装反而会成为瓶颈。
- 如果团队已经有一套成熟的手工配置体系,强行改成自动配置未必能带来直接收益,反而会造成不必要的黑盒。
- 当项目依赖大量第三方组件时,starter 之间的版本兼容会成为一个持续的维护问题。
自动配置的另外一面是“隐藏复杂性”。它让你不用关心细节,但也意味着当细节出错时,你需要花更多时间找到问题来源。所以,使用 SpringBoot 的同时,保持对底层 Spring 机制的持续理解,并不是浪费。
第一节结束之后,建议你不要急着看更多花哨的进阶内容。先把你电脑上的项目重新创建一遍,用排除法试一试:去掉依赖会怎样,改配置会怎样,把 Controller 移到启动类包外面会怎样。这些尝试会让你更清楚地触摸到 SpringBoot 替你做的那些事。
SpringBoot 的第一课,不是背 API,而是建立一种判断力:知道哪些东西是框架替你默认做好的,知道去哪里找突破口,知道什么时候该相信默认配置,什么时候该亲手改掉它。带着这个判断力继续往下学,后面的路会顺很多。