Files
gc-plan/week2/教案.md
2026-04-29 23:45:17 +08:00

15 KiB
Raw Permalink Blame History

第二阶段教案Spring Boot 入门(第 2 周)

学习周期7 天 每日用时2-3 小时 最终产出RESTful 学生管理 API分层架构 + 参数校验 + 多环境配置) 项目结构Maven + Spring Boot 3.2


Maven 项目导入

在 IDEA 中File → Open → 选择 week2/pom.xml → Open as Project。 IDEA 会自动下载依赖,等待右下角进度条完成即可。


第 1 天Spring 是什么IoC 和 DI

核心概念

没有 Spring 时:你写代码手动 new 对象,所有对象的创建和关联都由你自己管理。

UserRepository repo = new UserRepository();     // 自己创建
UserService service = new UserService();         // 自己创建
service.setUserRepository(repo);                 // 自己关联

有了 Spring 后:对象的创建和关联由容器负责,你只需要声明"我需要什么"。

@Service
public class UserService {
    private final UserRepository repo;   // Spring 会自动注入
    // ...
}

三个关键术语

术语 白话解释
IoC(控制反转) 创建对象的控制权从你手里"反转"给了 Spring 容器
DI(依赖注入) 当 A 需要 B 时Spring 自动把 B "注入"到 A 里,你不需要 new
容器 一个巨大的 Map存着所有被 Spring 管理的对象(称为 Bean

动手:运行 IoC 演示

打开 src/main/java/com/learn/ioc/SimpleIocDemo.java,右键 Run。

这个文件包含了一个用纯 Java 手写的极简 IoC 容器(约 100 行),没有依赖任何框架。它会输出:

========== IoC 容器原理演示 ==========

--- 方式一:手动 setter 注入 ---
调用结果: 张三(来自数据库)

--- 方式二:注解自动注入 ---
调用结果: 订单 #12345

========== 总结 ==========
没有 IoC 容器时A a = new A(); a.setB(new B());  // 自己控制一切
有了 IoC 容器后:@Autowired B b;  // 容器自动注入,你只管用

今日任务

  1. 逐行读懂 SimpleIocDemo.java,理解容器/注册/注入三步
  2. 尝试在 SimpleIocDemo 里新增一个类(如 ProductService),并把它注册到容器中
  3. 思考为什么反射Reflection是实现 IoC 的关键技术?

第 2 天Spring Boot 项目结构

核心概念

Spring Boot 不是新框架,它是对 Spring 的封装和简化。传统 Spring 需要大量 XML 配置Spring Boot 通过"约定大于配置"消除了这些繁琐步骤。

项目结构详解

week2/
├── pom.xml                          # Maven 配置(依赖管理)
└── src/
    ├── main/
    │   ├── java/com/learn/
    │   │   ├── Week2Application.java    # 启动入口
    │   │   ├── controller/              # 控制器层(接收 HTTP 请求)
    │   │   ├── service/                 # 业务逻辑层
    │   │   ├── repository/              # 数据访问层
    │   │   ├── model/                   # 数据模型
    │   │   ├── config/                  # 配置类
    │   │   └── exception/               # 全局异常处理
    │   └── resources/
    │       ├── application.yml          # 主配置文件
    │       ├── application-dev.yml      # 开发环境
    │       └── application-prod.yml     # 生产环境
    └── test/                            # 测试代码

Spring Boot 启动流程

  1. main() 调用 SpringApplication.run()
  2. 创建 ApplicationContext(即 IoC 容器)
  3. 扫描 @SpringBootApplication 所在包及子包
  4. 找到所有带 @Component / @Service / @Repository / @Controller 的类
  5. 实例化它们并处理依赖注入
  6. 启动内嵌 Tomcat监听 8080 端口

动手:观察启动日志

在 IDEA 中运行 Week2Application.main(),观察控制台输出:

Started Week2Application in 1.234 seconds
Tomcat started on port 8080

你会看到 Spring Boot 的 Banner、自动配置报告、Tomcat 启动信息。

今日任务

  1. 对着上面的目录结构,在你的项目中找到每个文件的位置
  2. 打开 pom.xml,理解每个 <dependency> 的作用
  3. 尝试改 application.yml 中的 server.port 为 9090重启验证

第 3 天:第一个 REST API

核心概念

注解 作用 举例
@RestController 标记 REST 控制器,方法返回值自动转 JSON 写在类上
@GetMapping("/xxx") 映射 HTTP GET 请求 @GetMapping("/hello")
@PathVariable 从 URL 路径中取值 /hello/{id}@PathVariable Long id
@RequestParam 从 URL 查询参数中取值 /greet?name=张三@RequestParam String name

动手:测试接口

运行 Week2Application,打开浏览器或 Postman 访问:

URL 预期结果
http://localhost:8080/hello Hello, Spring Boot!
http://localhost:8080/hello/小明 你好,小明!欢迎来到 Spring Boot 的世界。
http://localhost:8080/greet?name=张三&age=20 你好,张三!你今年 20 岁。
http://localhost:8080/greet 你好,同学!(默认值生效)
http://localhost:8080/info JSON 对象 {"status":"UP","message":"...","timestamp":...}

关键理解

@RestController 的请求处理流程:

HTTP 请求 → DispatcherServlet前端控制器
         → 找到匹配的 Controller 方法
         → 执行方法,得到返回值
         → Jackson 将返回值序列化为 JSON
         → HTTP 响应返回给客户端

今日任务

  1. HelloController 中新增一个接口:GET /echo?msg=xxx,返回 {"echo": "xxx"}
  2. 用 Postman 测试所有接口,观察请求头和响应头
  3. 思考:@RestController 和普通 @Controller 有什么区别?(提示:@ResponseBody

第 4 天RESTful CRUD—— 请求映射

核心概念

RESTful API 用 HTTP 方法表达操作意图:

HTTP 方法 对应注解 语义
GET @GetMapping 查询(安全、幂等)
POST @PostMapping 新增
PUT @PutMapping 更新(幂等)
DELETE @DeleteMapping 删除(幂等)

@RequestBody —— 接收 JSON 请求体

@PostMapping
public ResponseEntity<?> add(@RequestBody Student student) {
    // Spring 自动将请求体的 JSON → Student 对象
}

客户端发送:

POST /api/students
Content-Type: application/json

{
    "name": "张三",
    "age": 20,
    "email": "zhangsan@mail.com",
    "score": 85
}

动手:测试 CRUD 接口

第 4 天的代码在 StudentController 中,此时还没有 Service/Repository 分层。 阅读 StudentController 的代码,理解每个方法的注解和 URL 设计。

操作 方法 URL 请求体
查全部 GET /api/students -
查单个 GET /api/students/1 -
搜索 GET /api/students?keyword=张 -
新增 POST /api/students JSON
更新 PUT /api/students/1 JSON
删除 DELETE /api/students/1 -

用 Postman 逐个测试(启动后已有 5 条预置数据)。

今日任务

  1. 用 Postman 完成"新增→查询→更新→删除"的完整流程
  2. 观察 PUT 更新时只传部分字段的效果
  3. 尝试故意传一个错误的 JSON少字段、错类型观察返回什么

第 5 天:分层架构 —— Controller → Service → Repository

为什么需要分层?

第 4 天所有逻辑挤在 Controller 里。当项目变复杂时:

  • Controller 既处理请求又写业务逻辑,又操作数据 → 难以维护
  • 一个地方改了,可能影响不相关的地方 → 难以测试

三层架构

┌──────────────────────────────────────┐
│  Controller 层(@RestController     │  ← 接收请求、参数校验、返回响应
│  "我只负责接待客人,不干活"             │
├──────────────────────────────────────┤
│  Service 层(@Service               │  ← 业务逻辑、事务管理
│  "我负责干活,不知道客人从哪来的"        │
├──────────────────────────────────────┤
│  Repository 层(@Repository          │  ← 数据存取
│  "我只负责存取数据,不知道业务逻辑"      │
└──────────────────────────────────────┘

依赖方向(重要!)

Controller → Service → Repository
    ↑            ↑
    只能从上往下依赖,不能反着来

每个层只依赖下一层不跨层调用。Repository 不能依赖 ServiceService 不能依赖 Controller。

本项目的分层实现

打开对应文件,理解各层职责:

文件 关键点
controller/StudentController.java Controller 只做参数提取和响应封装,调用 Service
service/StudentService.java Service 业务逻辑(校验、组合),调用 Repository
repository/StudentRepository.java Repository 数据存取,使用 ConcurrentHashMap 模拟数据库
model/Student.java Model 贯穿三层的数据载体

注入方式对比

// ❌ 字段注入(不推荐——测试困难,掩盖依赖关系)
@Autowired
private StudentService service;

// ✅ 构造方法注入(推荐——依赖明确,易于测试)
private final StudentService service;
public StudentController(StudentService service) {
    this.service = service;
}

今日任务

  1. 画出三层架构的依赖关系图
  2. StudentService 中新增一个方法:getTopStudents(int n),返回成绩最高的 n 个学生
  3. 为这个新方法在 Controller 中新增接口 GET /api/students/top?n=3

第 6 天:配置管理

application.yml vs application.properties

# YAML 格式(推荐,层次清晰)
server:
  port: 8080
spring:
  application:
    name: my-app
# properties 格式(传统,扁平)
server.port=8080
spring.application.name=my-app

两者等效YAML 更直观。

多环境配置

application.yml          # 公共配置
application-dev.yml      # 开发环境
application-prod.yml     # 生产环境

application.yml 中通过 spring.profiles.active: dev 指定激活哪个环境。

激活方式(优先级从高到低):

  1. 命令行:java -jar app.jar --spring.profiles.active=prod
  2. 环境变量:SPRING_PROFILES_ACTIVE=prod
  3. application.yml 中的 spring.profiles.active

@Value 读取配置

@Value("${app.name}")
private String appName;

自定义配置

在 YAML 中写:

app:
  name: 学生管理系统
  version: 1.0.0

在代码中读:

@Value("${app.name}")
private String appName;

今日任务

  1. 修改 spring.profiles.activeprod,观察端口和日志变化
  2. HelloController 中用 @Value 注入 app.name,新增接口返回应用名称
  3. 想一想为什么数据库密码、API Key 要放在配置文件中而不是代码里?

第 7 天:整合与总结

全局异常处理

复习 exception/GlobalExceptionHandler.java

@RestControllerAdvice                     // 拦截所有 Controller 的异常
public class GlobalExceptionHandler {

    @ExceptionHandler(MethodArgumentNotValidException.class)  // 参数校验失败
    public ResponseEntity<?> handleValidation(...) { ... }

    @ExceptionHandler(Exception.class)    // 兜底:所有未捕获的异常
    public ResponseEntity<?> handleAll(...) { ... }
}

有了这个Controller 的任何异常都会被拦截并返回友好的 JSON 错误信息。

参数校验Bean Validation

Student 类中的注解:

@NotBlank(message = "姓名不能为空")
private String name;

@Min(value = 1, message = "年龄必须大于 0")
private int age;

@Email(message = "邮箱格式不正确")
private String email;

Controller 中用 @Valid 触发校验:

@PostMapping
public ResponseEntity<?> add(@Valid @RequestBody Student student) { ... }

本周完整的请求处理流程

1. HTTP 请求到达 → DispatcherServlet 接收
2. 根据 URL + HTTP Method 匹配 Controller 方法
3. @Valid 触发参数校验(失败 → GlobalExceptionHandler 捕获 → 返回 400
4. Controller 调用 Service 处理业务逻辑
5. Service 调用 Repository 存取数据
6. 结果返回 Controller封装为 ResponseEntity
7. Jackson 序列化为 JSON写入 HTTP 响应

测试(预习第 8 周)

打开 StudentControllerTest.java,这是一个集成测试示例,用 TestRestTemplate 模拟 HTTP 请求来测试真实接口。

运行方式:右键类名 → Run。观察所有测试通过。

最终验收清单

用 Postman 完成以下测试:

  • GET /api/students 返回 5 条预置数据
  • GET /api/students/1 返回张三的信息
  • GET /api/students?keyword=李 只返回李四
  • POST /api/students 新增一个学生,返回 201
  • POST /api/students 传空 name返回 400 校验错误
  • PUT /api/students/1 修改张三的年龄,返回更新后数据
  • DELETE /api/students/1 删除张三,返回成功
  • DELETE /api/students/999 删除不存在的,返回 404
  • 重启应用,之前新增/修改的数据被重置(内存存储)

本周产出

一个完整的 RESTful 学生管理 API 服务,具备:

✅ 三层分层架构Controller → Service → Repository
✅ 完整的 CRUD 接口GET/POST/PUT/DELETE
✅ 模糊搜索功能
✅ 统一 JSON 响应格式code + message + data
✅ 参数校验(@Valid + Bean Validation
✅ 全局异常处理(@RestControllerAdvice
✅ 多环境配置dev/prod
✅ 启动时预置测试数据CommandLineRunner
✅ 基础集成测试

常见问题排查

问题 原因 解决
启动报端口占用 8080 端口已被占用 application.yml 中的 server.port
@Autowired 报红 类上没有 @Service 等注解 检查是否漏了 @Service / @Repository / @Component
找不到 Bean 类不在启动类所在包的子包下 Spring 默认扫描启动类同级及子包
POST 请求 415 Content-Type 没设对 Postman 中 Body 选 raw → JSON
中文乱码 未设编码 Spring Boot 默认 UTF-8如果乱码检查 Postman 设置
JSON 返回 406 可能缺少 Jackson 依赖 spring-boot-starter-web 已包含,检查 pom.xml
修改代码不生效 需要重启 后续第 8 周会学 DevTools 热重载

本周核心:理解 Spring 的 IoC/DI 思想 → 掌握分层架构 → 能写标准的 RESTful CRUD API。 这 7 天你建立的是整个 Spring 技术栈的地基,后续 JPA、Security、AI 集成都是在这个地基上添砖加瓦。