02|从 `pnpm dev` 到 Spring Boot 启动:后端服务到底怎么跑起来?

02|从 pnpm dev 到 Spring Boot 启动:后端服务到底怎么跑起来?

本篇是"前端工程师转后端"系列的第 2 篇。第 1 篇我们先建立了工程地图,知道要从 README.mdbackend/pom.xml、Controller、Facade 这些入口开始读。现在进入真正的后端基础:Java 程序如何运行,Maven 如何管理依赖和多模块,Spring Boot 为什么能把一个 Java 工程启动成 HTTP 服务。本篇仍然基于当前 fullstack-mall 后端工程,不会假设你已经懂 MyBatis-Plus、Redis、JWT,只会先让你把"项目怎么跑起来"这件事弄清楚。
说明:本文中的前端代码仍然是"前端侧示意代码",用于类比。当前仓库没有真实前端源码目录。本文引用的后端文件都来自当前工程。

1. 这篇解决什么问题

前端同学学习后端,经常第一步就卡在"启动方式不熟"。你可能很熟悉:

bash 复制代码
pnpm install
pnpm dev

也知道前端项目里 package.json 会写依赖、脚本、构建命令。可是打开 Java 后端项目后,你看到的是:

text 复制代码
backend/pom.xml
backend/contract/pom.xml
backend/service/pom.xml
backend/service/src/main/java/...
backend/service/src/main/resources/application.yml

然后命令变成:

bash 复制代码
cd backend
mvn -pl service -am spring-boot:run

如果只背命令,你可能能启动一次,但不知道每个词是什么意思。下一次遇到编译失败、依赖找不到、端口冲突、数据库连不上、profile 不对,就会不知道从哪里排查。

本篇解决下面几个问题:

  1. Java 后端程序和浏览器前端程序的运行模型有什么不同;
  2. JDK、JVM、Maven、Spring Boot 分别是什么;
  3. pom.xml 为什么类似 package.json,但又不只是依赖清单;
  4. 当前项目为什么拆成 contractservice 两个 Maven 模块;
  5. FullstackMallApplication.java 为什么是后端启动入口;
  6. application.ymlapplication-local.ymlapplication-debug.yml 分别解决什么问题;
  7. 如何用最小命令启动服务,并从日志、端口、接口验证启动是否成功。

学完本篇,你不一定马上会写复杂 Java 代码,但你应该能回答一句非常关键的话:这个后端服务是如何从源码变成一个正在监听 HTTP 请求的进程的。

2. 用前端知识类比:从 Vite dev server 到 Spring Boot server

先从你熟悉的前端启动流程开始。一个 Vue + Vite 项目通常是:

bash 复制代码
pnpm install
pnpm dev

背后大概发生这些事:

  1. pnpm 根据 package.json 和 lockfile 下载依赖;
  2. Vite 启动开发服务器;
  3. 浏览器访问某个端口,例如 http://localhost:5173
  4. Vite 负责处理模块、热更新、静态资源、代理等;
  5. 用户操作页面时,前端再通过 Axios 请求后端 API。

Java 后端也有类似流程,只是工具和产物不同:

bash 复制代码
cd backend
mvn -pl service -am spring-boot:run

背后大概发生这些事:

  1. Maven 读取 pom.xml,解析项目模块和依赖;
  2. Maven 编译 Java 源码;
  3. Spring Boot 插件启动 service 模块;
  4. JVM 运行 FullstackMallApplication.main()
  5. Spring Boot 创建应用上下文,扫描 Controller、Service、Config 等 Bean;
  6. 内嵌 Web 服务器启动,监听 server.port,默认是 8080
  7. 浏览器、curl 或前端页面请求 http://localhost:8080/api/... 时,后端 Controller 开始处理请求。

可以用这张图类比:

flowchart LR subgraph FE[前端开发启动] A1[package.json] --> A2[pnpm install] A2 --> A3[pnpm dev] A3 --> A4[Vite dev server] A4 --> A5[浏览器访问页面] end subgraph BE[后端开发启动] B1[pom.xml] --> B2[mvn 解析依赖和模块] B2 --> B3[编译 Java 源码] B3 --> B4[Spring Boot run] B4 --> B5[JVM 运行 main 方法] B5 --> B6[后端监听 HTTP 端口] end A5 --> C[Axios / fetch 请求 API] C --> B6

这个类比能帮助你快速入门,但你要注意两点差异。

第一,前端代码最终大多运行在浏览器里,而 Java 后端代码运行在服务端 JVM 进程里。前端页面刷新可能重建状态,但后端服务是一个长期运行的进程,会同时处理很多用户请求。

第二,前端开发服务器更多服务于开发体验,而后端服务本身就是业务系统。它要连接 MySQL、Redis,要校验 JWT,要执行事务,要记录日志,要处理错误,还要面对并发请求。

所以,学习后端启动不是为了"会敲命令",而是为了理解:一个服务进程启动后,配置、依赖、Bean、端口、数据库连接、缓存连接是如何准备好的。

3. 后端核心概念讲解

3.1 JDK、JVM、Java 源码:先分清三件事

前端世界里,你可能会区分 Node.js、npm、TypeScript、浏览器。Java 世界也有几层:

名词 先怎么理解 类比
Java 源码 .java 文件,人写的代码 .ts / .vue 源码
JDK Java 开发工具包,包含编译器和运行工具 Node.js + 一些开发工具的组合类比
JVM Java 虚拟机,真正运行 Java 字节码的地方 浏览器 JS 引擎 / Node 运行时的类比
Maven Java 项目构建和依赖管理工具 pnpm / npm scripts / 构建工具的组合类比
Spring Boot 快速搭建可运行后端服务的框架 Vite + Web server + 依赖装配框架的粗略类比

Java 源码不是直接像脚本那样被操作系统运行。它通常先被编译成字节码,再由 JVM 执行。你现在不需要深入 JVM 内存模型,只需要知道:运行 Java 后端时,真正常驻的是一个 JVM 进程。

这和前端很不同。前端业务代码最终主要在用户浏览器里运行,用户 A 和用户 B 的浏览器状态彼此隔离。后端服务则是所有用户共同请求同一个服务进程,服务进程再连接共享的 MySQL 和 Redis。因此后端代码天然要考虑多用户、并发、共享资源和失败恢复。

3.2 Maven:不只是安装依赖

Maven 的核心配置文件叫 pom.xml。当前后端有 3 个重要 POM:

text 复制代码
backend/pom.xml
backend/contract/pom.xml
backend/service/pom.xml

你可以先把 pom.xml 类比成 package.json,但 Maven 的职责更偏"工程构建"。它不仅声明依赖,还声明:

  • 这个项目的 groupIdartifactIdversion
  • 这个项目是否是父工程;
  • 子模块有哪些;
  • 使用哪个 Java 版本;
  • 依赖版本如何统一管理;
  • 构建时使用哪些插件;
  • 哪些依赖只在测试或运行时生效。

在当前项目 backend/pom.xml 中,父工程声明了:

  • Spring Boot 父依赖版本是 3.5.16
  • Java 版本是 21
  • 模块包括 contractservice
  • MyBatis-Plus、Druid、JJWT 等版本统一在 properties 里定义。

这就像一个前端 monorepo 的根配置:根配置管公共版本和 workspace,子包再声明自己的职责。

3.3 Maven 坐标:groupIdartifactIdversion

如果你看过 npm 包,会熟悉包名和版本,例如:

json 复制代码
{
  "name": "my-app",
  "version": "1.0.0"
}

Maven 世界里,一个依赖通常用 3 个坐标定位:

xml 复制代码
<groupId>com.example.fullstackmall</groupId>
<artifactId>fullstack-mall-service</artifactId>
<version>0.0.1-SNAPSHOT</version>

可以这样理解:

  • groupId:组织或命名空间;
  • artifactId:具体模块 / 产物名;
  • version:版本号。

当前 service 模块依赖 contract 模块时,也是通过 Maven 坐标声明:

xml 复制代码
<dependency>
    <groupId>com.example.fullstackmall</groupId>
    <artifactId>fullstack-mall-contract</artifactId>
    <version>${project.version}</version>
</dependency>

这说明 service 模块要使用 contract 模块里的 Request、Response、Facade 接口。前端类比就是某个 package 引用了 monorepo 里的另一个 package。

3.4 多模块:为什么有 contractservice

当前后端父工程的 <modules> 是:

xml 复制代码
<modules>
    <module>contract</module>
    <module>service</module>
</modules>

这表示 backend 下面有两个 Maven 子模块。

contract 的职责是"对外接口、请求和响应模型"。它里面放:

  • *Request.java
  • *Response.java
  • enum;
  • I...Facade.java

service 的职责是"可运行的 Spring Boot 后端服务"。它里面放:

  • Controller;
  • Facade 实现;
  • Entity;
  • Mapper;
  • DbService;
  • Security;
  • Cache;
  • Config;
  • 测试;
  • application.yml

为什么要拆?因为对外契约和内部实现的变化频率、关注点不同。前端最关心的是接口能传什么、返回什么;后端内部则关心怎么查库、怎么校验、怎么做缓存、怎么处理事务。把契约单独放出来,有助于保持边界清晰。

你可以用这张图理解:

flowchart TD A[backend 父工程] --> B[contract 模块\n接口契约] A --> C[service 模块\n可运行服务] B --> B1[Request DTO] B --> B2[Response DTO] B --> B3[enum] B --> B4[I...Facade] C --> C1[Controller] C --> C2[Facade 实现] C --> C3[DbService / Mapper / Entity] C --> C4[Security / Cache / Config] C --> C5[FullstackMallApplication 启动类] C -->|依赖| B

注意箭头方向:service 依赖 contract。也就是说,服务实现要遵守对外契约。反过来,contract 不应该依赖 service 的内部实现。这个边界类似前端中"类型定义和 API 契约不应该反向依赖页面组件"。

3.5 Spring Boot:把 Java 类装配成服务

Spring Boot 做了很多事,但入门阶段可以先记住三句话:

  1. 它帮你启动一个 Java 后端应用;
  2. 它帮你创建和管理对象,也就是 Bean;
  3. 它帮你把 HTTP 请求分发给 Controller。

当前项目启动类是:

text 复制代码
backend/service/src/main/java/com/example/fullstackmall/service/FullstackMallApplication.java

代码非常短:

java 复制代码
@EnableScheduling
@SpringBootApplication(exclude = UserDetailsServiceAutoConfiguration.class)
public class FullstackMallApplication {

    public static void main(String[] args) {
        SpringApplication.run(FullstackMallApplication.class, args);
    }
}

你可以先这样翻译:

  • main 方法是 Java 程序入口;
  • SpringApplication.run(...) 启动 Spring Boot 应用;
  • @SpringBootApplication 表示这是 Spring Boot 应用主类;
  • @EnableScheduling 开启定时任务能力,后续支付超时关单会用到;
  • exclude = UserDetailsServiceAutoConfiguration.class 表示排除 Spring Security 默认用户详情自动配置,本项目有自己的认证方式。

不用急着深挖每个注解。你现在只需要知道:后端服务从这个 main 方法开始运行。

3.6 application.yml:后端版运行配置

前端项目有 .env.development.env.productionvite.config.ts。后端项目也需要配置:端口、数据库、Redis、JWT、业务参数等。当前主要配置在:

text 复制代码
backend/service/src/main/resources/application.yml

里面可以看到这些内容:

  • 应用名:fullstack-mall-service
  • MySQL 连接:jdbc:mysql://localhost:3308/fullstack_mall
  • 数据库账号:默认 mall
  • Redis host:默认 localhost
  • Redis port:默认 6379
  • 服务端口:默认 8080
  • MyBatis-Plus 驼峰映射;
  • Swagger UI 路径;
  • JWT secret 和过期时间;
  • 订单支付超时时间;
  • 商品详情缓存 TTL。

如果你写前端,看到:

env 复制代码
VITE_API_BASE_URL=http://localhost:8080

你知道这是运行配置。后端里的 application.yml 同理,只是配置项更多,而且很多配置直接影响服务能不能正常启动。

例如这行:

yaml 复制代码
server:
  port: ${SERVER_PORT:8080}

表示服务默认监听 8080,也可以通过环境变量 SERVER_PORT 覆盖。如果你启动时发现端口冲突,就要想到这里。

再看数据库配置:

yaml 复制代码
spring:
  datasource:
    url: ${DB_URL:jdbc:mysql://localhost:3308/fullstack_mall?...}
    username: ${DB_USERNAME:mall}
    password: ${DB_PASSWORD:mall123}

这表示默认连接本机 3308 端口的 MySQL。如果 Docker 里的 MySQL 没启动,或者端口不是 3308,服务启动或访问数据库时就会出错。

3.7 profile:为什么有 local 和 debug

当前项目还有:

text 复制代码
backend/service/src/main/resources/application-local.yml
backend/service/src/main/resources/application-debug.yml

application-local.yml 是本地无 Docker 快速启动配置。它使用 H2 内存数据库,并且默认关闭商品详情 Redis 缓存:

yaml 复制代码
mall:
  cache:
    product-detail:
      enabled: false

这对初学者很友好。因为你刚开始可能还没装 Docker,或者 MySQL / Redis 没启动。local profile 可以让你先把服务跑起来,验证接口链路。

application-debug.yml 则用于调试,默认把端口改成 8081,避免和普通开发实例的 8080 冲突。

前端类比是:

  • .env.development:开发环境配置;
  • .env.local:本地私有配置;
  • .env.production:生产环境配置。

Spring Boot profile 也是类似思路:同一个项目,在不同场景加载不同配置。

4. 在本项目中对应哪些文件

本篇最重要的文件如下:

文件 作用 前端类比
backend/pom.xml 父工程,定义 Spring Boot、Java 版本、模块和公共版本 monorepo 根 package.json / workspace 配置
backend/contract/pom.xml 契约模块 POM,定义契约模块依赖 packages/api-types/package.json 类比
backend/service/pom.xml 服务模块 POM,定义 Web、Security、Redis、MyBatis-Plus、MySQL 等依赖 应用包 package.json
backend/service/src/main/java/com/example/fullstackmall/service/FullstackMallApplication.java Spring Boot 启动入口 src/main.ts 的后端类比
backend/service/src/main/resources/application.yml 默认运行配置,连接 MySQL / Redis,设置端口和业务参数 .env + vite.config.ts 的部分职责
backend/service/src/main/resources/application-local.yml 本地无 Docker 快速启动配置,使用 H2,关闭商品详情缓存 .env.local 类比
backend/service/src/main/resources/application-debug.yml Debug 配置,默认端口 8081 本地调试专用配置
docker-compose.dev.yml 启动 MySQL 和 Redis 前端项目较少直接对应,可类比本地依赖服务编排

建议你先打开这些文件,不要急着读业务代码。你要先知道后端服务"启动前需要什么、启动时读什么、启动后监听哪里"。

5. Mermaid 图:从源码到可访问接口

下面这张图把本篇主线串起来:

flowchart TD A[开发者执行 mvn spring-boot:run] --> B[Maven 读取 backend/pom.xml] B --> C[识别 contract 和 service 模块] C --> D[编译 contract\nRequest / Response / IFacade] D --> E[编译 service\nController / Facade / Config] E --> F[Spring Boot 插件启动 service] F --> G[JVM 执行 FullstackMallApplication.main] G --> H[SpringApplication.run] H --> I[读取 application.yml / profile 配置] I --> J[创建 Spring Bean\nController / Service / Mapper] J --> K[启动内嵌 Web Server\n监听 8080 或配置端口] K --> L[浏览器 / curl / 前端页面请求 API]

再看配置和外部依赖:

flowchart LR A[Spring Boot service] --> B[server.port\n默认 8080] A --> C[MySQL datasource\nlocalhost:3308] A --> D[Redis\nlocalhost:6379] A --> E[JWT 配置\nsecret / expiration] A --> F[业务配置\n订单超时 / 缓存 TTL] G[application.yml] --> A H[application-local.yml] -.覆盖部分配置.-> A I[application-debug.yml] -.覆盖端口.-> A J[docker-compose.dev.yml] --> C J --> D

这两张图加在一起,你就能理解一个后端服务的启动需要经过"构建、运行、加载配置、创建对象、监听端口、连接外部服务"几个步骤。

6. 逐段读源码和配置

6.1 读 backend/pom.xml

先看父工程。关键片段是:

xml 复制代码
<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>3.5.16</version>
</parent>

这说明当前项目继承 Spring Boot 的父工程配置。你可以理解为 Spring Boot 帮你预先管理了很多常见依赖和插件的默认版本,避免你手动拼一堆容易冲突的版本。

然后看项目自己的坐标:

xml 复制代码
<groupId>com.example.fullstackmall</groupId>
<artifactId>fullstack-mall-backend</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>pom</packaging>

packagingpom,说明它是父工程,不是最终可运行 jar。真正可运行的是 service 模块。

再看模块:

xml 复制代码
<modules>
    <module>contract</module>
    <module>service</module>
</modules>

这告诉 Maven:构建父工程时要处理这两个子模块。

最后看 properties:

xml 复制代码
<java.version>21</java.version>
<mybatis-plus.version>3.5.16</mybatis-plus.version>
<druid.version>1.2.28</druid.version>
<jjwt.version>0.13.0</jjwt.version>

这就像前端项目把重要依赖版本统一放在根配置里。以后升级 MyBatis-Plus 或 JJWT,就可以从这里看版本。

6.2 读 backend/contract/pom.xml

contract 模块比较轻,它的描述是"对外接口、请求和响应模型"。依赖也很少:

  • Lombok;
  • Jakarta Validation API。

这说明 contract 主要定义模型和接口,不启动 Web 服务,也不连接 MySQL。你在这里看到的 Request、Response、enum,应该尽量保持干净,不要混入 service 的内部实现。

前端类比是:如果你有一个 api-types 包,它只放接口类型,不应该反过来依赖页面组件。

6.3 读 backend/service/pom.xml

service 模块的描述是"可运行的 Spring Boot 后端服务"。这里才是功能依赖最多的地方。

看到:

xml 复制代码
<artifactId>spring-boot-starter-web</artifactId>

说明项目可以写 HTTP Controller。

看到:

xml 复制代码
<artifactId>spring-boot-starter-validation</artifactId>

说明项目可以对请求参数做校验,例如必填、长度、数值范围。

看到:

xml 复制代码
<artifactId>spring-boot-starter-security</artifactId>

说明项目使用 Spring Security 做认证授权。

看到:

xml 复制代码
<artifactId>spring-boot-starter-data-redis</artifactId>

说明项目可以连接 Redis。

看到:

xml 复制代码
<artifactId>mybatis-plus-spring-boot3-starter</artifactId>

说明项目使用 MyBatis-Plus 操作数据库。

看到:

xml 复制代码
<artifactId>mysql-connector-j</artifactId>

说明项目可以连接 MySQL。

对前端同学来说,读 pom.xml 的方法和读 package.json 类似:先不要逐行背,先问"这个依赖让项目具备什么能力"。比如 spring-boot-starter-web 对应 HTTP 接口,spring-boot-starter-security 对应登录权限,mybatis-plus 对应数据库访问,spring-boot-starter-data-redis 对应缓存。

6.4 读启动类 FullstackMallApplication.java

启动类只有十几行,但非常重要:

java 复制代码
@EnableScheduling
@SpringBootApplication(exclude = UserDetailsServiceAutoConfiguration.class)
public class FullstackMallApplication {

    public static void main(String[] args) {
        SpringApplication.run(FullstackMallApplication.class, args);
    }
}

main 是 Java 程序入口。你可以暂时类比前端的 main.ts,但后端 main 方法启动的是一个长期运行的服务进程。

@SpringBootApplication 是组合注解。入门阶段可以先理解为它告诉 Spring Boot:"从这里开始扫描和启动应用"。默认情况下,Spring 会扫描这个类所在包及其子包。当前包是:

text 复制代码
com.example.fullstackmall.service

所以 service 包下面的 Controller、Facade、Config 等类会被扫描到。

@EnableScheduling 开启定时任务。当前项目后续支付超时关单会用到调度能力。你现在只需要知道,这个注解使得类似 @Scheduled 的定时任务可以工作。

exclude = UserDetailsServiceAutoConfiguration.class 是和 Spring Security 相关的配置。Spring Security 默认可能会自动创建一个用户体系,但当前项目有自己的 JWT 登录认证,所以排除了默认用户详情自动配置。这个细节第 6 篇再深入。

6.5 读 application.yml

application.yml 是后端运行配置中心之一。先看服务名:

yaml 复制代码
spring:
  application:
    name: fullstack-mall-service

服务名在日志、监控、配置中都可能有用。

再看数据库:

yaml 复制代码
spring:
  datasource:
    type: com.alibaba.druid.pool.DruidDataSource
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: ${DB_URL:jdbc:mysql://localhost:3308/fullstack_mall?...}
    username: ${DB_USERNAME:mall}
    password: ${DB_PASSWORD:mall123}

这里说明默认连接本机 3308 端口的 MySQL,数据库名是 fullstack_mall${DB_URL:默认值} 这种写法表示:如果环境变量 DB_URL 存在,就用环境变量;否则用冒号后面的默认值。

前端也常见类似写法:开发环境默认 API 地址是 localhost,但生产环境通过环境变量注入真实地址。

再看 Redis:

yaml 复制代码
spring:
  data:
    redis:
      host: ${REDIS_HOST:localhost}
      port: ${REDIS_PORT:6379}
      timeout: ${REDIS_TIMEOUT:2s}

这说明默认连接本机 Redis。后续 Redis 篇会讲 key、TTL、缓存失效,现在只要知道连接配置在这里。

再看端口:

yaml 复制代码
server:
  port: ${SERVER_PORT:8080}

所以默认服务地址是:

text 复制代码
http://localhost:8080

如果端口被占用,你可以设置 SERVER_PORT,或者使用 debug profile。

6.6 读 application-local.yml

application-local.yml 的注释写得很清楚:这是"本地无 Docker 快速启动配置"。它用 H2 内存数据库替代 MySQL,并且初始化本地 schema 和 data。

对初学者来说,这个配置非常重要。因为你学习后端初期可能还没把 Docker、MySQL、Redis 全部准备好。local profile 可以让你先体验 Spring Boot 启动和 HTTP 接口。

但是你也要知道局限:H2 是内存数据库,应用重启后数据会重置;它可以帮助快速学习,但不能完全替代真实 MySQL 行为。后续涉及事务、锁、并发、真实 SQL 方言时,还是要回到 MySQL。

6.7 读 application-debug.yml

debug 配置主要做一件事:把端口默认改成 8081

yaml 复制代码
server:
  port: ${SERVER_PORT:8081}

为什么需要?因为你可能已经在普通方式启动了一个 8080 服务,又想在 IDEA 里 Debug 再启动一个。如果两个进程都抢 8080,就会端口冲突。debug profile 使用 8081,可以减少冲突。

7. 本地怎么运行 / curl 怎么验证

7.1 方式一:使用 Docker MySQL 和 Redis 的正常开发启动

先在项目根目录启动依赖:

bash 复制代码
docker compose -f docker-compose.dev.yml up -d mysql redis

然后启动后端:

bash 复制代码
cd backend
mvn -pl service -am spring-boot:run

命令里的几个部分解释如下:

命令片段 含义
mvn 调用 Maven
-pl service 指定要操作的模块是 service
-am 同时构建 service 依赖的模块,例如 contract
spring-boot:run 使用 Spring Boot Maven 插件运行应用

启动成功后,服务默认监听:

text 复制代码
http://localhost:8080

你可以用 curl 请求公开接口,例如:

bash 复制代码
curl -i http://localhost:8080/api/products/1001

如果返回 JSON,说明 HTTP 服务已经正常响应。至于商品是否存在、状态是否上架、缓存是否命中,是后续业务章节要看的内容。

7.2 方式二:使用 local profile 快速启动

如果你暂时不想启动 Docker,可以尝试 local profile:

bash 复制代码
cd backend
mvn -pl service -am spring-boot:run -Dspring-boot.run.profiles=local

local profile 会使用 H2 内存数据库,并默认关闭商品详情缓存。它适合先学习 Controller、Request、Response、启动流程,不适合验证所有 MySQL 和 Redis 真实行为。

7.3 方式三:Debug profile

如果你在 IDEA 中调试,可以把 Active profiles 设置为:

text 复制代码
debug

或者命令行:

bash 复制代码
cd backend
mvn -pl service -am spring-boot:run -Dspring-boot.run.profiles=debug

debug profile 默认端口是 8081。请求时要改成:

bash 复制代码
curl -i http://localhost:8081/api/products/1001

如果你同时激活多个 profile,例如 local + debug,需要注意配置覆盖关系。入门阶段建议一次只做一个目标:要么快速 local 启动,要么正常 Docker 启动,要么 Debug 调试。

7.4 Maven 常用命令:不要只会一条启动命令

前端同学刚接触 Maven 时,最容易把它理解成"Java 版 npm"。这个类比可以帮助入门,但不能完全等价。npm run devnpm run buildnpm test 往往是项目作者在 package.json 里定义的脚本;而 Maven 有一套相对固定的生命周期,很多命令不是项目作者临时起名,而是 Maven 生态约定出来的阶段。

在当前项目里,下面这些命令最值得你先记住:

bash 复制代码
cd backend
mvn -pl service -am compile
mvn -pl service -am test
mvn -pl service -am package
mvn -pl service -am spring-boot:run

它们的含义可以这样理解。

compile 负责把主代码编译一遍。你可以把它类比成前端里的 tsc --noEmit 或构建前的类型检查:它不一定真正启动服务,但能告诉你 Java 代码、依赖、注解处理器是否能组成一个可编译的整体。后端开发中,如果你改了 DTO、Facade 接口、Service 实现,先跑一次 compile 是很好的习惯,因为 Java 是静态类型语言,很多错误可以在编译阶段暴露,而不用等到运行时才发现。

test 会执行测试。前端里你可能用 Vitest、Jest、Playwright;后端里通常用 JUnit、Spring Boot Test、Testcontainers。当前项目的 service 模块引入了测试相关依赖,说明它具备写单元测试、集成测试的基础。后续我们讲订单、库存、支付回调时,你会看到测试对后端特别重要,因为这些逻辑不只是页面展示问题,而是会影响数据一致性。

package 会把项目打包。Spring Boot 项目通常会产出一个可以运行的 jar 包。前端打包产物常常是 dist/ 下的一堆静态文件;后端打包产物则更像一个完整服务程序。它包含你的业务代码、依赖、配置入口,部署时可以通过 java -jar ... 启动。理解这一点后,你就会知道:后端上线不是把源码丢到服务器上,而是把一个构建产物以进程方式运行起来。

spring-boot:run 是 Spring Boot Maven Plugin 提供的运行目标。它适合本地开发阶段使用。你可以把它类比成 vite dev:目的不是产出最终上线包,而是快速把服务跑起来,方便你改代码、看日志、验证接口。

这里还要重点解释 -pl service -am-pl--projects 的缩写,意思是只选择某个模块;service 是当前要启动的模块。-am--also-make 的缩写,意思是同时构建它依赖的模块。因为 service 依赖 contract,所以你不能只关心 service,还要让 Maven 把 contract 一起编译好。前端 monorepo 里也有类似情况:如果 app package 依赖 shared package,启动 app 前也需要保证 shared 已经构建或能被工作区解析。

flowchart LR A[执行 mvn -pl service -am spring-boot:run] --> B[选择 service 模块] B --> C[发现 service 依赖 contract] C --> D[先编译 contract] D --> E[再编译 service] E --> F[执行 Spring Boot Maven Plugin] F --> G[启动 FullstackMallApplication]

7.5 从日志判断启动到了哪一步

后端启动失败时,不要一上来就复制整段报错去搜索。更推荐你像调试前端构建一样,先判断它卡在哪个阶段。一个 Spring Boot 服务从命令到可访问,大致经历这些阶段:Maven 阶段、JVM 阶段、Spring 容器阶段、Web 服务器阶段、外部资源连接阶段、业务初始化阶段。

如果错误出现在 Maven 阶段,常见表现是依赖下载失败、找不到模块、编译报错、测试失败。这个阶段服务其实还没有真正启动,端口也不会被监听。你应该看的是 pom.xml、本地 Maven 仓库、Java 版本、命令执行目录,以及最近改动的 Java 类型签名。

如果错误出现在 JVM 阶段,常见表现是 Java 版本不匹配,例如项目要求 Java 21,但你本机实际使用了更低版本。前端里也有类似问题:项目要求 Node 20,但你用 Node 16,就可能出现语法、依赖或构建工具不兼容。后端里可以用下面命令先确认:

bash 复制代码
java -version
mvn -version

如果错误出现在 Spring 容器阶段,常见表现是某个 Bean 创建失败、依赖注入失败、配置属性绑定失败。你会看到类似 BeanCreationExceptionUnsatisfiedDependencyException 这样的异常名称。这个阶段最重要的排查思路是:哪个类需要哪个依赖,Spring 为什么没有办法创建它。前端类比的话,就像一个组件 import 了某个模块,但模块没有正确导出;或者一个组件需要某个 Provider,但上层没有提供。

如果错误出现在 Web 服务器阶段,常见表现是端口被占用,或者启动日志里没有出现 Tomcat 监听端口的信息。当前项目默认端口来自 server.port,默认是 8080debug profile 默认是 8081。如果你本地已经有另一个服务占用了端口,就要换端口或者关闭旧进程。

如果错误出现在外部资源连接阶段,最常见就是 MySQL 或 Redis 连接不上。当前默认配置会连 localhost:3308 的 MySQL 和 localhost:6379 的 Redis;如果你没有启动 docker-compose.dev.yml 里的容器,就可能失败。使用 local profile 时,数据库会切换到 H2 内存数据库,并且商品详情缓存默认关闭,这就能绕开一部分外部依赖,适合先学习启动链路。

所以,后端排错第一问不是"这个异常是什么意思",而是"服务启动走到了哪一层"。只要层次判断正确,后面的搜索范围会小很多。

7.6 profile、环境变量和配置覆盖:后端也有多环境

前端项目里常见 .env.development.env.production.env.local。你可能已经习惯通过 VITE_API_BASE_URL 区分本地、测试、线上 API 地址。Spring Boot 的 profile 也是类似思想:同一套代码,在不同环境读取不同配置。

当前工程的主配置是:

text 复制代码
backend/service/src/main/resources/application.yml

它定义了服务端口、数据库地址、Redis 地址、MyBatis-Plus 配置、Swagger UI 地址、JWT 参数和商品详情缓存参数。这个文件相当于"默认配置"。当你启用 local profile 时,Spring Boot 会额外读取:

text 复制代码
backend/service/src/main/resources/application-local.yml

当你启用 debug profile 时,Spring Boot 会额外读取:

text 复制代码
backend/service/src/main/resources/application-debug.yml

覆盖规则可以简单理解为:默认配置先加载,profile 配置再补充或覆盖。比如默认端口是 8080debug profile 里如果设置端口为 8081,那么最终监听的就是 8081。这和前端里基础 .env 加上 .env.development 的覆盖关系很像。

当前 application.yml 里有很多 ${NAME:default} 形式的配置。它表示:优先读取环境变量 NAME,如果没有,就使用冒号后面的默认值。例如端口配置类似"优先看 SERVER_PORT,没有就用 8080"。前端里的环境变量通常在构建时注入,而后端环境变量更多在运行时读取。这个差异非常关键:前端构建后的静态文件通常不能随便改变构建期变量;后端进程每次启动时都可以根据启动环境读取不同变量。

举个例子,如果你临时想把后端跑到 8090 端口,可以用:

bash 复制代码
cd backend
SERVER_PORT=8090 mvn -pl service -am spring-boot:run

如果你想启用 local profile,可以用:

bash 复制代码
cd backend
mvn -pl service -am spring-boot:run -Dspring-boot.run.profiles=local

如果你只是学习后端,不想一开始就被 MySQL、Redis、Docker 全部拦住,建议先用 local profile 跑通;等你理解 Controller 和数据库访问后,再切回默认配置,启动 Docker 里的 MySQL 与 Redis。学习路径不要一上来追求"完全真实生产环境",而要先分层:先让服务启动,再让接口通,再让数据库通,再让缓存通,最后再考虑安全、性能和部署。

7.7 本章建议的调试顺序

前端调试常常是:先看页面是否打开,再看 Network,再看 Console,再看接口响应。后端也应该建立固定顺序。对于当前项目,我建议你每次启动失败都按下面顺序排查:

  1. 确认目录:命令是否在 backend 目录执行;
  2. 确认 Java:java -version 是否满足项目要求;
  3. 确认 Maven:mvn -version 是否使用了正确 JDK;
  4. 确认模块:命令是否带了 -pl service -am
  5. 确认 profile:你到底在用默认配置、local,还是 debug
  6. 确认端口:80808081 是否被占用;
  7. 确认数据库:默认配置下 MySQL 是否在 3308
  8. 确认 Redis:默认配置下 Redis 是否在 6379
  9. 确认接口:用 curl 请求健康检查或商品查询接口;
  10. 确认日志:从第一条真正的异常开始读,而不是只看最后一行。

这套顺序的价值在于它符合因果链。目录错了,后面所有 Maven 讨论都没意义;Java 版本错了,Spring Boot 还没机会启动;profile 错了,你以为连 H2,实际却在连 MySQL;端口错了,前端请求再正确也访问不到服务。后端学习最怕"同时怀疑所有东西",固定排查顺序能显著降低心智负担。

7.8 给前端同学的一次完整心智演练

最后我们把本章内容串成一次"从零启动"的心智演练。假设你刚拉下这个仓库,第一眼看到后端目录,不要马上猜接口怎么写,也不要马上研究 Redis 和 MyBatis-Plus。第一步先确认项目边界:backend 是后端父工程,contract 是接口契约模块,service 是真正可运行的服务模块。这个判断来自 backend/pom.xml<modules>,不是凭感觉猜出来的。

第二步看运行入口。前端项目通常从 package.jsonscripts 找入口,再去看 Vite、Nuxt 或 Next.js 的配置;后端项目则先找 Spring Boot 启动类。当前启动类是 FullstackMallApplication.java,它所在包名是 com.example.fullstackmall.service。Spring Boot 默认会从启动类所在包向下扫描组件,因此 Controller、Facade、Mapper、Config 等类如果放在这个包的子包下,就更容易被自动发现。你以后新增后端类时,也要有"包扫描范围"的意识,不要随便放到完全无关的包路径里。

第三步看运行配置。你要知道服务监听哪个端口、连哪个数据库、连哪个 Redis、JWT 多久过期、缓存是否开启。当前这些信息主要在 application.yml。当你使用 local profile 时,配置会切到 H2 内存数据库,并关闭商品详情缓存;当你使用 debug profile 时,端口会变成 8081。所以同一段 Controller 代码,在不同 profile 下可能访问的是不同数据源,这就是很多后端新手"我明明改了数据,接口怎么没变化"的根源。

第四步才是启动命令。如果你希望尽量贴近真实开发环境,就先启动 Docker 里的 MySQL 和 Redis,再运行默认 profile;如果你只是想快速学习 Controller 和参数接收,可以先用 local profile。两种方式没有高低之分,它们服务于不同阶段。默认 profile 更接近真实依赖,适合学习 MySQL、Redis、缓存、事务;local profile 更轻量,适合先跑通 Web 层和 Java 基础。

第五步是验证。后端不能只看"控制台没有报错"就算成功。你至少要做三层验证:进程是否还活着,端口是否在监听,接口是否能返回预期 JSON。前端同学可以把这理解成:页面能打开只是第一层,Network 里接口成功才是第二层,数据渲染正确才是第三层。后端也是一样,启动日志只是过程证据,curl 响应才是接口证据,数据库里的数据变化才是业务证据。

第六步是记录。每次你遇到启动问题,都建议写下这几个字段:使用的命令、启用的 profile、Java 版本、Maven 版本、端口、数据库地址、第一条异常。这样做看似麻烦,但它会训练你用工程化方式排错。后端问题往往不是单点问题,而是代码、配置、环境、依赖共同作用的结果;没有记录,就很容易在同一个坑里反复试错。

如果你能按这六步稳定启动当前项目,那么恭喜你,你已经完成了从"会写页面"到"能掌控一个服务进程"的第一道门槛。后续我们学习 Controller、MyBatis-Plus、Redis、JWT、事务时,都建立在这个启动模型之上。也就是说,后端学习不是从背注解开始,而是从理解工程如何被构建、如何被配置、如何被启动、如何被验证开始。只要这条主线清楚,后面任何框架知识都不会显得孤立。

8. 常见错误

错误 1:在项目根目录直接乱跑 Maven 命令

当前 Maven 后端工程在 backend/ 下。你应该先:

bash 复制代码
cd backend

再执行 Maven 命令。如果你在仓库根目录直接跑 mvn,很可能找不到 POM。

错误 2:只启动后端,不启动 MySQL

默认 application.yml 连接的是 localhost:3308 的 MySQL。如果 Docker MySQL 没启动,访问数据库相关接口时会失败。解决方式是先启动:

bash 复制代码
docker compose -f docker-compose.dev.yml up -d mysql redis

或者使用 local profile 快速学习。

错误 3:端口冲突不知道看哪里

如果启动时报端口占用,先看 application.yml 里的 server.port。默认是 8080。你可以停止占用端口的进程,也可以设置 SERVER_PORT,或者使用 debug profile 的 8081

错误 4:把 H2 local profile 当成真实 MySQL

H2 可以帮你快速跑起来,但它不是 MySQL。某些 SQL 方言、事务并发、锁行为和真实 MySQL 可能不同。学习基础接口可以用 local,验证库存、订单、支付这类核心行为时要回到 MySQL。

错误 5:看到依赖名就开始深挖源码

刚开始不需要读 Spring Boot、MyBatis-Plus、Redis 客户端源码。你先要知道依赖解决什么问题,在本项目哪个文件使用。比如看到 MyBatis-Plus,先知道它帮助 Mapper / Service 操作数据库;看到 Redis,先知道它是缓存;看到 Security,先知道它处理认证授权。

错误 6:不区分 compile、runtime、test 依赖

service/pom.xml 里有些依赖只在测试时用,例如 spring-boot-starter-testspring-security-test、Testcontainers。有些驱动是 runtime scope,例如 MySQL 驱动。前端也有 dependencies 和 devDependencies 的区别,后端 scope 也会影响依赖在什么时候可用。

错误 7:修改配置后不知道重启

很多后端配置在应用启动时读取。你修改 application.yml 后,通常要重启服务才生效。前端热更新让你习惯保存即生效,但后端配置不一定都是热加载。

9. 本章小练习

练习 1:读父 POM

打开:

text 复制代码
backend/pom.xml

回答:

  1. 当前 Java 版本是多少?
  2. Spring Boot 版本是多少?
  3. 当前有哪些 Maven 模块?
  4. MyBatis-Plus 版本在哪里定义?

练习 2:读 service POM

打开:

text 复制代码
backend/service/pom.xml

找到这些依赖:

  • spring-boot-starter-web
  • spring-boot-starter-validation
  • spring-boot-starter-security
  • spring-boot-starter-data-redis
  • mybatis-plus-spring-boot3-starter
  • mysql-connector-j

回答:它们分别给项目带来了什么能力?

练习 3:读启动类

打开:

text 复制代码
backend/service/src/main/java/com/example/fullstackmall/service/FullstackMallApplication.java

回答:

  1. main 方法在哪里?
  2. SpringApplication.run(...) 传入了哪个类?
  3. @EnableScheduling 可能和后续哪个业务有关?
  4. @SpringBootApplication 为什么是启动类上最重要的注解?

练习 4:读配置

打开:

text 复制代码
backend/service/src/main/resources/application.yml

回答:

  1. 默认 HTTP 端口是多少?
  2. 默认 MySQL 端口是多少?
  3. 默认 Redis 端口是多少?
  4. JWT 过期时间默认是多少秒?
  5. 商品详情缓存默认是否开启?

练习 5:尝试启动

如果你已经有 Docker,尝试:

bash 复制代码
docker compose -f docker-compose.dev.yml up -d mysql redis
cd backend
mvn -pl service -am spring-boot:run

如果你暂时没有 Docker,尝试:

bash 复制代码
cd backend
mvn -pl service -am spring-boot:run -Dspring-boot.run.profiles=local

然后用 curl 请求一个接口,观察响应和日志。

10. 本篇总结

这一章我们没有写业务代码,也没有深入 Redis、MyBatis-Plus、JWT 的具体 API。我们只做了一件非常重要的事:把后端服务启动链路看懂。

你现在应该知道:

  • Java 后端运行在 JVM 进程里,不是在浏览器里;
  • Maven 读取 pom.xml,管理依赖、模块、编译、测试和启动;
  • 当前后端是 Maven 多模块项目,父工程下面有 contractservice
  • contract 放对外契约,service 是可运行服务;
  • FullstackMallApplication.main() 是 Spring Boot 启动入口;
  • application.yml 管默认端口、MySQL、Redis、JWT、业务参数;
  • application-local.yml 可以让你不依赖 Docker 先快速启动;
  • docker-compose.dev.yml 提供真实 MySQL 和 Redis;
  • mvn -pl service -am spring-boot:run 是当前项目常用启动命令。

从前端转后端,千万不要跳过这一步。因为以后遇到任何接口问题,你都要先判断:服务有没有启动?端口对不对?profile 对不对?数据库连的是哪一个?Redis 是否开启?依赖是否存在?这些都属于"后端工程运行基础"。

11. 下一章预告

下一篇是第 3 篇:Controller、Request、Response 与统一返回:从 Axios 调接口到 Spring Boot 接参数

我们会正式进入 HTTP 接口开发基础,重点讲:

  • @RestController@RequestMapping@GetMapping@PostMapping
  • @PathVariable@RequestParam@RequestBody
  • 为什么 Request DTO 不等于 Entity;
  • 为什么 Response DTO 不应该直接暴露数据库对象;
  • @Valid 如何做参数校验;
  • ApiResponse<T> 如何统一返回给前端;
  • 前端看到 400、401、403、404、500 时,后端分别可能发生了什么。

学完下一篇,你就能真正从一个接口开始,读懂"前端传参如何变成 Java 对象,Java 对象如何变成 JSON 返回"。

相关推荐
csdn2015_1 小时前
vue 前端运行命令
前端·javascript·vue.js
网易云信1 小时前
网易智企Data Agent实践入选信通院《智能体创新实践案例汇编》
人工智能·后端·线下活动
geovindu2 小时前
go:Backtracking Algorithm
开发语言·后端·算法·golang·回溯算法
AskHarries2 小时前
图片存储怎么选
后端
阿懂在掘金2 小时前
做命令式弹窗工具应该考虑什么——我为什么把 vue-layerx 设计成这样
前端·前端框架·函数式编程
太平洋月光2 小时前
AI 快捷指令:Cursor Rules · Commands · Skills
前端·ai编程
默_笙2 小时前
😁 LLM 不知道的事它不会说"不知道",而是会"编"——RAG 就是让它闭嘴先查资料
前端·javascript
stringwu2 小时前
用 LangGraph 落地飞书 Bug 自动修复 Agent 实践
前端
Revolution612 小时前
变量提升到底提升了什么:为什么 var 是 undefined,let 却直接报错
前端·javascript