第三期:深入理解 Spring Web MVC [特殊字符](数据传参+ 特殊字符处理 + 编码问题解析)

发布于:2025-04-05 ⋅ 阅读:(17) ⋅ 点赞:(0)

✨前言:传参和状态管理,看似简单其实门道不少

在 Web 开发中,前端和后端最核心的交流方式就是“传参”,而“传参”除了涉及如何写代码获取参数,还藏着很多开发者容易忽略的细节:

  • 为什么 URL 带了中文,后端拿到是乱码?

  • @PathVariable@RequestParam 到底怎么选?

  • 前端传 数据(#),后端拿不到值怎么办?

  • 用户登录后,我要怎么“记住”他?

  • Cookie 和 Session 有啥区别,怎么用才优雅?

这些问题都是你在 Spring MVC 实战中一定会遇到的!本篇内容就是为了解决这些“看起来简单,实际容易踩坑”的问题。


📌 本期我们将系统讲解以下几个关键点:

  1. 多种传参方式(GET、POST、Path、JSON)

  2. 中文、空格、特殊字符(# % & +)等处理方式

  3. 如何在 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 请求。非常适合初学者掌握前后端分离接口开发的基础。


在前后端分离开发中,前端通常使用 fetchaxiosjQuery.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

  • bodydata 是一个 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.propertiesapplication.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%20BA B

  • /search?kw=A+BA B ✅(但依赖解码器行为)

  • /search?kw=A%2BBA+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:
  1. File -> Settings -> Editor -> File Encodings

  2. 设置以下内容为 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)即可避免!

 结语:

核心内容总结
  1. 数据传参方式与场景
    Spring Web MVC 支持多种参数绑定方式:

    • 路径变量@PathVariable 处理 URL 中的动态参数(如 /user/{id}),需注意特殊字符(如 /, %)需 URL 编码。

    • 请求参数@RequestParam 获取 ?key=value 形式的参数,特殊字符(如 &, +)需前端编码。

    • 表单提交:通过 POST 提交,Content-Type: application/x-www-form-urlencoded,需配置字符编码过滤器。

    • JSON 数据@RequestBody 接收 JSON 格式参数,默认使用 UTF-8 编码,需确保前后端编码一致。

  2. 特殊字符处理策略

    • URL 保留字符:如 #, ?, & 需通过 URLEncoder 编码(如 %23 代替 #)。

    • 空格与加号问题:URL 中空格默认转为 +,后端需显式处理(如替换为 %20)。

    • JSON 转义:特殊字符(如 ", \)需 JSON 序列化工具(如 Jackson)自动转义。

    • 数据库存储:通过预处理语句(PreparedStatement)或 ORM 框架(如 Hibernate)防止 SQL 注入。

  3. 编码问题根源与解决方案

    • 乱码常见原因

      • 请求/响应未统一编码(如 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 应用。


网站公告

今日签到

点亮在社区的每一天
去签到