✨前言:传参和状态管理,看似简单其实门道不少
在 Web 开发中,前端和后端最核心的交流方式就是“传参”,而“传参”除了涉及如何写代码获取参数,还藏着很多开发者容易忽略的细节:
为什么 URL 带了中文,后端拿到是乱码?
@PathVariable
和@RequestParam
到底怎么选?前端传 数据(#),后端拿不到值怎么办?
用户登录后,我要怎么“记住”他?
Cookie 和 Session 有啥区别,怎么用才优雅?
这些问题都是你在 Spring MVC 实战中一定会遇到的!本篇内容就是为了解决这些“看起来简单,实际容易踩坑”的问题。
📌 本期我们将系统讲解以下几个关键点:
多种传参方式(GET、POST、Path、JSON)
中文、空格、特殊字符(
# % & +
)等处理方式如何在 Spring MVC 中正确获取和管理 Cookie/Session
不仅让你学会“怎么用”,还帮你彻底搞懂“为什么这样用”。
📎 适合人群:
初学 Spring MVC 的小伙伴
使用前后端分离开发时遇到参数/状态问题的开发者
想彻底理解请求细节、编码和状态管理原理的同学、
🧭 第一章:Spring MVC 中的多种数据传参方式
前后端交互的核心是“请求—响应”,而传参方式直接决定了后端能否正确拿到前端发送的数据。Spring MVC 支持多种传参方式,每种方式都各有使用场景和注意事项。
🔹 1.1 Query 参数(GET 请求)
在 Web 开发中,最常见的请求类型之一就是 GET 请求,用于向后端“索取资源”。GET 请求通常会将参数拼接在 URL 的末尾,形式如下:
/search?keyword=java&page=1
这类参数就叫做 Query 参数,也称为 URL 查询参数。
✅ 使用 @RequestParam
获取参数
Spring MVC 中可以使用 @RequestParam
注解来接收 GET 请求的参数。
@GetMapping("/search")
public String search(@RequestParam("keyword") String keyword,
@RequestParam("page") int page) {
System.out.println("搜索关键词:" + keyword + ",页码:" + page);
return "searchResult";
}
如果你访问:
http://localhost:8080/search?keyword=springmvc&page=2
控制台输出将会是:
搜索关键词:springmvc,页码:2
🛠️ 默认值 & 是否必传配置
有些参数可能不是必传的。比如分页的 page
参数,默认展示第一页。这时候可以设置参数为可选,并给一个默认值:
@GetMapping("/search")
public String search(@RequestParam(defaultValue = "spring") String keyword,
@RequestParam(required = false, defaultValue = "1") int page) {
System.out.println("搜索关键词:" + keyword + ",页码:" + page);
return "searchResult";
}
解释:
required = false
:表示这个参数不是必须要传的。defaultValue = "..."
:如果参数没有传,则使用默认值。
🔍 小细节提示:
@RequestParam
不区分 GET/POST,本质上是从参数中提取数据。如果你传的是数组/列表,可以直接使用
List<String>
或String[]
接收。
@GetMapping("/tags")
public String tags(@RequestParam List<String> tag) {
// /tags?tag=java&tag=spring&tag=web
System.out.println(tag); // [java, spring, web]
return "tagList";
}
🔹 1.2 传递参数(POST 请求)
✅传递单个参数
接收单个参数,在SpringMVC中直接⽤⽅法中的参数就可以,⽐如以下代码:
@RequestMapping("/user")
public class user {
@RequestMapping("/m1")
public String m1(String name) {
return "接受参数name"+ name;
}
}
咱们使⽤浏览器发送请求并传参
http://127.0.0.1:8080/user/m1?name=spring
可以看到,后端程序正确拿到了name参数的值.
SpringMVC会根据⽅法的参数名,找到对应的参数,赋值给⽅法
如果参数不⼀致,是获取不到参数的.
⽐如请求URL:http://127.0.0.1:8080/user/m1?name1=spring
注意事项
使⽤基本类型来接收参数时,参数必须传(除boolean类型),否则会报500错误 类型不匹配时,会报400错误
@RequestMapping("/age")
public String age(int age) {
return "接受的数字为age "+ age;
}
1. 正常传递参数
http://127.0.0.1:8080/user/age?age=1
浏览器响应情况:
通过Fiddler观察请求和响应,HTTP响应状态码为200,Content-Type 为text/html
2. 不传递age参数
http://127.0.0.1:8080/user/age
浏览器响应情况:
通过Fiddler观察请求和响应,HTTP响应状态码为500
3. 传递参数类型不匹配
http://127.0.0.1:8080/user/age?age=abc
浏览器响应情况:
通过Fiddler观察请求和响应, HTTP响应状态码为400
对于包装类型,如果不传对应参数,Spring接收到的数据则为null
所以企业开发中,对于参数可能为空的数据,建议使⽤包装类型
✅传递多个参数
和接收单个参数⼀样,直接使⽤⽅法的参数接收即可.使⽤多个形参.
@RequestMapping("/m2")
public String m2(String name,String password) {
return "返回的参数 + name" + name +" password" + password;
}
使⽤浏览器发送请求并传参:http://127.0.0.1:8080/user/m2?name=zhangsan&password=123456
可以看到,后端程序正确拿到了name和password参数的值
当有多个参数时,前后端进⾏参数匹配时,是以参数的名称进⾏匹配的,因此参数的位置是不影响后 端获取参数的结果.
⽐如访问:http://127.0.0.1:8080/user/m2?password=123456&name=zhangsan
✅传递对象
如果参数⽐较多时,⽅法声明就需要有很多形参.并且后续每次新增⼀个参数,也需要修改⽅法声明. 我们不妨把这些参数封装为⼀个对象.
SpringMVC也可以⾃动实现对象参数的赋值
@RequestMapping("/m3")
public String m3(Info info) {
return info.toString();
}
package com.example.demo;
public class Info {
private String user;
public String getPassword() {
return password;
}
@Override
public String toString() {
return "Info{" +
"user='" + user + '\'' +
", password='" + password + '\'' +
", age=" + age +
'}';
}
public void setPassword(String password) {
this.password = password;
}
public String getUser() {
return user;
}
public void setUser(String user) {
this.user = user;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
private String password;
private int age;
}
使⽤浏览器发送请求并传参: http://127.0.0.1:8080/user/m3?age=5&name=zhangsan&password=123456
可以看到,后端程序正确拿到了Info对象⾥各个属性的值
✅传递数组
SpringMVC可以⾃动绑定数组参数的赋值
后端实现代码:
@RequestMapping("/m5")
public String method5(String[] arrayParam) {
return Arrays.toString(arrayParam);
}
使⽤浏览器发送请求并传参:
数组参数:请求参数名与形参数组名称相同且请求参数为多个,后端定义数组类型形参即可接收参数
http://127.0.0.1:8080/param/m5?arrayParam=zhangsan&arrayParam=lisi&arrayParam=wangwu
或者使⽤http://127.0.0.1:8080/param/m6?listParam=zhangsan%2clisi%2cwangwu
浏览器响应结果:
✅传递集合
集合参数:和数组类似,同⼀个请求参数名有为多个,且需要使⽤@RequestParam 绑定参数关系
默认情况下,请求中参数名相同的多个值,是封装到数组.如果要封装到集合,要使⽤ @RequestParam 绑定参数关系
请求⽅式和数组类似:
浏览器传参:
⽅式⼀:http://127.0.0.1:8080/param/m6?listParam=zhangsan&listParam=lisi&listParam=wangwu
⽅式⼆:http://127.0.0.1:8080/param/m6?listParam=zhangsan%2clisi%2cwangwu
%2c是逗号的转义编码,解码后的url为:http://127.0.0.1:8080/param/m6?listParam=zhangsan,lisi,wangwu
后端接收代码:
@RequestMapping("/m6")
public String method6(@RequestParam List<String> listParam){
return "size:"+listParam.size() + ",listParam:"+listParam;
}
✅传递JSON数据
好的,这里我们来专门总结一下如何在 Spring MVC 中传递 JSON 数据(即:前端 → 后端)以及后端如何正确解析这些 JSON 请求。非常适合初学者掌握前后端分离接口开发的基础。
在前后端分离开发中,前端通常使用 fetch
、axios
、jQuery.ajax
等方式向后端发送 JSON 数据,而不是传统的表单提交。Spring MVC 提供了强大的机制来处理这些 JSON 请求。
1.2.1 前端如何发送 JSON 数据
使用 fetch
示例(原生 JS):
fetch("/api/user", {
method: "POST",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify({
username: "jack",
password: "123456"
})
});
使用 axios
示例:
axios.post("/api/user", {
username: "jack",
password: "123456"
});
关键点:
必须设置请求头
Content-Type: application/json
body
或data
是一个 JSON 对象(会被序列化成字符串)
1.2.2 后端 Spring MVC 接收 JSON 数据
后端通过 @RequestBody
注解来接收请求体中的 JSON 数据,并将其绑定到一个 Java 对象。
示例 Controller:
@RestController
@RequestMapping("/api")
public class UserController {
@PostMapping("/user")
public String createUser(@RequestBody User user) {
System.out.println("用户名:" + user.getUsername());
System.out.println("密码:" + user.getPassword());
return "用户创建成功";
}
}
示例实体类:
public class User {
private String username;
private String password;
// Getter & Setter
}
⚠️ 注意:使用
@RequestBody
表示数据是从 请求体中提取 的,不能再用来接收 URL 参数或表单字段。
1.2.3 常见问题排查
问题 | 可能原因 |
---|---|
接收不到数据 | 前端请求头没设置 Content-Type: application/json |
报错 HttpMessageNotReadableException |
请求体不是合法 JSON 或字段不匹配 |
校验不生效 | 忘了加 @Valid 或 @Validated |
返回乱码 | 没设置响应编码或响应类型 |
1.2.4 结合参数校验(推荐)
可以在实体类中添加注解如 @NotBlank
、@Size
,然后在 Controller 中使用 @Valid
自动进行参数校验:
@PostMapping("/user")
public String createUser(@Valid @RequestBody User user, BindingResult result) {
if (result.hasErrors()) {
return "参数错误:" + result.getFieldError().getDefaultMessage();
}
return "用户创建成功";
}
1.2.5 返回 JSON 数据给前端
可以直接返回一个对象,Spring 会自动将其转为 JSON(前提是使用了 @RestController
或方法上加了 @ResponseBody
):
@PostMapping("/user")
public User createUser(@RequestBody User user) {
user.setUsername(user.getUsername().toUpperCase());
return user;
}
1.2.6 JSON字符串 和 Java对象的互相转换(Jackson)
Spring MVC(尤其是 Spring Boot)默认集成了 Jackson 作为 JSON 解析器,支持 Java 对象和 JSON 字符串之间的自动转换。
1.2.6.1 Java 对象 ➡️ JSON 字符串
使用 ObjectMapper
(Jackson 提供)进行对象序列化:
import com.fasterxml.jackson.databind.ObjectMapper;
public class JsonDemo {
public static void main(String[] args) throws Exception {
User user = new User();
user.setUsername("Alice");
user.setPassword("123456");
ObjectMapper mapper = new ObjectMapper();
String json = mapper.writeValueAsString(user);
System.out.println("JSON 字符串:" + json);
}
}
🧾 输出示例:
{"username":"Alice","password":"123456"}
1.2.6.2 JSON 字符串 ➡️ Java 对象
使用 ObjectMapper.readValue()
将 JSON 字符串反序列化成 Java 对象:
String json = "{\"username\":\"Bob\",\"password\":\"654321\"}";
User user = mapper.readValue(json, User.class);
System.out.println("用户名:" + user.getUsername());
1.2.6.3 List / Map 类型转换
JSON 数组转为 List:
String json = "[{\"username\":\"Tom\"},{\"username\":\"Jerry\"}]";
List<User> users = mapper.readValue(json, new TypeReference<List<User>>() {});
JSON 对象转为 Map:
String json = "{\"id\":1,\"name\":\"Java\"}";
Map<String, Object> map = mapper.readValue(json, new TypeReference<Map<String, Object>>() {});
1.2.6.4 自定义 ObjectMapper(可选)
你可以自定义 ObjectMapper
来格式化输出、修改时间格式、字段命名等:
@Bean
public ObjectMapper objectMapper() {
ObjectMapper mapper = new ObjectMapper();
mapper.setDateFormat(new SimpleDateFormat("yyyy-MM-dd"));
mapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
return mapper;
}
🔚 总结
操作 | 工具 | 注解 |
---|---|---|
前端发送 JSON | fetch / axios | 设置 Content-Type: application/json |
后端接收 JSON | Spring MVC | @RequestBody |
参数校验 | JSR-303 | @Valid + BindingResult |
返回 JSON | Spring Boot / MVC | @ResponseBody 或 @RestController |
类型 | 方法 |
---|---|
Java ➡️ JSON | mapper.writeValueAsString(obj) |
JSON ➡️ Java | mapper.readValue(json, Class.class) |
自动处理 | 使用 @RequestBody / @ResponseBody |
常用库 | Jackson(Spring 默认)、Gson、Fastjson(需手动集成) |
🔹 1.3 路径参数(REST 风格)
随着 RESTful API 的流行,很多接口不再使用传统的 Query 参数来传值,而是将参数嵌入在 URL 路径中,以表达资源之间的关系:
GET /user/1001
GET /order/1001/items/5001
Spring MVC 通过 @PathVariable
注解,可以轻松提取路径中的动态参数。
✅ 使用 @PathVariable
获取单个参数
@GetMapping("/user/{id}")
public String getUser(@PathVariable("id") Long userId) {
System.out.println("获取用户ID:" + userId);
return "userDetail";
}
✨ 说明:
{id}
是路径中的占位符,实际访问时用具体数字或字符串替代。@PathVariable("id")
中的"id"
要和 URL 中的{id}
一致。如果参数名和占位符相同,可以简写为:
@GetMapping("/user/{id}")
public String getUser(@PathVariable Long id) {
return "userDetail";
}
✅ 绑定多个路径参数
当路径中包含多个动态段时,可以绑定多个参数:
@GetMapping("/order/{userId}/item/{itemId}")
public String getOrderItem(@PathVariable Long userId,
@PathVariable Long itemId) {
System.out.println("用户ID:" + userId + ",商品ID:" + itemId);
return "orderItemDetail";
}
访问 URL 示例:
/order/2001/item/888
控制台输出:
用户ID:2001,商品ID:888
🔍 常见用法拓展:
✅ 路径参数 + Query 参数组合使用:
@GetMapping("/product/{id}")
public String getProduct(@PathVariable Long id,
@RequestParam(defaultValue = "1") int page) {
// 访问 /product/10?page=2
System.out.println("商品ID:" + id + ",评论页码:" + page);
return "productDetail";
}
🧱 小结:
特性 | @PathVariable |
---|---|
来源 | URL 路径 |
类型支持 | 数字、字符串、UUID 等 |
常用于 | RESTful 风格接口 |
可与其他注解混用 | ✅ 支持与 @RequestParam 、@RequestBody 一起使用 |
🔹1.4 文件上传:使用 @RequestPart
接收文件 & JSON
在实际开发中,经常会遇到这样的需求:前端上传一个文件,同时还需要提交一些 JSON 格式的表单数据。这时候,我们就可以使用:
@RequestPart
接收 Multipart 表单中的文件和 JSON 字段MultipartFile
类型接收上传的文件
✅ 示例:上传头像 + 用户资料
前端发送一个包含文件和 JSON 的 multipart 请求(Content-Type: multipart/form-data
)
💡 控制器:
@RestController
@RequestMapping("/api")
public class UploadController {
@PostMapping("/upload")
public String uploadUserInfo(@RequestPart("file") MultipartFile file,
@RequestPart("user") User user) throws IOException {
System.out.println("接收到用户:" + user.getUsername());
System.out.println("上传文件名:" + file.getOriginalFilename());
return "上传成功";
}
}
🧾 示例实体类:
public class User {
private String username;
private String email;
// Getter & Setter
}
✅ 前端请求示例(使用 JavaScript + fetch + FormData):
const formData = new FormData();
formData.append("file", document.querySelector("#fileInput").files[0]);
formData.append("user", new Blob([
JSON.stringify({
username: "alice",
email: "alice@example.com"
})
], { type: "application/json" }));
fetch("/api/upload", {
method: "POST",
body: formData
});
✅ 请求格式说明
file
是文件上传字段,对应MultipartFile
user
是一个 JSON 字符串包装成的 Blob,对应@RequestPart("user")
自动反序列化为 Java 对象请求头 不需要设置
Content-Type
,浏览器会自动生成multipart/form-data
并带上 boundary。
✅ 注意事项
问题 | 说明 |
---|---|
JSON 必须为 Blob 类型 | 如果直接 append JSON 字符串,会作为普通字段而不是 part |
不可使用 @RequestParam 接 JSON |
@RequestParam 只能接简单类型或文件,无法处理复杂 JSON |
请求方式必须是 multipart/form-data |
否则文件不会被正确识别 |
✅ 配置文件大小限制(Spring Boot 示例)
在 application.properties
或 application.yml
中设置上传限制:
spring.servlet.multipart.max-file-size=10MB
spring.servlet.multipart.max-request-size=20MB
🧱 小结
内容 | 注解 | 类型 |
---|---|---|
文件上传 | @RequestPart("file") |
MultipartFile |
JSON 数据 | @RequestPart("user") |
Java 对象 |
普通字段 | @RequestParam("name") |
String / 基本类型 |
请求类型 | multipart/form-data |
浏览器自动生成 |
👣 到此,我们已经覆盖了多种常见的数据传参方式:
Query 参数:使用
@RequestParam
从 URL 中获取查询参数。表单参数:通过
@ModelAttribute
获取表单数据,并结合BindingResult
进行校验。路径参数:使用
@PathVariable
从 URL 路径中提取参数。JSON 请求体:通过
@RequestBody
获取前端传来的 JSON 数据,并可与数据校验结合使用。
第二章:特殊字符处理 & 编码问题解析
、
当然可以!这是 2.1 常见特殊字符 的详细讲解,适用于博客中“Spring MVC 数据传参中的特殊字符处理”章节 ✅
🔹 2.1 常见特殊字符解析(中文、空格、#、% 等)
在使用 Spring MVC 开发 Web 接口时,我们经常会通过 URL 传递参数,例如:
http://localhost:8080/search?name=张三#abc
但是,如果参数中包含 中文、空格、#、%、&、= 等特殊字符,就可能出现参数截断、乱码、无法接收等问题。了解这些字符在 URL 中的表现和解决方法非常关键。
✅ 一、特殊字符的常见类型
字符 | 描述 | 问题 |
---|---|---|
中文 |
非 ASCII 字符 | 会出现乱码 |
空格 |
空白字符 | 会变成 + 或 %20 |
# |
锚点符号 | 浏览器不传给服务器 |
% |
转义符号开头 | 若不正确使用会解析失败 |
& |
参数分隔符 | 用于多个参数 |
= |
参数赋值符号 | 不能出现在值中 |
? |
查询字符串开始 | 只能出现一次 |
✅ 二、示例问题分析
请求示例:
/search?name=张三#abc
实际情况:
浏览器会 截断
#
后的内容,它认为#abc
是页面锚点,不会发送到后端。后端接收到的请求其实是:
/search?name=张三
如果没编码,Spring MVC 控制器中打印参数会变成乱码(如
å¼ ä¸‰
)
✅ 三、如何解决?
1. 对 URL 进行 URL 编码(encode)
使用前端 JS 或其他工具对参数进行编码:
const name = encodeURIComponent("张三#abc");
// 结果:%E5%BC%A0%E4%B8%89%23abc
发送请求时变成:
/search?name=%E5%BC%A0%E4%B8%89%23abc
后端接收到参数后自动解码
2. Spring MVC 端设置编码(避免乱码)
如果是 Spring Boot,在
application.properties
中添加:
spring.http.encoding.enabled=true
spring.http.encoding.charset=UTF-8
spring.http.encoding.force=true
3. 使用过滤器统一设置编码(传统 Spring MVC)
public class EncodingFilter implements Filter {
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
request.setCharacterEncoding("UTF-8");
response.setCharacterEncoding("UTF-8");
chain.doFilter(request, response);
}
}
✅ 四、常见特殊字符编码表
字符 | 编码 |
---|---|
空格 | %20 或 + |
中文 | %E5%BC%A0%E4%B8%89 (张三) |
# |
%23 |
% |
%25 |
& |
%26 |
= |
%3D |
✅ 五、后端示例(使用 @RequestParam 接收参数)
@GetMapping("/search")
public String search(@RequestParam("name") String name) {
System.out.println("接收到参数 name = " + name);
return "ok";
}
若前端发送的是未经编码的中文或特殊字符,控制器可能会收到乱码或不完整内容。通过前端 encodeURIComponent()
编码 + 后端统一 UTF-8 解码即可避免这些问题。
✅ 六、测试建议
测试内容 | 预期结果 |
---|---|
/search?name=张三 |
后端接收到 “张三” |
/search?name=张三#abc |
实际只收到 “张三” |
/search?name=%E5%BC%A0%E4%B8%89%23abc |
后端接收到 “张三#abc” |
🔹2.2 GET 请求中的编码问题解析(%20、+、%23 的区别)
在 Web 开发中,GET 请求的参数直接拼接在 URL 中。当 URL 包含中文、空格或特殊字符时,浏览器和服务器之间的编码解码处理就变得非常关键。
✅ 一、浏览器对 URL 的自动编码行为
浏览器会自动对 URL 中 非 ASCII 字符 和 部分特殊字符 进行编码,例如:
/search?keyword=Java 编码
浏览器实际发送请求时,会自动将其编码为:
/search?keyword=Java%20%E7%BC%96%E7%A0%81
中文“编码” →
%E7%BC%96%E7%A0%81
空格 →
%20
(或有些场景中为+
)
✅ 二、Spring MVC 如何解析这些编码
Spring MVC 默认使用 UTF-8 对 URL 进行解码(如果配置正确),即:
@GetMapping("/search")
public String search(@RequestParam String keyword) {
System.out.println("关键词:" + keyword);
return "ok";
}
请求 /search?keyword=Java%20%E7%BC%96%E7%A0%81
后端接收到的参数会被正确解析为:
Java 编码
✅ 所以只要前端编码正确,Spring 后端是可以自动解析的。
✅ 三、重点区别:%20
vs +
vs %23
表达形式 | 代表含义 | 用途说明 |
---|---|---|
%20 |
空格(编码) | URL 中最标准的空格编码方式 |
+ |
空格(表单风格) | 仅适用于 application/x-www-form-urlencoded 表单编码 |
%23 |
# 字符本身 |
因为 # 是锚点标记,需转义才能被服务器接收到 |
示例解析:
/search?kw=A%20B
→A B
✅/search?kw=A+B
→A B
✅(但依赖解码器行为)/search?kw=A%2BB
→A+B
✅(如果真要传+
本身)/search?kw=A#B
→ 浏览器只会发送A
,不会包含#B
/search?kw=A%23B
→ 后端会收到A#B
✅ 四、如何避免乱码和错误
场景 | 建议处理 |
---|---|
中文参数 | 使用 encodeURIComponent 编码 |
空格 | 推荐使用 %20 而非 + (更标准) |
# 符号 |
必须编码为 %23 |
后端乱码 | 确保 Spring Boot 配置了 UTF-8 解码(默认已支持) |
Spring Boot 配置参考:
spring.servlet.encoding.charset=UTF-8
spring.servlet.encoding.enabled=true
spring.servlet.encoding.force=true
✅ 五、简单测试表格
原始字符 | URL 编码 | 解码结果(Spring) |
---|---|---|
空格 | %20 |
空格 |
空格 | + |
空格(表单风格) |
+ |
%2B |
+ |
# |
%23 |
# |
中文“张三” | %E5%BC%A0%E4%B8%89 |
张三 |
🧪 示例 Controller 方法(用于调试测试):
@GetMapping("/test")
public String test(@RequestParam String value) {
return "收到参数:" + value;
}
可以测试这些 URL:
/test?value=A%20B
/test?value=A+B
/test?value=A%2BB
/test?value=A%23B
✅六 URL 编码常见字符对照表(开发必备小抄)
字符原文 |
编码后 |
解码结果 |
说明 |
---|---|---|---|
空格(半角) | %20 |
空格 | 推荐使用 %20 表示空格 |
空格(表单场景) | + |
空格 | 仅在 x-www-form-urlencoded 中解析为空格 |
加号 + |
%2B |
+ |
若想传“+”本身,必须使用 %2B |
井号 # |
%23 |
# |
避免被浏览器视为锚点 |
中文 “张三” | %E5%BC%A0%E4%B8%89 |
张三 | 需要 UTF-8 编码 |
百分号 % |
%25 |
% |
编码开头符号自身的转义 |
与号 & |
%26 |
& |
避免作为参数分隔符 |
等号 = |
%3D |
= |
避免参数赋值误解析 |
✅ 建议用
encodeURIComponent()
对所有参数值进行编码,避免问题。
🔸 前端 JS 测试代码(复制即可用)
这是一个简单的前端测试页面,能快速测试各种字符编码的行为:
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<title>GET 编码测试</title>
</head>
<body>
<h2>GET 参数编码测试</h2>
<input type="text" id="input" placeholder="输入内容,如:张三 + # & = 空格">
<button onclick="sendRequest()">测试请求</button>
<p id="result"></p>
<script>
function sendRequest() {
const raw = document.getElementById("input").value;
const encoded = encodeURIComponent(raw);
const url = `/test?value=${encoded}`;
fetch(url)
.then(res => res.text())
.then(res => {
document.getElementById("result").innerText = "服务端返回: " + res;
});
}
</script>
</body>
</html>
🔸 Spring Boot Controller 示例代码(配合前端测试)
@RestController
public class EncodingTestController {
@GetMapping("/test")
public String test(@RequestParam String value) {
return "服务端收到参数:" + value;
}
}
🔸 实际测试示例
输入 | 实际请求 URL | 后端收到 |
---|---|---|
张三 | /test?value=%E5%BC%A0%E4%B8%89 |
张三 |
A B | /test?value=A%20B |
A B |
A+B | /test?value=A%2BB |
A+B |
A#B | /test?value=A%23B |
A#B |
%25 | /test?value=%2525 |
%25 |
✅ 小贴士
永远不要手动拼接 URL 参数,使用
encodeURIComponent()
浏览器不会发送
#
后的内容,必须提前编码为%23
表单提交推荐用
POST
,GET 仅用于短、简单的查询参数
🔸 2.3 POST 请求中的编码设置(CharacterEncodingFilter 全解析)
虽然 GET 请求中浏览器会自动进行 URL 编码,但在 POST 请求中,请求体的数据编码依赖客户端和服务端的配置是否匹配,否则会出现中文乱码等问题。
✅ 一、为什么 POST 请求容易出现编码问题?
GET 请求参数放在 URL 中,浏览器会自动进行 UTF-8 编码。
POST 请求的参数通常放在 请求体 body 中,如表单数据、JSON 数据等。
若服务端未正确设置编码方式,则无法按预期解析参数,例如表单提交中文可能乱码。
✅ 二、传统 Spring MVC 中的解决方案:CharacterEncodingFilter
在不使用 Spring Boot 的老项目中,开发者必须手动配置字符编码过滤器 CharacterEncodingFilter
,否则 Spring MVC 默认使用 ISO-8859-1 解码表单参数,导致乱码。
示例配置方式(web.xml):
<filter>
<filter-name>encodingFilter</filter-name>
<filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
<init-param>
<param-name>encoding</param-name>
<param-value>UTF-8</param-value>
</init-param>
<init-param>
<param-name>forceEncoding</param-name>
<param-value>true</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>encodingFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
或使用 Java Config:
@Bean
public FilterRegistrationBean<CharacterEncodingFilter> characterEncodingFilter() {
CharacterEncodingFilter filter = new CharacterEncodingFilter();
filter.setEncoding("UTF-8");
filter.setForceEncoding(true);
FilterRegistrationBean<CharacterEncodingFilter> bean = new FilterRegistrationBean<>(filter);
bean.setOrder(Ordered.HIGHEST_PRECEDENCE);
return bean;
}
✅ 三、Spring Boot 的自动配置(默认已支持)
Spring Boot 已经默认为你配置好了 CharacterEncodingFilter
,你只需确保配置项存在即可:
application.properties 中配置:
spring.servlet.encoding.enabled=true
spring.servlet.encoding.charset=UTF-8
spring.servlet.encoding.force=true
这些配置会自动启用 CharacterEncodingFilter
,确保所有请求(包括 POST 表单)都使用 UTF-8 编码。
✅ 四、实测对比如下:
场景 | 是否乱码 | 原因 |
---|---|---|
Spring MVC 未配置编码 | ❌乱码 | 默认使用 ISO-8859-1 解码 |
手动添加 CharacterEncodingFilter | ✅正常 | 统一使用 UTF-8 |
Spring Boot 默认配置 | ✅正常 | 自动注入编码过滤器 |
Spring Boot 配置关闭 encoding.enabled=false | ❌乱码 | 无过滤器生效 |
✅ 五、编码生效的两个关键点
条件 | 说明 |
---|---|
CharacterEncodingFilter.forceEncoding=true |
强制使用你设置的编码解析请求体 |
客户端 Content-Type 正确 | 表单应为 application/x-www-form-urlencoded; charset=UTF-8 ,JSON 为 application/json; charset=UTF-8 |
✅ 六、示例控制器验证 POST 编码
@PostMapping("/submit")
public String submit(@RequestParam String name) {
System.out.println("接收到 name = " + name);
return "ok";
}
使用表单或 Postman 发送中文数据,可以观察是否成功接收。
✅ 总结一句话:
GET 编码靠浏览器自动,POST 编码必须服务端主动设定;用 Spring Boot 就别怕,它自动帮你搞定!
📷 POST 中文乱码演示(Postman 测试案例)
我们用一个简单的 Spring MVC 控制器来测试:
✅ 控制器代码
@RestController
public class PostTestController {
@PostMapping("/submit")
public String submit(@RequestParam String name) {
System.out.println("收到参数 name = " + name);
return "服务器已收到:" + name;
}
}
📤 测试用例 1:无编码过滤器(或未设置 forceEncoding)
💬 Postman 请求设置:
请求方式:
POST
请求地址:
http://localhost:8080/submit
类型:
x-www-form-urlencoded
参数:
name=张三
Headers:未设置 charset
⚠️ 后台打印:
收到参数 name = ???
👉 出现乱码!因为 Spring 默认解码方式是 ISO-8859-1。
📤 测试用例 2:配置 CharacterEncodingFilter + UTF-8
✅ Spring Boot 自动配置(或你手动加上):
spring.servlet.encoding.charset=UTF-8
spring.servlet.encoding.enabled=true
spring.servlet.encoding.force=true
💬 Postman 请求相同,但 header 手动添加:
Content-Type: application/x-www-form-urlencoded; charset=UTF-8
✅ 后台打印:
收到参数 name = 张三
成功 ✅
🔸 2.4 遇到乱码怎么办?全流程排查 + 解决方案 ✅
乱码问题在 Web 开发中常见,尤其是在多语言环境、前后端交互复杂时。这里为你总结一份统一编码 + 排查方案清单,帮助你快速定位与解决乱码。
✅ 一、配置统一的字符编码过滤器(服务端核心)
在 Spring 项目中,CharacterEncodingFilter 是解决乱码的关键组件。
💡 Spring Boot 自动配置(推荐):
spring.servlet.encoding.charset=UTF-8
spring.servlet.encoding.enabled=true
spring.servlet.encoding.force=true
✔ 适用于 GET、POST、文件上传等请求
🧰 非 Spring Boot 项目(传统配置):
@Bean
public FilterRegistrationBean<CharacterEncodingFilter> characterEncodingFilter() {
CharacterEncodingFilter filter = new CharacterEncodingFilter();
filter.setEncoding("UTF-8");
filter.setForceEncoding(true);
return new FilterRegistrationBean<>(filter);
}
✅ 二、IDE 配置统一编码(避免开发阶段引入乱码)
IDE 设置不对,直接导致
.java
或.html
文件保存就是乱码!
🌟 IntelliJ IDEA 设置 UTF-8:
File -> Settings -> Editor -> File Encodings
设置以下内容为
UTF-8
:Global Encoding
Project Encoding
Default encoding for properties files
Transparent native-to-ascii conversion ✔
✅ 三、页面文件声明编码(前端页面不能少)
对于 Thymeleaf / JSP 等后端渲染页面:
HTML 示例:
<meta charset="UTF-8">
JSP 页面开头:
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
✅ 四、客户端请求编码设置(特别是 POST)
表单提交(需带 charset):
<form method="post" accept-charset="UTF-8" action="/submit">
Postman 请求 Header 添加:
Content-Type: application/x-www-form-urlencoded; charset=UTF-8
或 JSON:
Content-Type: application/json; charset=UTF-8
✅ 五、数据库连接设置编码(从根上解决保存乱码)
MySQL 示例:
jdbc:mysql://localhost:3306/db?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai
characterEncoding=utf8
一定不要忘!
🧭 六、乱码排查流程图(逻辑全链路)
[客户端输入]
↓
[前端页面编码] <— HTML charset / Content-Type
↓
[请求编码方式] <— GET (URL) / POST (Body)
↓
[Spring Filter: CharacterEncodingFilter]
↓
[Servlet 解码]
↓
[Controller 获取参数]
↓
[打印日志 or 存储数据库]
↓
[页面展示编码] <— View Resolver / Template Engine
✅ 七、一键 Checklist(乱码排查清单)
检查项 | 描述 | 是否完成 |
---|---|---|
页面 <meta charset="UTF-8"> |
HTML 编码声明 | ✅ |
IDEA 文件保存编码 UTF-8 | 保证源码无乱码 | ✅ |
请求头中 charset=UTF-8 | Postman / Axios / Form | ✅ |
CharacterEncodingFilter 开启 | Spring Boot 自动或手动配置 | ✅ |
JSON 或表单传参编码设置 | 前后端统一 | ✅ |
数据库连接设置 UTF-8 | JDBC URL 参数 | ✅ |
✅ 小结一句话:
乱码的本质是编码-解码不一致,确保「前端 → 网络传输 → 后端 → 存储/展示」各环节使用统一编码(UTF-8)即可避免!
结语:
核心内容总结
数据传参方式与场景
Spring Web MVC 支持多种参数绑定方式:路径变量:
@PathVariable
处理 URL 中的动态参数(如/user/{id}
),需注意特殊字符(如/
,%
)需 URL 编码。请求参数:
@RequestParam
获取?key=value
形式的参数,特殊字符(如&
,+
)需前端编码。表单提交:通过
POST
提交,Content-Type: application/x-www-form-urlencoded
,需配置字符编码过滤器。JSON 数据:
@RequestBody
接收 JSON 格式参数,默认使用 UTF-8 编码,需确保前后端编码一致。
特殊字符处理策略
URL 保留字符:如
#
,?
,&
需通过URLEncoder
编码(如%23
代替#
)。空格与加号问题:URL 中空格默认转为
+
,后端需显式处理(如替换为%20
)。JSON 转义:特殊字符(如
"
,\
)需 JSON 序列化工具(如 Jackson)自动转义。数据库存储:通过预处理语句(PreparedStatement)或 ORM 框架(如 Hibernate)防止 SQL 注入。
编码问题根源与解决方案
乱码常见原因:
请求/响应未统一编码(如 GET 请求的 URI 编码与 POST 的 Body 编码不一致)。
Tomcat 等容器默认使用 ISO-8859-1 解码 URI。
全局编码配置:
添加
CharacterEncodingFilter
并设置forceEncoding=true
(UTF-8)。配置 Tomcat 的
URIEncoding="UTF-8"
(server.xml
中<Connector>
节点)。
局部编码覆盖:
使用
String
类型接收参数后手动解码:
@RequestParam String name) {
String decodedName = new String(name.getBytes(StandardCharsets.ISO_8859_1), StandardCharsets.UTF_8);
}
实战建议
前端协作:
使用
encodeURIComponent()
对动态参数编码(如路径变量)。表单提交设置
<form accept-charset="UTF-8">
。
后端防御:
对用户输入进行合法性校验(如正则表达式过滤非法字符)。
使用拦截器或 AOP 统一处理参数解码。
测试工具:
通过 Postman 发送含特殊字符的请求(如
%0A
换行符),观察后端日志解析结果。使用 Wireshark 抓包检查原始 HTTP 请求编码。
结语
Spring Web MVC 的参数处理机制灵活强大,但特殊字符和编码问题往往隐藏于细节之中。理解 HTTP 协议层与框架行为的交互是解决问题的关键:从 URL 编码规则到 Servlet 容器的默认配置,再到 Spring 的过滤器链,每一步都可能成为乱码的“罪魁祸首”。通过统一全局编码、严格校验输入输出,并结合自动化测试,可以有效构建高鲁棒性的 Web 应用。