前端开发里有一行极其普通的代码:
const todos = await axios.get('/api/todos')
它普通到我们经常忘记追问:/api/todos 的另一端到底是什么?
浏览器不会凭空变出数据。那一端有一个持续运行的程序,它收到请求,确认地址,寻找数据,处理异常,最后把结果包装成 JSON。Spring Boot,就是 Java 世界里用来构建这类程序的主流方式之一。
Spring Boot 不是“会返回 JSON 的 Java”
如果把一个网站看成一家线上商店,前端负责橱窗、货架和用户操作,后端负责库存、订单、规则和权限。Spring Boot 不是其中某一个业务部门,而是一整套让后端部门可以顺畅工作的基础设施。
它会帮你处理很多重复工作:
- 启动一个 HTTP 服务;
- 根据 URL 把请求交给对应方法;
- 把 JSON 转换成 Java 对象;
- 管理数据库连接和应用配置;
- 创建并组装业务对象;
- 接入日志、测试、安全和监控。
因此,Spring Boot 更像“带完整工程能力的后端底座”。它不替你决定待办能不能删除,却让你不必从网络端口和 JSON 解析开始造工具。
#mermaid-svg-KLUoYBlbtj5aLRDx{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-KLUoYBlbtj5aLRDx .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-KLUoYBlbtj5aLRDx .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-KLUoYBlbtj5aLRDx .error-icon{fill:#552222;}#mermaid-svg-KLUoYBlbtj5aLRDx .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-KLUoYBlbtj5aLRDx .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-KLUoYBlbtj5aLRDx .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-KLUoYBlbtj5aLRDx .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-KLUoYBlbtj5aLRDx .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-KLUoYBlbtj5aLRDx .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-KLUoYBlbtj5aLRDx .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-KLUoYBlbtj5aLRDx .marker{fill:#333333;stroke:#333333;}#mermaid-svg-KLUoYBlbtj5aLRDx .marker.cross{stroke:#333333;}#mermaid-svg-KLUoYBlbtj5aLRDx svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-KLUoYBlbtj5aLRDx p{margin:0;}#mermaid-svg-KLUoYBlbtj5aLRDx .label{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;color:#333;}#mermaid-svg-KLUoYBlbtj5aLRDx .cluster-label text{fill:#333;}#mermaid-svg-KLUoYBlbtj5aLRDx .cluster-label span{color:#333;}#mermaid-svg-KLUoYBlbtj5aLRDx .cluster-label span p{background-color:transparent;}#mermaid-svg-KLUoYBlbtj5aLRDx .label text,#mermaid-svg-KLUoYBlbtj5aLRDx span{fill:#333;color:#333;}#mermaid-svg-KLUoYBlbtj5aLRDx .node rect,#mermaid-svg-KLUoYBlbtj5aLRDx .node circle,#mermaid-svg-KLUoYBlbtj5aLRDx .node ellipse,#mermaid-svg-KLUoYBlbtj5aLRDx .node polygon,#mermaid-svg-KLUoYBlbtj5aLRDx .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-KLUoYBlbtj5aLRDx .rough-node .label text,#mermaid-svg-KLUoYBlbtj5aLRDx .node .label text,#mermaid-svg-KLUoYBlbtj5aLRDx .image-shape .label,#mermaid-svg-KLUoYBlbtj5aLRDx .icon-shape .label{text-anchor:middle;}#mermaid-svg-KLUoYBlbtj5aLRDx .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-KLUoYBlbtj5aLRDx .rough-node .label,#mermaid-svg-KLUoYBlbtj5aLRDx .node .label,#mermaid-svg-KLUoYBlbtj5aLRDx .image-shape .label,#mermaid-svg-KLUoYBlbtj5aLRDx .icon-shape .label{text-align:center;}#mermaid-svg-KLUoYBlbtj5aLRDx .node.clickable{cursor:pointer;}#mermaid-svg-KLUoYBlbtj5aLRDx .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-KLUoYBlbtj5aLRDx .arrowheadPath{fill:#333333;}#mermaid-svg-KLUoYBlbtj5aLRDx .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-KLUoYBlbtj5aLRDx .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-KLUoYBlbtj5aLRDx .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-KLUoYBlbtj5aLRDx .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-KLUoYBlbtj5aLRDx .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-KLUoYBlbtj5aLRDx .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-KLUoYBlbtj5aLRDx .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-KLUoYBlbtj5aLRDx .cluster text{fill:#333;}#mermaid-svg-KLUoYBlbtj5aLRDx .cluster span{color:#333;}#mermaid-svg-KLUoYBlbtj5aLRDx div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-KLUoYBlbtj5aLRDx .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-KLUoYBlbtj5aLRDx rect.text{fill:none;stroke-width:0;}#mermaid-svg-KLUoYBlbtj5aLRDx .icon-shape,#mermaid-svg-KLUoYBlbtj5aLRDx .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-KLUoYBlbtj5aLRDx .icon-shape p,#mermaid-svg-KLUoYBlbtj5aLRDx .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-KLUoYBlbtj5aLRDx .icon-shape .label rect,#mermaid-svg-KLUoYBlbtj5aLRDx .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-KLUoYBlbtj5aLRDx .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-KLUoYBlbtj5aLRDx .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-KLUoYBlbtj5aLRDx :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}
HTTP 请求
JSON 响应
Vue / React
Spring Boot
业务规则
MySQL
登录与权限
JDK、JVM、Java:名字很像,身份不同
前端开发者刚进 Java 世界,最容易先被一串缩写拦住。
Java 是编程语言,类似 JavaScript 或 TypeScript。
JVM 是 Java 虚拟机,负责运行编译后的 Java 字节码。它有点像 JavaScript 引擎,但 JVM 不依附浏览器,而且承担了内存管理、垃圾回收、线程调度等大量运行时工作。
JDK 是开发工具包,里面包含编译器、运行工具和常用命令。可以把它粗略类比成 Node.js 运行时加上一套完整开发工具。
我们使用 Java 21,是因为它是长期支持版本,语法和生态都足够成熟。Spring Boot 3.5 建立在现代 Java 之上,后续代码也会使用 record 等更简洁的写法。
Maven,就是 Java 项目的“包管理加构建系统”
前端项目有 package.json,Spring Boot 的 Maven 项目通常有 pom.xml。
两者都描述项目名称、版本和依赖,但 Maven 还深度参与编译、测试、打包等过程。你会经常见到这些对应关系:
| Node.js | JDK |
| npm / pnpm | Maven |
| package.json | pom.xml |
| src/main.ts | Java 启动类 |
| .env | application.yml + 环境变量 |
| npm run dev | ./mvnw spring-boot:run |
| 构建产物 dist/ | 可运行的 JAR |
Spring Boot 项目常使用 starter 依赖。例如 spring-boot-starter-web 不是一个孤零零的库,而是一组已经配好兼容版本的 Web 能力。它像一套官方维护的技术套餐:HTTP 服务、JSON 转换、参数绑定等常用组件一次到位。
为什么 Spring Boot 不需要单独安装 Tomcat
传统 Java Web 项目经常要把应用交给外部服务器运行。Spring Boot 改变了这种体验:Tomcat 可以直接作为依赖装进应用。
于是,一个 Spring Boot 项目打包后就是可运行程序:
java -jar todo-api.jar
这件事对部署影响很大。应用不再依赖服务器上某个神秘的 Tomcat 配置,而是自己带着运行所需的 Web 容器。不同环境运行的是同一个产物,出错空间更小。
你可以把它理解成:不是“把代码上传到一台特殊服务器”,而是“带着服务器一起发布应用”。
启动类为什么只有几行
一个典型入口长这样:
@SpringBootApplication
public class TodoApplication {
public static void main(String[] args) {
SpringApplication.run(TodoApplication.class, args);
}
}
main 是 Java 程序入口,角色接近前端的 main.ts。
真正值得注意的是 @SpringBootApplication。它不是给人看的注释,而是框架会读取的元数据。它大致告诉 Spring:
“自动配置”是 Spring Boot 的核心体验。项目里出现 Web 依赖,它就准备 Web 环境;出现数据库依赖和连接配置,它就准备数据源。自动配置不是猜测一切,而是按照明确条件提供合理默认值,你仍然可以覆盖它。
第一个接口,其实只做了三次翻译
下面是一个最小接口:
@RestController
@RequestMapping("/api/todos")
class TodoController {
@GetMapping
List<TodoResponse> list() {
return List.of(new TodoResponse(1L, "认识 Spring Boot", false));
}
}
record TodoResponse(Long id, String title, boolean completed) {}
它看起来有不少陌生符号,本质上只发生了三次翻译:
第一次,URL 翻译成 Java 方法。
@RequestMapping("/api/todos") 加上 @GetMapping,表示 GET 请求到达 /api/todos 时执行 list()。
第二次,Java 返回值翻译成 JSON。
方法返回的是 List<TodoResponse>,Spring Boot 内置的 Jackson 会把它转换成 JSON。你不需要手动 JSON.stringify。
第三次,HTTP 响应翻译成前端对象。
Axios 收到 JSON,再把它放进 response.data。前端看到的是普通对象数组,不需要知道 Java 类长什么样。
最终响应是:
[
{ "id": 1, "title": "认识 Spring Boot", "completed": false }
]
record 为什么特别适合接口数据
TypeScript 的 interface 只存在于编译阶段,Java 的 record 是真实的运行时类型。它适合表达“这一组数据放在一起传递”,并自动提供构造方法和读取方法。
record TodoResponse(Long id, String title, boolean completed) {}
可以近似理解为:一份具有运行时身份、默认不可随意修改的 TypeScript 数据结构。
接口输入和输出会大量使用 record,因为它简洁,而且能明确告诉读者:这个对象主要用于传输数据,不负责复杂业务行为。
配置为什么不应该写死在代码里
应用端口、数据库地址、日志级别会随环境变化。如果写进 Java 代码,每换一个环境都要重新编译。
Spring Boot 通常把默认配置放在 application.yml:
server:
port: 8080
spring:
application:
name: todo–api
敏感值则来自环境变量:
spring:
datasource:
password: ${DB_PASSWORD}
这和 Vite 中使用环境变量的思路相似:代码描述“需要什么配置”,部署环境提供“这次使用什么值”。区别是后端配置经常包含数据库密码、JWT 密钥等真正的秘密,绝不能打包进前端或提交到仓库。
跨域错误,为什么只在浏览器里出现
当前端运行在 localhost:5173,后端运行在 localhost:8080,它们端口不同,浏览器就把它们视为不同来源。
浏览器为了保护用户,不允许网页随意读取其他来源的响应。于是你可能看到 CORS 错误。这个错误来自浏览器安全策略,不代表 Spring Boot 没有运行,也不代表网络请求一定没到后端。
开发时常见做法是让 Vite 代理 /api:
server: {
proxy: { '/api': 'http://localhost:8080' }
}
浏览器以为自己一直在访问前端服务,Vite 在背后把接口请求转发给 Spring Boot。生产环境中,Nginx 或网关通常承担类似角色。
需要记住的是:CORS 不是权限系统。它只能约束浏览器页面,无法阻止别人用脚本或接口工具直接请求后端。真正的安全仍然要靠认证和授权。
当你访问 /api/todos 时,背后已经启动了一座小城市
看起来只是一个方法返回数组,背后却已经有完整的运行体系:
- JVM 正在管理内存和线程;
- 内置 Tomcat 正在监听 8080 端口;
- Spring 容器正在管理 Controller;
- 请求映射系统正在匹配 URL;
- Jackson 正在转换 Java 对象和 JSON;
- 配置系统正在合并文件与环境变量。
Spring Boot 的价值不在于让“Hello World”少写几行,而在于这些能力会随着项目变大继续工作。你从第一个接口开始使用的结构,可以自然延伸到数据库、权限、测试和部署。
下一篇,我们不再只看入口和出口,而是跟着一次请求进入后端内部,看看 Controller、Service、Repository 为什么存在,以及 Spring 是怎样把它们组装起来的。






