SpringBoot请求响应详解
SpringBoot 请求响应详解
本章位置:第二阶段 Java 核心框架
前置知识:Servlet、HTTP、JSON、JavaBean、MyBatis
本章内容:SpringBoot Web 请求与响应
下一篇:RESTful
学习目标:理解 SpringBoot 如何接收 HTTP 请求、完成参数绑定、调用 Controller 方法并返回文本、对象、JSON,以及如何排查常见 Web 请求问题。
一、为什么学 SpringBoot 请求响应
前面 Servlet 阶段,我们处理请求要写:
@WebServlet(
"/student/detail"
)
public class StudentDetailServlet
extends HttpServlet {
@Override
protected void doGet(
HttpServletRequest request,
HttpServletResponse response
)
throws IOException {
String idText =
request.getParameter(
"id"
);
// 参数转换
// 业务调用
// 响应输出
}
}
现在进入 SpringBoot 后,大量模板代码会被框架接管。
例如:
@GetMapping(
"/student/detail"
)
public Student detail(
Long id
) {
return studentService.findById(
id
);
}
SpringBoot 会帮我们:
接收 HTTP 请求
匹配 URL
找到 Controller
读取请求参数
自动类型转换
调用 Java 方法
把返回值转 JSON
写回 HTTP Response
二、SpringBoot Web 请求整体流程
最重要的一张图:
浏览器 / Vue / Postman
↓
HTTP Request
↓
Tomcat
↓
DispatcherServlet
↓
HandlerMapping
↓
Controller
↓
Service
↓
Mapper
↓
Database
↓
Controller 返回结果
↓
HttpMessageConverter
↓
JSON / Text
↓
HTTP Response
↓
客户端
三、SpringBoot 和 Servlet 的关系
SpringBoot Web 并没有“抛弃 Servlet”。
Spring MVC 核心仍然建立在:
Servlet 体系
之上。
核心 Servlet:
DispatcherServlet
它相当于:
Spring MVC 的前端控制器
四、DispatcherServlet 是什么
以前:
每一个 URL
对应一个 Servlet
现在:
大量请求
先进入 DispatcherServlet
然后由 Spring 根据:
@RequestMapping
@GetMapping
@PostMapping
找到具体 Controller 方法。
五、为什么叫前端控制器
这里“前端”不是:
Vue / HTML 前端
而是:
所有请求进入业务 Controller 之前,先经过的统一入口。
也就是:
Front Controller Pattern
六、创建 SpringBoot Web 项目后的基础结构
常见:
src/main/java
└─ com.example.demo
├─ DemoApplication.java
├─ controller
│ └─ StudentController.java
├─ service
│ └─ StudentService.java
├─ mapper
│ └─ StudentMapper.java
└─ entity
└─ Student.java
七、SpringBoot 启动类
@SpringBootApplication
public class DemoApplication {
public static void main(
String[] args
) {
SpringApplication.run(
DemoApplication.class,
args
);
}
}
八、@SpringBootApplication
它是一个组合注解。
可以简单理解为组合了:
@Configuration
@EnableAutoConfiguration
@ComponentScan
当前重点不是底层细节。
先理解:
它标记 SpringBoot 启动类
九、SpringBoot 默认内嵌 Tomcat
传统项目:
先安装 Tomcat
再部署 WAR
SpringBoot Web 常见:
项目内部已经带内嵌 Tomcat
直接运行:
main()
就可以启动 Web 服务。
十、默认端口
默认:
8080
访问:
http://localhost:8080
十一、修改端口
application.properties:
server.port=8081
或者 application.yml:
server:
port: 8081
十二、Controller 是什么
Controller:
负责接收 HTTP 请求
调用业务逻辑
返回响应结果
这和以前 Servlet 的 Controller 职责一致。
十三、@RestController
最常见:
@RestController
public class HelloController {
}
表示:
这是一个 Controller
方法返回值直接写入响应体
十四、@RestController 等价理解
可以简单理解:
@Controller
+
@ResponseBody
十五、@Controller
@Controller
public class PageController {
}
更传统地用于:
返回页面 View
例如:
JSP
Thymeleaf
十六、@ResponseBody
如果:
@Controller
的方法加:
@ResponseBody
返回值会直接写入:
HTTP Response Body
十七、第一个接口
@RestController
public class HelloController {
@GetMapping(
"/hello"
)
public String hello() {
return "Hello SpringBoot";
}
}
访问:
GET /hello
响应:
Hello SpringBoot
十八、@GetMapping
@GetMapping(
"/hello"
)
表示:
只处理 GET 请求
十九、@PostMapping
@PostMapping(
"/student"
)
表示:
只处理 POST
二十、@PutMapping
@PutMapping(
"/student"
)
处理:
PUT
二十一、@DeleteMapping
@DeleteMapping(
"/student/{id}"
)
处理:
DELETE
二十二、@PatchMapping
处理:
PATCH
例如:
@PatchMapping(
"/student/{id}"
)
二十三、@RequestMapping
最通用:
@RequestMapping(
"/student"
)
可以放:
类上
方法上
二十四、类级别映射
@RestController
@RequestMapping(
"/students"
)
public class StudentController {
}
方法:
@GetMapping(
"/list"
)
最终路径:
/students/list
二十五、推荐的 Controller 路径结构
@RestController
@RequestMapping(
"/students"
)
public class StudentController {
@GetMapping
public List<Student> list() {
}
@GetMapping(
"/{id}"
)
public Student detail() {
}
@PostMapping
public Student add() {
}
@PutMapping(
"/{id}"
)
public void update() {
}
@DeleteMapping(
"/{id}"
)
public void delete() {
}
}
这会自然过渡到下一篇:
RESTful
二十六、请求参数有哪几类
SpringBoot Web 中常见参数来源:
Query String
Path Variable
Form Data
JSON Body
Header
Cookie
Multipart File
二十七、Query String
例如:
GET /students?pageNum=1&pageSize=10
参数:
pageNum
pageSize
二十八、最简单参数绑定
@GetMapping(
"/search"
)
public String search(
String keyword
) {
return keyword;
}
请求:
/search?keyword=java
Spring 会自动:
keyword=java
绑定给:
String keyword
二十九、参数名必须匹配
请求:
?keyword=java
方法:
String keyword
匹配。
如果参数名完全不同:
就需要显式 @RequestParam
三十、@RequestParam
推荐明确写:
@GetMapping(
"/search"
)
public String search(
@RequestParam(
"keyword"
)
String keyword
) {
return keyword;
}
三十一、@RequestParam required
默认通常:
required = true
如果没传:
可能返回 400
三十二、可选参数
@RequestParam(
value = "keyword",
required = false
)
String keyword
这样:
不传也可以
三十三、默认值
@RequestParam(
value = "pageNum",
defaultValue = "1"
)
int pageNum
如果用户没传:
pageNum = 1
三十四、分页参数示例
@GetMapping
public String list(
@RequestParam(
defaultValue = "1"
)
int pageNum,
@RequestParam(
defaultValue = "10"
)
int pageSize
) {
return pageNum
+ "-"
+ pageSize;
}
三十五、Spring 自动类型转换
请求:
?pageNum=10
方法参数:
int pageNum
Spring 自动:
String
→
int
三十六、类型转换失败
如果请求:
?pageNum=abc
方法:
int pageNum
Spring 无法转换:
通常返回 400
三十七、包装类型和基本类型
如果参数可能缺失:
Integer age
通常比:
int age
更合适。
因为:
Integer
可以为 null
三十八、PathVariable 是什么
路径参数:
/students/10
其中:
10
就是:
Path Variable
三十九、@PathVariable
@GetMapping(
"/students/{id}"
)
public String detail(
@PathVariable
Long id
) {
return "id="
+ id;
}
请求:
/students/10
得到:
id = 10
四十、显式指定变量名
@PathVariable(
"id"
)
Long studentId
这样路径:
{id}
可以绑定到:
studentId
四十一、RequestParam 和 PathVariable 区别
例如查询:
/students?id=10
使用:
@RequestParam
RESTful 风格:
/students/10
使用:
@PathVariable
四十二、表单参数绑定 JavaBean
前端表单:
studentNo=20260001
name=张三
age=20
major=软件工程
JavaBean:
public class Student {
private String studentNo;
private String name;
private Integer age;
private String major;
// Getter / Setter
}
Controller:
@PostMapping(
"/students/form"
)
public Student add(
Student student
) {
return student;
}
Spring 可以自动:
请求参数
→
Student
四十三、自动绑定为什么能实现
本质会结合:
JavaBean
反射
内省
类型转换
根据参数名:
name
age
major
找到属性:
setName()
setAge()
setMajor()
四十四、@ModelAttribute
表单/Query 参数绑定对象可以显式写:
@PostMapping(
"/students/form"
)
public Student add(
@ModelAttribute
Student student
) {
return student;
}
很多时候:
可以省略
四十五、JSON 请求
前后端分离最常见:
POST /students
Content-Type: application/json
Body:
{
"studentNo": "20260001",
"name": "张三",
"age": 20,
"major": "软件工程"
}
四十六、@RequestBody
Controller:
@PostMapping(
"/students"
)
public Student add(
@RequestBody
Student student
) {
return student;
}
表示:
读取 HTTP Request Body
按照 JSON 解析
转换成 Student
四十七、为什么 JSON 必须用 @RequestBody
如果只是:
Student student
Spring 更倾向于:
表单 / Query 参数绑定
如果请求体是 JSON:
通常需要 @RequestBody
四十八、@RequestBody 背后是谁转换
Spring MVC 使用:
HttpMessageConverter
把:
JSON
↔
Java Object
四十九、Jackson
SpringBoot Web 默认常见使用:
Jackson
完成:
Java 对象
↔
JSON
转换。
五十、Java 对象返回为什么自动变 JSON
例如:
@GetMapping(
"/student"
)
public Student getStudent() {
Student student =
new Student();
student.setName(
"张三"
);
student.setAge(
20
);
return student;
}
因为:
@RestController
最终 Spring 会:
Student
↓
Jackson
↓
JSON
响应:
{
"name": "张三",
"age": 20
}
五十一、List 也可以自动转 JSON
@GetMapping(
"/students"
)
public List<Student> list() {
return studentService.findAll();
}
返回:
[
{
"id": 1,
"name": "张三"
},
{
"id": 2,
"name": "李四"
}
]
五十二、Map 响应
@GetMapping(
"/info"
)
public Map<String, Object> info() {
Map<String, Object> map =
new HashMap<>();
map.put(
"code",
200
);
map.put(
"message",
"success"
);
return map;
}
会自动变 JSON。
五十三、为什么不推荐项目里到处返回 Map
Map 优点:
快
灵活
缺点:
字段没有类型约束
容易拼错 key
IDE 不好提示
维护困难
更推荐:
DTO
VO
统一响应对象
五十四、统一响应对象
常见:
public class Result<T> {
private Integer code;
private String message;
private T data;
}
五十五、Result 成功方法
public static <T>
Result<T> success(
T data
) {
Result<T> result =
new Result<>();
result.setCode(
200
);
result.setMessage(
"success"
);
result.setData(
data
);
return result;
}
五十六、失败方法
public static <T>
Result<T> fail(
Integer code,
String message
) {
Result<T> result =
new Result<>();
result.setCode(
code
);
result.setMessage(
message
);
return result;
}
五十七、Controller 使用统一响应
@GetMapping(
"/students/{id}"
)
public Result<Student> detail(
@PathVariable
Long id
) {
Student student =
studentService.findById(
id
);
return Result.success(
student
);
}
五十八、统一响应的优点
前端永远可以按:
code
message
data
读取。
减少:
接口格式混乱
五十九、业务 code 和 HTTP 状态码不是一回事
例如:
{
"code": 404,
"message": "学生不存在"
}
并不代表 HTTP 一定:
404
很多项目会:
HTTP 200
+
业务 code
也有项目会:
正确使用 HTTP 状态码
两种风格都存在。
更推荐:
HTTP 状态码表达协议层结果
业务 code 表达业务层结果
六十、ResponseEntity
如果需要控制 HTTP 状态码:
@GetMapping(
"/students/{id}"
)
public ResponseEntity<Student> detail(
@PathVariable
Long id
) {
Student student =
studentService.findById(
id
);
if (
student == null
) {
return ResponseEntity
.notFound()
.build();
}
return ResponseEntity.ok(
student
);
}
六十一、ResponseEntity 能做什么
控制:
状态码
响应头
响应体
六十二、返回 201 Created
新增资源:
return ResponseEntity
.status(
HttpStatus.CREATED
)
.body(
student
);
六十三、返回 204 No Content
删除成功:
return ResponseEntity
.noContent()
.build();
六十四、请求头 @RequestHeader
@GetMapping(
"/header"
)
public String header(
@RequestHeader(
"User-Agent"
)
String userAgent
) {
return userAgent;
}
六十五、可选请求头
@RequestHeader(
value = "X-Token",
required = false
)
String token
六十六、获取全部 Header
可以使用:
HttpHeaders
或者:
@RequestHeader
Map<String, String> headers
六十七、Cookie 参数
可以:
@CookieValue(
value = "JSESSIONID",
required = false
)
String sessionId
六十八、原生 HttpServletRequest 仍然可以使用
Spring MVC 并没有禁止原生 Servlet API。
例如:
@GetMapping(
"/request-info"
)
public String requestInfo(
HttpServletRequest request
) {
return request.getRequestURI();
}
六十九、什么时候用原生 request
当 Spring 已经能自动绑定时:
优先自动绑定
只有特殊场景才直接使用:
HttpServletRequest
HttpServletResponse
七十、为什么优先自动绑定
因为:
代码更少
类型更明确
更容易测试
业务代码不依赖 Servlet API
七十一、多个 Query 参数绑定对象
例如查询:
/students?keyword=java&minAge=18&maxAge=25
定义:
public class StudentQuery {
private String keyword;
private Integer minAge;
private Integer maxAge;
}
Controller:
@GetMapping(
"/students/search"
)
public List<Student> search(
StudentQuery query
) {
return studentService.search(
query
);
}
Spring 自动绑定。
七十二、DTO 是什么
DTO:
Data Transfer Object
用于:
请求或响应的数据传输
例如:
public class StudentCreateDTO {
private String studentNo;
private String name;
private Integer age;
}
七十三、为什么不要所有接口都直接用 Entity
数据库实体:
可能包含很多内部字段
例如:
createTime
updateTime
deleted
passwordHash
前端新增时:
根本不应该提交这些字段
所以真实项目常分:
Entity
DTO
VO
七十四、Entity
主要对应:
数据库表
七十五、DTO
主要对应:
客户端提交的数据
七十六、VO
VO:
View Object
常用于:
返回给前端的数据结构
七十七、JSON 字段名和 Java 属性名
JSON:
{
"studentNo": "20260001"
}
Java:
private String studentNo;
名称匹配:
自动绑定
七十八、JSON 字段名不一样怎么办
Jackson 可以通过:
@JsonProperty(
"student_no"
)
private String studentNo;
让:
student_no
↔
studentNo
七十九、JSON 日期格式
例如:
private LocalDateTime createTime;
实际项目可能需要配置:
日期序列化格式
例如:
@JsonFormat(
pattern = "yyyy-MM-dd HH:mm:ss"
)
八十、JSON 忽略字段
例如密码:
@JsonIgnore
private String password;
返回 JSON 时:
不输出
八十一、请求体只能读取一次吗
底层:
HTTP Request Body
本质是输入流。
Spring 会统一处理:
@RequestBody
一般不要自己又:
手动读 InputStream
再让框架读一次。
八十二、Content-Type 必须匹配
请求发送 JSON:
Content-Type: application/json
Controller:
@RequestBody Student student
最匹配。
如果 Content-Type 错误:
可能 415
八十三、415 Unsupported Media Type
常见原因:
接口要求 JSON
客户端却发 form-data
或者 Content-Type 没设置正确
八十四、400 Bad Request
SpringBoot 中常见原因:
参数缺失
类型转换失败
JSON 格式错误
@RequestBody 解析失败
八十五、404 Not Found
常见:
URL 错
Controller 不在扫描范围
@RequestMapping 路径不对
应用启动失败
八十六、405 Method Not Allowed
例如:
接口只允许 POST
客户端发送:
GET
会返回:
405
八十七、500 Internal Server Error
通常:
Controller / Service / Mapper 内部异常
例如:
NullPointerException
SQL 错误
业务异常没处理
八十八、请求参数日志怎么排查
浏览器:
F12
→ Network
Postman:
Params
Body
Headers
SpringBoot 控制台:
看异常堆栈
八十九、Postman 测试 Query 参数
例如:
GET /students/search
Params:
keyword=java
pageNum=1
九十、Postman 测试 JSON
Body:
raw
↓
JSON
内容:
{
"studentNo": "20260003",
"name": "王五",
"age": 20,
"major": "人工智能"
}
Header:
Content-Type: application/json
九十一、文件上传简单预览
Controller:
@PostMapping(
"/upload"
)
public String upload(
@RequestParam(
"file"
)
MultipartFile file
) {
return file.getOriginalFilename();
}
请求类型:
multipart/form-data
九十二、MultipartFile 常用方法
getOriginalFilename()
getSize()
getContentType()
getInputStream()
transferTo(...)
九十三、文件上传安全注意
不要直接相信:
原始文件名
Content-Type
扩展名
真实项目要校验:
文件大小
文件类型
保存路径
文件名
权限
九十四、多个请求参数集合
例如:
?id=1&id=2&id=3
可以:
@RequestParam(
"id"
)
List<Long> ids
Spring 可自动绑定集合。
九十五、数组参数
@RequestParam(
"id"
)
Long[] ids
也可以。
九十六、PathVariable 多参数
@GetMapping(
"/schools/{schoolId}/students/{studentId}"
)
public String detail(
@PathVariable
Long schoolId,
@PathVariable
Long studentId
) {
return schoolId
+ ":"
+ studentId;
}
九十七、请求路径命名建议
推荐:
名词
复数资源
例如:
/students
/users
/orders
而不是大量:
/getStudent
/addStudent
/deleteStudent
下一篇 RESTful 会详细讲。
九十八、Controller 不应该直接写 SQL
错误:
@GetMapping(
"/students"
)
public List<Student> list() {
// JDBC
// SQL
}
推荐:
Controller
↓
Service
↓
Mapper
九十九、Controller 应该负责什么
接收请求
参数绑定
基础参数校验
调用 Service
返回响应
一百、Service 负责什么
业务逻辑
事务
业务校验
多个 Mapper 协调
一百零一、Mapper 负责什么
数据库读写
一百零二、一个完整新增接口
DTO:
public class StudentCreateDTO {
private String studentNo;
private String name;
private Integer age;
private String major;
// Getter / Setter
}
Controller:
@PostMapping(
"/students"
)
public Result<Student> add(
@RequestBody
StudentCreateDTO dto
) {
Student student =
studentService.create(
dto
);
return Result.success(
student
);
}
一百零三、一个完整详情接口
@GetMapping(
"/students/{id}"
)
public Result<Student> detail(
@PathVariable
Long id
) {
Student student =
studentService.findById(
id
);
if (
student == null
) {
return Result.fail(
404,
"学生不存在"
);
}
return Result.success(
student
);
}
一百零四、一个完整分页接口
@GetMapping(
"/students"
)
public Result<PageResult<Student>>
list(
@RequestParam(
defaultValue = "1"
)
int pageNum,
@RequestParam(
defaultValue = "10"
)
int pageSize,
@RequestParam(
required = false
)
String keyword
) {
PageResult<Student> page =
studentService.findPage(
keyword,
pageNum,
pageSize
);
return Result.success(
page
);
}
一百零五、返回 JSON 的完整结构
可能:
{
"code": 200,
"message": "success",
"data": {
"records": [],
"total": 100,
"pageNum": 1,
"pageSize": 10
}
}
一百零六、前端 Vue 为什么喜欢这种格式
因为前端可以统一:
判断 code
显示 message
读取 data
所有接口结构一致。
一百零七、参数校验基础
手工:
if (
dto.getName() == null
||
dto.getName().isBlank()
) {
return Result.fail(
400,
"姓名不能为空"
);
}
但真实项目大量手写会:
重复
一百零八、Bean Validation 预览
后面可以使用:
jakarta.validation
例如:
@NotBlank(
message = "姓名不能为空"
)
private String name;
Controller:
@Valid
@RequestBody
StudentCreateDTO dto
一百零九、SpringBoot 3 使用 jakarta 包
需要注意:
SpringBoot 3 基于:
Jakarta EE
常见:
jakarta.validation.Valid
jakarta.servlet.http.HttpServletRequest
这和你之前 Tomcat 8.5 的:
javax.servlet
属于不同命名空间时代。
一百一十、不要把 javax 和 jakarta 乱混
SpringBoot 3 项目里:
优先使用 jakarta.*
传统 Tomcat 8.5 Servlet 项目:
通常使用 javax.*
一百一十一、返回 String 的含义取决于注解
如果类:
@RestController
返回:
"hello"
通常是:
响应正文
一百一十二、@Controller 返回 String
如果:
@Controller
方法返回:
"student-list"
通常解释为:
View 名称
而不是直接响应字符串。
一百一十三、这就是 @RestController 和 @Controller 的重要区别
@RestController
更适合前后端分离 JSON API
@Controller
更适合服务端页面渲染
一百一十四、produces
可以指定响应类型:
@GetMapping(
value = "/hello",
produces = "text/plain;charset=UTF-8"
)
一百一十五、consumes
可以限制请求类型:
@PostMapping(
value = "/students",
consumes = "application/json"
)
表示:
只接受 JSON
一百一十六、如果 consumes 不匹配
可能:
415 Unsupported Media Type
一百一十七、URL 编码
Query 参数包含中文:
keyword=软件工程
浏览器通常会进行:
URL Encoding
Spring / Tomcat 再解析。
现代 SpringBoot 默认 UTF-8 处理通常比老 Servlet 项目方便很多。
一百一十八、请求参数和 JSON 不要混淆
例如:
GET /students?id=1
使用:
@RequestParam
JSON:
{
"id": 1
}
使用:
@RequestBody
一百一十九、Path 参数不要用 RequestParam
请求:
/students/1
应该:
@PathVariable
一百二十、常见错误:忘记 @RequestBody
前端发 JSON:
{
"name": "张三"
}
Controller:
public void add(
Student student
)
结果:
对象字段可能全是 null
此时优先检查:
是不是应该加 @RequestBody
一百二十一、常见错误:Content-Type 不对
Postman Body 选择:
raw
但没有正确:
application/json
可能导致:
@RequestBody 无法解析
一百二十二、常见错误:JSON 格式错误
错误:
{
"name": "张三",
}
最后多一个逗号。
可能:
400
JSON parse error
一百二十三、常见错误:数字字段传文字
Java:
private Integer age;
JSON:
{
"age": "abc"
}
可能:
400
类型转换失败
一百二十四、常见错误:路径重复
类:
@RequestMapping(
"/students"
)
方法:
@GetMapping(
"/students"
)
最终:
/students/students
很多初学者会误以为:
还是 /students
一百二十五、常见错误:Controller 没被扫描
启动类:
com.example.DemoApplication
Controller 却在:
com.other.controller
默认组件扫描可能扫不到。
推荐目录:
启动类放最外层根包
例如:
com.example
├─ DemoApplication
├─ controller
├─ service
└─ mapper
一百二十六、常见错误:404 但应用正常启动
检查:
端口
context-path
类级 RequestMapping
方法级 Mapping
Controller 扫描
一百二十七、server.servlet.context-path
可以配置:
server.servlet.context-path=/api
那么:
/hello
真实访问:
/api/hello
一百二十八、为什么不建议随便设置 context-path
前后端联调时经常造成:
路径多一层
404
学习阶段如果不是必要:
可以保持默认空 context path
一百二十九、常见错误:405
例如:
@PostMapping(
"/students"
)
却在浏览器地址栏直接访问:
GET /students
会:
405
一百三十、常见错误:415
通常:
Content-Type 与 @RequestBody/consumes 不匹配
一百三十一、常见错误:406
可能是:
客户端 Accept
和服务端可产生响应类型
不匹配。
初学阶段遇到较少。
一百三十二、常见错误:500
查看:
Caused by
可能:
Service 异常
Mapper 异常
SQL 异常
空指针
一百三十三、日志排查顺序
客户端
↓
URL
↓
Method
↓
Headers
↓
Content-Type
↓
Params / Body
↓
Controller 是否进入
↓
Service
↓
Mapper
↓
SQL
↓
Response
一百三十四、开发阶段可以打印简单日志
例如:
System.out.println(
"进入 add"
);
用来确认:
请求是否进入 Controller
但实际项目更推荐:
日志框架
一百三十五、SpringBoot 默认日志
通常使用:
SLF4J
+
Logback
以后可以写:
private static final Logger log =
LoggerFactory.getLogger(
StudentController.class
);
一百三十六、不要打印密码
日志中不要输出:
密码
Token
身份证
银行卡号
完整敏感数据
一百三十七、Controller 方法参数可以很多吗
技术上可以:
public Result<?> search(
String keyword,
Integer minAge,
Integer maxAge,
String major,
Integer pageNum,
Integer pageSize
)
但参数越来越多时:
建议封装 Query DTO
一百三十八、Query DTO
public class StudentQueryDTO {
private String keyword;
private Integer minAge;
private Integer maxAge;
private String major;
private Integer pageNum = 1;
private Integer pageSize = 10;
}
Controller:
@GetMapping(
"/students"
)
public Result<?> list(
StudentQueryDTO query
) {
return Result.success(
studentService.findPage(
query
)
);
}
一百三十九、请求对象和响应对象分离
例如:
StudentCreateDTO
新增请求
StudentUpdateDTO
修改请求
StudentQueryDTO
查询参数
StudentVO
响应对象
这样接口更清晰。
一百四十、为什么修改 DTO 和新增 DTO 可能不同
新增:
id 不需要前端传
修改:
可能需要 id
新增必填字段:
可能比修改多
所以不要强迫:
一个 DTO 走天下
一百四十一、RequestBody 一般配 POST/PUT/PATCH
因为这些请求通常:
带请求体
GET:
通常不推荐依赖 JSON Body
一百四十二、GET 查询条件适合什么
@RequestParam
Query DTO
@PathVariable
一百四十三、接口设计例子
查询列表:
GET /students?pageNum=1&pageSize=10
详情:
GET /students/1
新增:
POST /students
JSON Body
修改:
PUT /students/1
JSON Body
删除:
DELETE /students/1
这就是下一章 RESTful 的核心风格。
一百四十四、Servlet 写法和 SpringBoot 对照
Servlet:
String idText =
request.getParameter(
"id"
);
Long id =
Long.valueOf(
idText
);
SpringBoot:
@RequestParam
Long id
一百四十五、Servlet JSON 输出和 SpringBoot 对照
Servlet:
response.setContentType(
"application/json;charset=UTF-8"
);
response
.getWriter()
.write(
json
);
SpringBoot:
@RestController
public Student detail() {
return student;
}
一百四十六、Servlet 路由和 SpringBoot 对照
Servlet:
@WebServlet(
"/student/list"
)
SpringBoot:
@GetMapping(
"/students"
)
一百四十七、Servlet 参数绑定和 SpringBoot 对照
Servlet:
request.getParameter(
"name"
);
SpringBoot:
@RequestParam
String name
一百四十八、Servlet 路径参数
原生 Servlet:
要自己分析 URI / PathInfo
SpringBoot:
@PathVariable
Long id
非常方便。
一百四十九、Servlet JSON 解析
原生 Servlet:
自己读 InputStream
再调用 Jackson
SpringBoot:
@RequestBody
Student dto
框架自动完成。
一百五十、SpringBoot 为什么代码少很多
因为 Spring MVC 帮我们封装了:
请求路由
参数绑定
类型转换
JSON 序列化
JSON 反序列化
状态码处理
异常处理扩展
文件上传
一百五十一、但底层逻辑没有消失
仍然是:
HTTP
↓
Tomcat
↓
Servlet
↓
Spring MVC
↓
Controller
只是:
框架替你写了模板代码
一百五十二、练习 1:Hello Controller
创建:
@GetMapping(
"/hello"
)
返回:
Hello SpringBoot
一百五十三、练习 2:RequestParam
接口:
GET /hello?name=张三
返回:
你好,张三
一百五十四、练习 3:默认参数
GET /page
参数:
pageNum 默认 1
pageSize 默认 10
一百五十五、练习 4:PathVariable
接口:
GET /students/10
方法:
@PathVariable
Long id
一百五十六、练习 5:表单绑定
提交:
studentNo
name
age
major
直接绑定:
Student student
一百五十七、练习 6:JSON 请求
Postman:
POST /students
Body:
{
"studentNo": "20260010",
"name": "张三",
"age": 20,
"major": "软件工程"
}
Controller:
@RequestBody
Student student
一百五十八、练习 7:JSON 响应
Controller 返回:
Student
观察 Postman:
自动 JSON
一百五十九、练习 8:List JSON
返回:
List<Student>
观察:
JSON 数组
一百六十、练习 9:统一 Result
创建:
Result<T>
所有接口统一返回:
code
message
data
一百六十一、练习 10:400
接口参数:
@RequestParam
Integer age
请求:
?age=abc
观察:
400
一百六十二、练习 11:405
接口只:
POST
使用浏览器 GET 访问。
观察:
405
一百六十三、练习 12:415
接口:
@RequestBody Student
Postman 故意:
Content-Type 设错误
观察:
415
一百六十四、练习 13:Query DTO
创建:
StudentQueryDTO
支持:
keyword
minAge
maxAge
pageNum
pageSize
自动绑定。
一百六十五、练习 14:完整学生 CRUD Controller
实现:
GET /students
GET /students/{id}
POST /students
PUT /students/{id}
DELETE /students/{id}
当前先不追求 RESTful 完整规范。
下一章再系统优化。
一百六十六、必须掌握的注解
@RestController
@Controller
@ResponseBody
@RequestMapping
@GetMapping
@PostMapping
@PutMapping
@DeleteMapping
@PatchMapping
@RequestParam
@PathVariable
@RequestBody
@ModelAttribute
@RequestHeader
@CookieValue
一百六十七、必须掌握的请求类型
Query String
Path Variable
Form Data
JSON Body
Header
Cookie
Multipart
一百六十八、必须掌握的响应方式
String
JavaBean
List
Map
Result<T>
ResponseEntity
一百六十九、必须掌握常见状态码
200
201
204
400
404
405
415
500
一百七十、必须回答的问题
学完后应该能回答:
1. SpringBoot Web 请求为什么仍然和 Servlet 有关系?
2. DispatcherServlet 是什么?
3. @RestController 和 @Controller 有什么区别?
4. @GetMapping 和 @PostMapping 有什么区别?
5. @RequestMapping 可以写在哪里?
6. @RequestParam 是什么?
7. @PathVariable 是什么?
8. RequestParam 和 PathVariable 有什么区别?
9. @RequestBody 是什么?
10. 为什么 JSON 请求通常要加 @RequestBody?
11. SpringBoot 为什么能自动把 JSON 转 Java 对象?
12. HttpMessageConverter 是什么?
13. Jackson 是做什么的?
14. JavaBean 为什么能自动接收表单参数?
15. DTO 是什么?
16. Entity、DTO、VO 有什么区别?
17. 为什么不推荐所有接口都直接返回 Map?
18. 为什么要统一响应格式?
19. ResponseEntity 有什么作用?
20. 400、404、405、415、500 分别常见于什么情况?
21. 为什么 SpringBoot 3 使用 jakarta 包?
22. 为什么 JSON 的 Content-Type 应该是 application/json?
23. Controller 应该负责什么?
24. Service 应该负责什么?
25. Mapper 应该负责什么?
一百七十一、SpringBoot 请求响应知识结构
SpringBoot Web
│
├─ 请求入口
│ ├─ Tomcat
│ └─ DispatcherServlet
│
├─ Controller
│ ├─ RestController
│ ├─ RequestMapping
│ ├─ GetMapping
│ ├─ PostMapping
│ └─ DeleteMapping
│
├─ 参数绑定
│ ├─ RequestParam
│ ├─ PathVariable
│ ├─ ModelAttribute
│ ├─ RequestBody
│ ├─ RequestHeader
│ └─ CookieValue
│
├─ 数据转换
│ ├─ Type Conversion
│ ├─ HttpMessageConverter
│ └─ Jackson
│
├─ 响应
│ ├─ String
│ ├─ Object
│ ├─ List
│ ├─ Result<T>
│ └─ ResponseEntity
│
└─ 错误
├─ 400
├─ 404
├─ 405
├─ 415
└─ 500
一百七十二、从 Servlet 到 SpringBoot
以前:
@WebServlet
↓
doGet / doPost
↓
request.getParameter
↓
response.getWriter
现在:
@GetMapping / @PostMapping
↓
Controller 方法
↓
自动参数绑定
↓
直接 return
对照:
Servlet Mapping
→
@RequestMapping
request.getParameter
→
@RequestParam
URI 分析
→
@PathVariable
手工读 JSON
→
@RequestBody
response.getWriter
→
@ResponseBody / RestController
手工 Jackson
→
HttpMessageConverter
一百七十三、本章总结
SpringBoot 请求响应的核心不是:
背注解
而是理解:
HTTP 请求
如何被 Spring MVC
映射成 Java 方法调用
完整流程:
客户端
↓
HTTP
↓
Tomcat
↓
DispatcherServlet
↓
Controller
↓
参数绑定
↓
Service
↓
Mapper
↓
Controller 返回值
↓
HttpMessageConverter
↓
JSON
↓
HTTP Response
最重要参数注解:
@RequestParam
Query 参数
@PathVariable
路径参数
@RequestBody
JSON 请求体
@RequestHeader
请求头
最重要 Controller 注解:
@RestController
@RequestMapping
@GetMapping
@PostMapping
最重要响应方式:
Object
List
Result<T>
ResponseEntity
排错时:
URL
↓
HTTP Method
↓
Content-Type
↓
Query / Path / Body
↓
Controller Mapping
↓
参数绑定
↓
Service
↓
Mapper
↓
Response
到这里,你已经从:
手写 Servlet 请求响应
正式过渡到:
SpringBoot / Spring MVC 请求响应
按照课程表,下一篇就是:
RESTful
会把:
URL 设计
GET
POST
PUT
DELETE
资源
状态码
统一接口规范
系统整理成一套真正的 API 设计方式。