距离本文上次更新已经过去 365 天,请注意时效性。

参考视频:SpringMVC 教程,SpringMVC 从零到精通,老杜 SpringMVC,动力节点 SpringMVC

0. 思考

假设有这样一个请求:

http://localhost:8080/springmvc/register?name=muyoukule&password=123&email=muyoukule@example.com

在 SpringMVC 中应该如何获取请求提交的数据呢?

在 SpringMVC 中又应该如何获取请求头信息呢?

在 SpringMVC 中又应该如何获取客户端提交的 Cookie 数据呢?

1. 环境准备

1、创建模块,添加依赖

xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<groupId>com.muyoukule</groupId>
<artifactId>springmvc-004</artifactId>
<version>1.0-SNAPSHOT</version>
<!-- 打包方式设置为war方式 -->
<packaging>war</packaging>

<properties>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>

<dependencies>
<!-- Spring MVC依赖 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>6.1.4</version>
</dependency>
<!--日志框架Logback依赖-->
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.5.3</version>
</dependency>
<!--Servlet依赖-->
<dependency>
<groupId>jakarta.servlet</groupId>
<artifactId>jakarta.servlet-api</artifactId>
<version>6.0.0</version>
<scope>provided</scope>
</dependency>
<!--Spring6和Thymeleaf整合依赖-->
<dependency>
<groupId>org.thymeleaf</groupId>
<artifactId>thymeleaf-spring6</artifactId>
<version>3.1.2.RELEASE</version>
</dependency>
</dependencies>
</project>

2、添加 web 支持

004模块添加web支持

3、编写 web.xml 文件

xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
version="4.0">

<!--前端控制器-->
<servlet>
<servlet-name>springmvc</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<!--通过初始化参数来指定springmvc配置文件的路径和名字。-->
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:springmvc.xml</param-value>
</init-param>
<!--在服务器启动的时候初始化DispatcherServlet,提高第一次访问的效率-->
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>springmvc</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>

</web-app>

4、创建 UserController

java
1
2
3
4
5
6
7
@Controller
public class UserController {
@RequestMapping("/")
public String toRegisterPage() {
return "register";
}
}

5、编写 springmvc.xml

xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">

<!--组件扫描-->
<context:component-scan base-package="com.muyoukule.controller"/>

<!--视图解析器-->
<bean id="thymeleafViewResolver" class="org.thymeleaf.spring6.view.ThymeleafViewResolver">
<!--作用于视图渲染的过程中,可以设置视图渲染后输出时采用的编码字符集-->
<property name="characterEncoding" value="UTF-8"/>
<!--如果配置多个视图解析器,它来决定优先使用哪个视图解析器,它的值越小优先级越高-->
<property name="order" value="1"/>
<!--当 ThymeleafViewResolver 渲染模板时,会使用该模板引擎来解析、编译和渲染模板-->
<property name="templateEngine">
<bean class="org.thymeleaf.spring6.SpringTemplateEngine">
<!--用于指定 Thymeleaf 模板引擎使用的模板解析器。模板解析器负责根据模板位置、模板资源名称、文件编码等信息,加载模板并对其进行解析-->
<property name="templateResolver">
<bean class="org.thymeleaf.spring6.templateresolver.SpringResourceTemplateResolver">
<!--设置模板文件的位置(前缀)-->
<property name="prefix" value="/WEB-INF/templates/"/>
<!--设置模板文件后缀(后缀),Thymeleaf文件扩展名不一定是html,也可以是其他,例如txt,大部分都是html-->
<property name="suffix" value=".html"/>
<!--设置模板类型,例如:HTML,TEXT,JAVASCRIPT,CSS等-->
<property name="templateMode" value="HTML"/>
<!--用于模板文件在读取和解析过程中采用的编码字符集-->
<property name="characterEncoding" value="UTF-8"/>
</bean>
</property>
</bean>
</property>
</bean>

</beans>

6、编写 register.html 文件

在 WEB-INF 目录下新建 templates 目录,在 templates 目录中新建 register.html 文件:

xml
1
2
3
4
5
6
7
8
9
10
11
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>用户注册</title>
</head>
<body>
<h3>用户注册</h3>
<hr>
</body>
</html>

7、部署测试

部署测试

2. 使用原生的 Servlet API 进行获取

原生的 Servlet API 指的是:HttpServletRequest

在 SpringMVC 当中,一个 Controller 类中的方法参数上如果有 HttpServletRequest,SpringMVC 会自动将当前请求对象传递给这个参数,因此我们可以通过这个参数来获取请求提交的数据。

测试

1、在 register.html 中准备一个注册的表单:

html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>用户注册</title>
</head>
<body>
<h3>用户注册</h3>
<hr>
<form th:action="@{/register}" method="post">
用户名:<input type="text" name="username"><br>
密码:<input type="password" name="password"><br>
性别:
<input type="radio" name="sex" value="1">
<input type="radio" name="sex" value="0">
<br>
爱好:
抽烟 <input type="checkbox" name="hobby" value="smoke">
喝酒 <input type="checkbox" name="hobby" value="drink">
烫头 <input type="checkbox" name="hobby" value="perm">
<br>
简介:<textarea rows="10" cols="60" name="intro"></textarea><br>
<input type="submit" value="注册">
</form>
</body>
</html>

2、先测试这个页面是否可以正常打开,是否可以正常提交数据:

输入注册信息

3、点击注册:F12 的方式查看是否提交了数据:

查看是否提交了数据

通过测试得知:可以正常提交数据。

4、接下来在控制器添加一个方法来处理这个注册的请求:

java
1
2
3
4
5
6
7
8
9
10
11
@PostMapping(value="/register")
public String register(HttpServletRequest request){
// 通过当前请求对象获取提交的数据
String username = request.getParameter("username");
String password = request.getParameter("password");
String sex = request.getParameter("sex");
String[] hobbies = request.getParameterValues("hobby");
String intro = request.getParameter("intro");
System.out.println(username + "," + password + "," + sex + "," + Arrays.toString(hobbies) + "," + intro);
return "success";
}

5、提供视图页面:

html
1
2
3
4
5
6
7
8
9
10
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>注册成功</title>
</head>
<body>
<h1>注册成功</h1>
</body>
</html>

6、测试:

输入注册信息 跳转到注册成功页面

7、控制台打印:

plaintext
1
muyoukule,123,1,[smoke, drink, perm],66666666

这样通过 Servlet 原生的 API 获取到提交的数据。但是这种方式不建议使用,因为方法的参数依赖 Servlet 原生 API,Controller 的测试将不能单独测试,必须依赖 WEB 服务器才能测试。另外,换句话说,如果在 SpringMVC 中使用了原生的 Servlet,你为什么还要用 SpringMVC 框架呢🙄!!!!!

3. 使用 @RequestParam 注解标注

3.1 @RequestParam 注解的基本使用

@RequestParam 注解作用:将请求参数与方法上的形参映射

1、修改 register 方法

java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@PostMapping(value = "/register")
public String register(
@RequestParam(value="username")
String a,
@RequestParam(value="password")
String b,
@RequestParam(value="sex")
String c,
@RequestParam(value="hobby")
String[] d,
@RequestParam(name="intro")
String e) {
System.out.println(a);
System.out.println(b);
System.out.println(c);
System.out.println(Arrays.toString(d));
System.out.println(e);
return "success";
}

PS:对于 @RequestParam 注解来说,属性有 valuename,这两个属性的作用相同,都是用来指定提交数据的 name。

java
1
2
3
4
5
@AliasFor("name")
String value() default "";

@AliasFor("value")
String name() default "";

例如:发送请求时提交的数据是:name1=value1&name2=value2,则这个注解应该这样写:@RequestParam(value="name1"),@RequestParam(value="name2")

2、启动服务器测试:

输入注册信息2 跳转到注册成功页面

3、控制台打印:

plaintext
1
2
3
4
5
muyoukule002
123
1
[smoke, drink]
muyoukule666

PS: @RequestParam(value="name1") 中 value 一定要与前端传过来的参数相同,否则会出错。😨

例如:前端传过来的参数为 username,而后端接收参数时写的是 @RequestParam(value="uname"),就会出现如下错误:

前后端参数不匹配报错

3.1.1 @PathVariable 注解

在 SpringMVC 当中,如果请求的 URL 使用的是 RESTFul 风格的,那么这个数据应该在 java 程序中如何获取呢?使用占位符方式。

java
1
2
3
4
5
6
7
8
9
@RequestMapping(value = "/login/{a}/{b}")
public String testRESTFulURL(
@PathVariable("a")
String username,
@PathVariable("b")
String password){
System.out.println("用户名:" + username + ",密码:" + password);
return "ok";
}

@PathVariable 这个注解是可以省略的,如果请求的 URL 参数的名字和形参的 name 相同,则 @PathVariable 可以省略。

但有一个前提:如果你采用的是 Spring6 + 版本,你需要在 pom.xml 文件中指定编译参数 -parameter,配置如下:

xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.12.1</version>
<configuration>
<source>17</source>
<target>17</target>
<compilerArgs>
<arg>-parameters</arg>
</compilerArgs>
</configuration>
</plugin>
</plugins>
</build>

PS:如果你使用的是 Spring5 的版本,以上的配置是不需要的。😁

3.2 @RequestParam 注解的 required 属性

java
1
boolean required() default true;

required 属性用来设置该方法参数是否为必须的。

默认情况下,这个参数为 true,表示方法参数是必需的。如果请求中缺少对应的参数,则会抛出异常。

可以将其设置为 false,false 表示不是必须的,如果请求中缺少对应的参数,则方法的参数为 null

测试

1、修改 register 方法,添加一个 age 形参,没有指定 required 属性时,默认是 true,表示必需的

java
1
2
// 添加一个 age 形参
@RequestParam(name = "age") String age

2、启动服务器,但前端表单中没有填写年龄 age 的输入框,我们来看报错信息:

未提供参数age报错

错误信息告诉我们:参数 age 是必需的。没有提供这个请求参数,HTTP 状态码 400

3、如果将 required 属性设置为 false。则该参数则不是必须的,如果请求参数仍然未提供时,我们来看结果:

java
1
2
3
4
5
// 将 required 属性设置为 false
@RequestParam(name = "age", required = false) String age

//打印
System.out.println("age====>" + age);

4、启动服务器,填写信息后成功跳转到注册成功页面,控制台打印:

plaintext
1
2
3
4
5
6
muyoukule
123
1
[smoke, drink]
muyoukule66666
age====>null

通过测试得知,如果一个参数被设置为不是必需的,当没有提交对应的请求参数时,形参默认值 null

当然,如果请求参数中提供了 age,则 age 为真实提交的数据(PS:未填写信息会返回给后端一个空字符串 ""

1、给 register.html 添加如下输入框:

html
1
年龄:<input type="number" name="age"><br>

2、重新启动服务器,填写所有信息后点击注册成功跳转到注册成功页面,控制台打印:

plaintext
1
2
3
4
5
6
muyoukule
123
1
[smoke, drink, perm]
4245
age====>18

3.3 @RequestParam 注解的 defaultValue 属性

defaultValue 属性用来设置形参的默认值,当没有提供对应的请求参数或者请求参数的值是空字符串"" 的时候,方法的形参会采用默认值。

测试

1、给 age 形参的 @RequestParam 注解添加一个 defaultValue = "18" 属性

java
1
@RequestParam(name = "age", required = false,defaultValue = "18") String age

2、当前端表单中没有填写年龄 age 的输入框,用户正常填写其他信息,点击注册后成功跳转到注册成功界面,控制台打印:

plaintext
1
2
3
4
5
6
muyoukule003
123
1
[smoke, drink, perm]
muyoukule003
age====>18

3、给 register.html 添加如下输入框:

html
1
年龄:<input type="number" name="age"><br>

4、重新启动服务器

4.1. 当前端页面用户未填写 age(提交的 age 是空字符串 "")的时候,点击注册后成功跳转到注册成功界面,控制台打印:

plaintext
1
2
3
4
5
6
muyoukule004
123
1
[smoke, drink, perm]
muyoukule004
age====>18

4.2. 当前端提交的 age 不是空字符串的时候:

填写age提交参数

填写信息后点击注册成功跳转到注册成功页面,控制台打印:

plaintext
1
2
3
4
5
6
muyoukule005
123
1
[smoke, drink, perm]
muyoukule005
age====>81

4. 依靠控制器方法上的形参名来接收

@RequestParam 这个注解是可以省略的,如果方法形参的名字和提交数据时的 name 相同,则 @RequestParam 可以省略。

但有一个前提:如果你采用的是 Spring6 + 版本,你需要在 pom.xml 文件中指定编译参数 -parameter,配置如下:

xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.12.1</version>
<configuration>
<source>17</source>
<target>17</target>
<compilerArgs>
<arg>-parameters</arg>
</compilerArgs>
</configuration>
</plugin>
</plugins>
</build>

PS:如果你使用的是 Spring5 的版本,以上的配置是不需要的。😁

Controller 中的方法只需要这样写:形参的名字必须和提交的数据的 name 一致!!!!!

java
1
2
3
4
5
@PostMapping(value="/register")
public String register(String username, String password, String sex, String[] hobby, String intro){
System.out.println(username + "," + password + "," + sex + "," + Arrays.toString(hobby) + "," + intro);
return "success";
}

测试结果:

依靠控制器方法上的形参名来接收参数

填写信息后点击注册成功跳转到注册成功页面,控制台打印:

plaintext
1
muyoukule006,123,1,[smoke],muyoukule006

如果形参名和提交的数据的 name 不一致:

例如前端传过来的参数为 username,而后端接收参数时写的是 String umane,就会出现 uname 值为 null 的情况:

plaintext
1
null,123,1,[smoke],muyoukule007

另外,对于提交的 hobby 数据,也可以采用 String 来接收,不一定使用数组方式:

java
1
2
3
4
5
@PostMapping(value="/register")
public String register(String username, String password, String sex, String hobby, String intro){
System.out.println(username + "," + password + "," + sex + "," + hobby + "," + intro);
return "success";
}

测试结果:

plaintext
1
muyoukule008,123,1,smoke,drink,perm,muyoukule008

根据输出结果可以看到多个 hobby 是采用 , 进行连接的。

5. 使用 POJO 类 / JavaBean 接收请求参数

以上方式大家可以看到,当提交的数据非常多时,方法的形参个数会非常多,这不是很好的设计。在 SpringMVC 中也可以使用 POJO类/JavaBean 来接收请求参数。不过有一个非常重要的要求:POJO类的属性名 必须和 请求参数的参数名 保持一致!!😁

测试

1、提供以下的 JavaBean:

java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
package com.muyoukule.pojo;

import java.util.Arrays;

public class User {
private Long id;
private String username;
private String password;
private String sex;
private String[] hobby;
private String intro;

public User() {
}

public User(Long id, String username, String password, String sex, String[] hobby, String intro) {
this.id = id;
this.username = username;
this.password = password;
this.sex = sex;
this.hobby = hobby;
this.intro = intro;
}

public Long getId() {
return id;
}

public void setId(Long id) {
this.id = id;
}

public String getUsername() {
return username;
}

public void setUsername(String username) {
this.username = username;
}

public String getPassword() {
return password;
}

public void setPassword(String password) {
this.password = password;
}

public String getSex() {
return sex;
}

public void setSex(String sex) {
this.sex = sex;
}

public String[] getHobby() {
return hobby;
}

public void setHobby(String[] hobby) {
this.hobby = hobby;
}

public String getIntro() {
return intro;
}

public void setIntro(String intro) {
this.intro = intro;
}

@Override
public String toString() {
return "User{" +
"id=" + id +
", username='" + username + '\'' +
", password='" + password + '\'' +
", sex='" + sex + '\'' +
", hobby=" + Arrays.toString(hobby) +
", intro='" + intro + '\'' +
'}';
}
}

2、在控制器方法的形参位置上使用 javabean 来接收请求参数:

java
1
2
3
4
5
@PostMapping("/register")
public String register(User user){
System.out.println(user);
return "success";
}

3、执行结果:

4、填写信息后点击注册成功跳转到注册成功页面,控制台打印:

plaintext
1
User{id=null, username='muyoukule', password='123', sex='1', hobby=[smoke, perm], intro='muyoukulehaoshuaia'}

底层的实现原理:反射机制。先获取请求参数的名字,因为请求参数的名字就是 JavaBean 的属性名,通过这种方式给对应的属性赋值

测试一下:当 JavaBean 的属性名和请求参数的参数名不一致时,会出现什么问题?

PS:getter 和 setter 的方法名不修改,只修改属性名😃

1、修改属性名

java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
package com.muyoukule.pojo;

import java.util.Arrays;


public class User {
private Long id;
private String uname;
private String upwd;
private String usex;
private String[] uhobby;
private String uintro;

public User() {
}

public User(Long id, String username, String password, String sex, String[] hobby, String intro) {
this.id = id;
this.uname = username;
this.upwd = password;
this.usex = sex;
this.uhobby = hobby;
this.uintro = intro;
}

public Long getId() {
return id;
}

public void setId(Long id) {
this.id = id;
}

public String getUsername() {
return uname;
}

public void setUsername(String username) {
this.uname = username;
}

public String getPassword() {
return upwd;
}

public void setPassword(String password) {
this.upwd = password;
}

public String getSex() {
return usex;
}

public void setSex(String sex) {
this.usex = sex;
}

public String[] getHobby() {
return uhobby;
}

public void setHobby(String[] hobby) {
this.uhobby = hobby;
}

public String getIntro() {
return uintro;
}

public void setIntro(String intro) {
this.uintro = intro;
}

@Override
public String toString() {
return "User{" +
"id=" + id +
", username='" + uname + '\'' +
", password='" + upwd + '\'' +
", sex='" + usex + '\'' +
", hobby=" + Arrays.toString(uhobby) +
", intro='" + uintro + '\'' +
'}';
}
}

2、测试,填写和上面相同的信息后点击注册成功跳转到注册成功页面,控制台打印:

plaintext
1
User{id=null, username='muyoukule', password='123', sex='1', hobby=[smoke, perm], intro='muyoukulehaoshuaia'}

通过测试,我们得知:请求参数名 可以和 JavaBean的属性名 不一致。

1、我们继续将其中一个属性的 setter 和 getter 方法名修改一下:

java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 修改前
/*public String getUsername() {
return uname;
}

public void setUsername(String username) {
this.uname = username;
}*/

// 修改后
public String getUname() {
return uname;
}

public void setUname(String uname) {
this.uname = uname;
}

2、再次测试,填写和上面相同的信息后点击注册成功跳转到注册成功页面,控制台打印:

plaintext
1
User{id=null, username='null', password='123', sex='1', hobby=[smoke, perm], intro='muyoukulehaoshuaia'}

通过测试可以看到:username 属性没有赋上值。可见请求参数是否可以赋值到 JavaBean 对应的属性上,不是取决于属性名,而是 setter 方法名

6. @RequestHeader 注解

该注解的作用是:将 请求头信息 映射到 方法的形参上

@RequestParam 注解功能相似,@RequestParam 注解的作用:将请求参数映射到方法的形参上。

当然,对于 @RequestHeader 注解来说,也有三个属性:value、required、defaultValue,和 @RequestParam 一样,这里就不再赘述。

测试

1、编写 register 方法

java
1
2
3
4
5
6
7
8
@PostMapping("/register")
public String register(User user,
@RequestHeader(value = "Referer", required = false, defaultValue = "")
String referer) {
System.out.println(user);
System.out.println(referer);
return "success";
}

2、填写信息后点击注册成功跳转到注册成功页面,控制台打印:

plaintext
1
2
User{id=null, username='muyoukule', password='123', sex='1', hobby=[smoke, drink, perm], intro='muyoukulehaoshuaia'}
http://localhost:8080/springmvc/

7. @CookieValue 注解

该注解的作用:将 请求提交的Cookie数据 映射到 方法形参

同样是有三个属性:value、required、defaultValue

测试

1、前端页面中编写发送 cookie 的代码:

html
1
2
3
4
5
6
7
<script type="text/javascript">
function sendCookie() {
document.cookie = "id=123456789; expires=Thu, 18 Dec 2025 12:00:00 UTC; path=/";
document.location = "/springmvc/register";
}
</script>
<button onclick="sendCookie()">向服务器端发送Cookie</button>

2、后端 UserController 代码:

java
1
2
3
4
5
6
7
8
9
10
11
@GetMapping("/register")
public String register(User user,
@RequestHeader(value = "Referer", required = false, defaultValue = "")
String referer,
@CookieValue(value = "id", required = false, defaultValue = "2222222222")
String id) {
System.out.println(user);
System.out.println(referer);
System.out.println(id);
return "success";
}

3、启动服务器打开页面,直接点击向 服务器端发送Cookie 按钮测试,控制台打印:

plaintext
1
2
3
User{id=null, username='null', password='null', sex='null', hobby=null, intro='null'}
http://localhost:8080/springmvc/
123456789

8. 请求的中文乱码问题

8.1 get 请求乱码

get 请求数据在 URI 后面提交,这个乱码问题怎么解决呢?

解决办法是找到 CATALINA_HOME/config/server.xml 文件,找到其中配置端口号的标签 <Connector>,在该标签中添加 URIEncoding="UTF-8"

但是对于高版本的 Tomcat 服务器来说,是不需要设置的,例如 Tomcat10,Tomcat9,有如下的默认配置,在默认情况下 URIEncoding 使用的就是 UTF-8 的编码方式。

Tomcat10编码方式

但对于低版本的 Tomcat 服务器,例如:Tomcat8。URIEncoding 的默认配置是 ISO-8859-1,因此在 Tomcat8 中需要手动配置 server.xml 文件:

Tomcat8编码方式

conf 目录下打开 server.xml 文件,配置如下:

Tomcat8配置UTF-8编码方式

测试默认情况下,Tomcat10 是否已经解决了 get 请求乱码问题

html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<form th:action="@{/register}" method="get">
用户名:<input type="text" name="username"><br>
密码:<input type="password" name="password"><br>
性别:
<input type="radio" name="sex" value="1">
<input type="radio" name="sex" value="0">
<br>
爱好:
抽烟 <input type="checkbox" name="hobby" value="smoke">
喝酒 <input type="checkbox" name="hobby" value="drink">
烫头 <input type="checkbox" name="hobby" value="perm">
<br>
简介:<textarea rows="10" cols="60" name="intro"></textarea><br>
<input type="submit" value="注册">
</form>

注意,以上表单已经修改为 get 请求了。

java
1
2
3
4
5
@GetMapping("/register")
public String register(User user){
System.out.println(user);
return "success";
}

测试结果:

测试Tomcat10是否处理get请求乱码 Tomcat10get请求注册成功

控制台打印:

plaintext
1
User{id=null, username='木又枯了', password='123', sex='1', hobby=[smoke, drink, perm], intro='木又枯了太酷了!'}

8.2 post 请求乱码

post 请求是解决请求体的中文乱码问题。解决办法大家都知道:

java
1
request.setCharacterEncoding("UTF-8");

同样,对于高版本的 Tomcat10 服务器来说,针对请求体中的字符编码也是配置好的,默认也是采用了 UTF-8,中文乱码问题也解决了,在这个文件中配置的:apache-tomcat-10.1.19\conf\web.xml

配置内容如下:

Tomcat10针对请求体中的字符编码是配置好的

通过以上配置可以看到,Tomcat10 对请求和响应都设置了默认的字符编码方式为 UTF-8

PS:Tomcat9 以及之前的版本,以上的配置是没有的。

测试针对 Tomcat10,SpringMVC 会不会有乱码问题

html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<form th:action="@{/register}" method="post">
用户名:<input type="text" name="username"><br>
密码:<input type="password" name="password"><br>
性别:
<input type="radio" name="sex" value="1">
<input type="radio" name="sex" value="0">
<br>
爱好:
抽烟 <input type="checkbox" name="hobby" value="smoke">
喝酒 <input type="checkbox" name="hobby" value="drink">
烫头 <input type="checkbox" name="hobby" value="perm">
<br>
简介:<textarea rows="10" cols="60" name="intro"></textarea><br>
<input type="submit" value="注册">
</form>

PS:以上表单已经修改为 post 请求

java
1
2
3
4
5
@PostMapping("/register")
public String register(User user) {
System.out.println(user);
return "success";
}

测试结果:

plaintext
1
User{id=null, username='木又枯了', password='123', sex='1', hobby=[smoke, drink, perm], intro='木又枯了太酷啦!!'}

通过测试可以看到在 Tomcat10 当中,默认 SpringMVC,发送 POST 请求,是不会出现乱码问题的。

模拟乱码

如果不是 Tomcat10,则会出现乱码问题,我们来模拟一下乱码的产生,将 apache-tomcat-10.1.19\conf\web.xml 文件中的 UTF-8 配置修改为 ISO-8859-1

xml
1
2
3
<!-- Set the default request and response character encodings to ISO-8859-1.   -->
<request-character-encoding>ISO-8859-1</request-character-encoding>
<response-character-encoding>ISO-8859-1</response-character-encoding>

一定要重启 Tomcat10,新的配置才能生效,来测试一下是否存在乱码:

plaintext
1
User{id=null, username='??¨?????????', password='123', sex='1', hobby=[smoke, drink, perm], intro='??¨??????????¤?é?·?????????'}

那么,在 SpringMVC 中如何解决请求体的中文乱码问题呢?

当然,还是使用 request.setCharacterEncoding("UTF-8"),使用它有一个前提条件,要想解决请求体乱码问题,以上代码必须在 request.getParameter("username") 执行之前执行才有效。

也就是说以上代码如果放在 Controller 的相关方法中执行是无效的,因为 Controller 的方法在执行之前 DispatcherServlet 已经调用了 request.getParameter("username") 方法。因此在 Controller 方法中使用 request.setCharacterEncoding("UTF-8") 无效。

我们来测试一下(PS:注意表单为 post 请求):

java
1
2
3
4
5
6
@PostMapping("/register")
public String register(User user, HttpServletRequest request) throws UnsupportedEncodingException {
request.setCharacterEncoding("UTF-8");
System.out.println(user);
return "success";
}

测试结果:

plaintext
1
User{id=null, username='??¨?????????', password='123', sex='1', hobby=[smoke, drink, perm], intro='??¨??????????¤?é?·?????????'}

通过测试可以看到:在 Controller 当中调用 request.setCharacterEncoding("UTF-8") 是无法解决 POST 乱码问题的。

那怎么办呢?怎么样才能在 DispatcherServlet 之前执行 request.setCharacterEncoding("UTF-8") 呢?

没错,我相信大家想到了:过滤器 Filter。过滤器 Filter 可以在 Servlet 执行之前执行。有人又说了:监听器不行吗?不行。因为我们需要对每一次请求解决乱码,而监听器只在服务器启动阶段执行一次。因此这里解决每一次请求的乱码问题,应该使用过滤器 Filter。

1、编写过滤器 CharacterEncodingFilter

java
1
2
3
4
5
6
7
8
9
10
11
public class CharacterEncodingFilter implements Filter {
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
// 设置请求体的字符集
request.setCharacterEncoding("UTF-8");
// 设置响应的字符集
response.setContentType("text/html;charset=UTF-8");
// 执行下一个资源
chain.doFilter(request, response);
}
}

2、在 web.xml 里配置过滤器

xml
1
2
3
4
5
6
7
8
9
<!--配置字符编码过滤器-->
<filter>
<filter-name>characterEncodingFilter</filter-name>
<filter-class>com.muyoukule.filter.CharacterEncodingFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>characterEncodingFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>

3、编写方法

java
1
2
3
4
5
@PostMapping("/register")
public String register(User user) {
System.out.println(user);
return "success";
}

4、启动服务器,填写信息后点击注册成功跳转到注册成功页面,控制台打印:

plaintext
1
User{id=null, username='木又枯了', password='123', sex='1', hobby=[smoke, drink, perm], intro='木又枯了太酷啦!!!'}

SpringMVC 框架内置字符编码过滤器 CharacterEncodingFilter

除此以外,SpringMVC 框架内置了字符编码过滤器,叫做 CharacterEncodingFilter,我们直接配置好即可使用。

CharacterEncodingFilter 中最核心的方法是:

java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@Override
protected void doFilterInternal(
HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {

String encoding = getEncoding();
if (encoding != null) {
if (isForceRequestEncoding() || request.getCharacterEncoding() == null) {
request.setCharacterEncoding(encoding);
}
if (isForceResponseEncoding()) {
response.setCharacterEncoding(encoding);
}
}
filterChain.doFilter(request, response);
}

分析以上核心方法得知该过滤器对请求和响应都设置了字符编码方式。

  • 强行使用请求字符编码方式为true 时,或者 请求对象的字符编码方式为null 时,设置请求的字符编码方式。
  • 强行使用响应字符编码方式为true 时,设置响应的字符编码方式。

根据以上代码,可以得出以下配置信息,在 web.xml 文件中对过滤器进行如下配置:

xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<!--使用SpringMVC框架内置的字符编码过滤器-->
<filter>
<filter-name>characterEncodingFilter</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>forceRequestEncoding</param-name>
<param-value>true</param-value>
</init-param>
<init-param>
<param-name>forceResponseEncoding</param-name>
<param-value>true</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>characterEncodingFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>

重启 Tomcat10,看看乱码是否能够解决?

plaintext
1
User{id=null, username='木又枯了', password='123', sex='1', hobby=[smoke, drink, perm], intro='木又枯了太酷啦!!'}

PS:针对于我们当前的 Tomcat10 的配置来说,它有默认的字符集 ISO-8859-1,因此以下在 web.xml 文件中的配置是不能缺少的:

xml
1
2
3
4
<init-param>
<param-name>forceRequestEncoding</param-name>
<param-value>true</param-value>
</init-param>

如果缺少它,仍然是会存在乱码问题的。自行测试一下!!!!