欢迎光临
我们一直在努力

前端学 Spring Boot(1):Axios 的另一端,究竟发生了什么?

前端开发里有一行极其普通的代码:

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 还深度参与编译、测试、打包等过程。你会经常见到这些对应关系:

前端项目Spring Boot 项目
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: todoapi

    敏感值则来自环境变量:

    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 是怎样把它们组装起来的。

    赞(0)
    未经允许不得转载:171主机测评 » 前端学 Spring Boot(1):Axios 的另一端,究竟发生了什么?
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址