第三期:深入理解 Spring Web MVC [特殊字符](数据传参+ 特殊字符处理 + 编码问题解析)
✨前言:传参和状态管理,看似简单其实门道不少
在 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=webSystem.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;}@Overridepublic 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=2System.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 应用。
相关文章:
第三期:深入理解 Spring Web MVC [特殊字符](数据传参+ 特殊字符处理 + 编码问题解析)
✨前言:传参和状态管理,看似简单其实门道不少 在 Web 开发中,前端和后端最核心的交流方式就是“传参”,而“传参”除了涉及如何写代码获取参数,还藏着很多开发者容易忽略的细节: 为什么 URL 带了中文&…...
嵌入式学习笔记——ARM-中断与异常
文章目录 中断与异常的区别中断与 DMA 的区别中断能否睡眠?下半部能否睡眠?1. 中断处理程序不能睡眠2. 下半部(SoftIRQ、Tasklet、Workqueue) 中断处理注意点1. 快进快出2. 避免阻塞3. 正确返回值4. 如何处理大量任务5. 避免竞态问…...
Everything 安装教程与使用教程(附安装包)
文章目录 前言一、Everything 介绍二、Everything 安装教程1.Everything 安装包下载2.选择安装文件3.选择安装语言4.接受许可协议5.选择安装位置6.配置安装选项7.完成安装 三、Everything 使用教程1.启动软件2.简单关键词搜索3.按类型搜索 前言 在日常使用电脑时,随…...
嵌入式开发中栈溢出的处理方法
嵌入式开发中栈溢出的处理方法 目录 引言栈溢出的原理栈溢出的危害栈溢出检测方法 哨兵变量法栈着色法硬件监测机制编译器栈保护 裸机系统中的栈溢出处理操作系统中的栈溢出处理预防栈溢出的最佳实践结论 引言 在嵌入式系统开发中,栈溢出是一个常见且危险的问题…...
SQL语句(三)—— DQL
目录 基本语法 一、基础查询 1、查询多个字段 2、字段设置别名 3、去除重复记录 4、示例代码 二、条件查询 1、语法 2、条件列表常用的运算符 3、示例代码 三、分组查询 (一)聚合函数 1、介绍 2、常见的聚合函数 3、语法 4、示例代码 &…...
#python项目生成exe相关了解
在 Windows 上将 Python 项目 生成 EXE 可执行文件,主要使用 pyinstaller。以下是完整步骤: 📌 1. 安装 PyInstaller pip install pyinstaller如果已安装,可执行以下命令检查版本: pyinstaller --versionὌ…...
Opencv计算机视觉编程攻略-第九节 描述和匹配兴趣点
一般而言,如果一个物体在一幅图像中被检测到关键点,那么同一个物体在其他图像中也会检测到同一个关键点。图像匹配是关键点的常用功能之一,它的作用包括关联同一场景的两幅图像、检测图像中事物的发生地点等等。 1.局部模板匹配 凭单个像素就…...
JSON-lib考古现场:在2025年打开赛博古董店的奇妙冒险
各位在代码海洋里捡贝壳的探险家们!今天我们要打开一个尘封的Java古董箱——JSON-lib!这货可是2003年的老宝贝,比在座很多程序员的工龄还大!准备好穿越回Web 1.0时代,感受XML统治时期的余晖了吗? …...
Android: Handler 的用法详解
Android 中 Handler 的用法详解 Handler 是 Android 中用于线程间通信的重要机制,主要用于在不同线程之间发送和处理消息。以下是 Handler 的全面用法指南: 一、Handler 的基本原理 Handler 基于消息队列(MessageQueue)和循环器(Looper)工作,…...
汇编学习之《push , pop指令》
学习本章前线了解ESP, EBP 指令 汇编学习之《指针寄存器&大小端学习》-CSDN博客 栈的特点: 好比一个垂直容器,可以陆续放入物体,但是先放的物体通常会被后面放的物体压着,只有等上面后放的物品拿出来后,才能…...
Python循环控制语句
1. 循环类型概述 Python提供两种主要的循环结构: while循环 - 在条件为真时重复执行for循环 - 遍历序列中的元素 2. while循环 基本语法 while 条件表达式:循环体代码示例 count 0 while count < 5:print(f"这是第{count1}次循环")count 13. f…...
微信小程序(下)
目录 在事件处理函数中为 data 中的数据赋值 事件传参 bindinput 的语法格式 实现文本框和 data 之间的数据同步 条件渲染 结合 使用 wx:if hidden wx:if与 hidden 的对比 wx:for 手动指定索引和当前项的变量名 wx:key 的使用 WXSS 和 CSS 的关系 什么是 rpx 尺寸…...
【零基础入门unity游戏开发——2D篇】2D 游戏场景地形编辑器——TileMap的使用介绍
考虑到每个人基础可能不一样,且并不是所有人都有同时做2D、3D开发的需求,所以我把 【零基础入门unity游戏开发】 分为成了C#篇、unity通用篇、unity3D篇、unity2D篇。 【C#篇】:主要讲解C#的基础语法,包括变量、数据类型、运算符、…...
vector的介绍与代码演示
由于以后我们写OJ题时会经常使用到vector,所以我们必不可缺的是熟悉它的各个接口。来为我们未来作铺垫。 首先,我们了解一下: https://cplusplus.com/reference/vector/ vector的概念: 1. vector是表示可变大小数组的序列容器…...
ubuntu 22.04 解决LXC 报错CGroupV1 host system
解决CGroupV1 host system 报错 echo "cgroupv1 environment" sed -i s/^GRUB_CMDLINE_LINUX.*/GRUB_CMDLINE_LINUX_DEFAULT"quiet splash systemd.unified_cgroup_hierarchy0" / /etc/default/grub update-grub reboot 下载oracle 7 Linux 容器测试 l…...
JavaEE初阶复习(JVM篇)
JVM Java虚拟机 jdk java开发工具包 jre java运行时环境 jvm java虚拟机(解释执行 java 字节码) java作为一个半解释,半编译的语言,可以做到跨平台. java 通过javac把.java文件>.class文件(字节码文件) 字节码文件, 包含的就是java字节码, jvm把字节码进行翻译转化为…...
MINIQMT学习课程Day9
获取qmt账号的持仓情况后,我们进入下一步,如何获得当前账号的委托状况 还是之前的步骤,打开qmt,选择独立交易, 之后使用pycharm,编写py文件 导入包: from xtquant import xtdata from xtqua…...
动态规划似包非包系列一>组合总和IIV
目录 题目分析:状态表示:状态转移方程:初始化填表顺序返回值:代码呈现: 题目分析: 状态表示: 状态转移方程: 初始化填表顺序返回值: 代码呈现: class Soluti…...
Java 二叉树非递归遍历核心实现
非递归遍历的核心是用栈模拟递归的调用过程,通过手动维护栈来替代系统栈,实现前序、中序和后序遍历。以下是三种遍历的代码实现与关键逻辑分析: 一、二叉树遍历 1.1、前序遍历(根 → 左 → 右) 核心逻辑:…...
JavaScript性能优化实践:从微观加速到系统级策略
JavaScript性能优化实践:从微观加速到系统级策略 引言:性能优化的"时空折叠"思维 在JavaScript的世界里,性能优化如同在时间与空间的维度中折叠代码。本文将通过"时空折叠"的隐喻,从代码执行效率(时间维度)和内存占用(空间维度)两大核心,结合现代…...
《P1029 [NOIP 2001 普及组] 最大公约数和最小公倍数问题》
题目描述 输入两个正整数 x0,y0,求出满足下列条件的 P,Q 的个数: P,Q 是正整数。 要求 P,Q 以 x0 为最大公约数,以 y0 为最小公倍数。 试求:满足条件的所有可能的 P,Q 的个数。 输入格式 一行两个正整数 x0,y0。…...
【力扣hot100题】(052)课程表
什么人一学期要上2000节课啊jpg 看了非常久都没思路,主要是数据结构还没复习到图论,根本没思路怎么储存一个图…… 唯一记得的就是两种存储方法,一种是二维数组法,记录每一条边的有无,一种是只记录有的边,…...
SpringBoot配置文件多环境开发
目录 一、设置临时属性的几种方法 1.启动jar包时,设置临时属性 2.idea配置临时属性 3.启动类中创建数组指定临时属性 二、多环境开发 1.包含模式 2.分组模式 三、配置文件的优先级 1.bootstrap 文件优先: 2.特定配置文件优先 3.文件夹位置优…...
RSA和ECC在密钥长度相同的情况下哪个更安全?
现在常见的SSL证书,如:iTrustSSL都支持RSA和ECC的加密算法,正常情况下RAS和ECC算法该如何选择呢?实际上在密钥长度相同的情况下,ECC(椭圆曲线密码学)通常比RSA(Rivest-Shamir-Adle…...
Dive into Deep Learning - 2.4. Calculus (微积分)
Dive into Deep Learning - 2.4. Calculus {微积分} 1. Derivatives and Differentiation (导数和微分)1.1. Visualization Utilities 2. Chain Rule (链式法则)3. DiscussionReferences 2.4. Calculus https://d2l.ai/chapter_preliminaries/calculus.html For a long time, …...
【备考高项】附录:合同法全文(428条全)
更多内容请见: 备考信息系统项目管理师-专栏介绍和目录 文章目录 第一章 一般规定第二章 合同的订立第三章 合同的效力第四章 合同的履行第五章 合同的变更和转让第六章 合同的权利义务终止第七章 违约责任第八章 其他规定第九章 买卖合同第十章 供用电、水、气、热力合同第十…...
Ubuntu安装Podman教程
1、先修改apt源为阿里源加速 备份原文件: sudo cp /etc/apt/sources.list /etc/apt/sources.list.backup 修改源配置: vim sources.list删除里面全部内容后,粘贴阿里源: deb http://mirrors.aliyun.com/ubuntu/ focal main re…...
9.进程信号
信号量 信号量是什么? 本质是一个计数器,通常用来表示公共资源中,资源数量多少的问题。 公共资源:可以被多个进程同时访问的资源。 访问没有保护的公共资源会导致数据不一致问题 什么是数据不一致问题 由于公共资源…...
python爬虫:小程序逆向(需要的工具前期准备)
前置知识点 1. wxapkg文件 如何查看小程序包文件 打开wechat的设置: .wxapkg概述 .wxapkg是小程序的包文件格式,且其具有独特的结构和加密方式。它不仅包含了小程序的源代码,还包括了图像和其他资源文件,这些内容在普通的文件…...
PGSQL 对象创建函数生成工具
文章目录 代码结果 代码 <!DOCTYPE html> <html lang"zh"> <head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><title>PGSQL 函数生成器</tit…...
