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

0. 思考

假设有这样一个请求:

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

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

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

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

1. 环境准备

1、创建模块,添加依赖

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文件

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

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

5、编写springmvc.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文件:

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 中准备一个注册的表单:

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、接下来在控制器添加一个方法来处理这个注册的请求:

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、提供视图页面:

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、控制台打印:

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方法

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。

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、控制台打印:

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程序中如何获取呢?使用占位符方式。

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,配置如下:

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属性

1
boolean required() default true;

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

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

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

测试

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

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

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

未提供参数age报错

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

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

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

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

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

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

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

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

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

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

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

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"属性

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

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

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

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

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

4、重新启动服务器

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

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

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

填写age提交参数

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

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

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

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

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

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一致!!!!!

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";
}

测试结果:

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

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

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

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

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

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

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

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";
}

测试结果:

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

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

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

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

测试

1、提供以下的JavaBean:

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来接收请求参数:

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

3、执行结果:

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

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

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

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

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

1、修改属性名

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、测试,填写和上面相同的信息后点击注册成功跳转到注册成功页面,控制台打印:

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

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

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

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、再次测试,填写和上面相同的信息后点击注册成功跳转到注册成功页面,控制台打印:

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方法

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、填写信息后点击注册成功跳转到注册成功页面,控制台打印:

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的代码:

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代码:

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 按钮测试,控制台打印:

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请求乱码问题

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请求了。

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

测试结果:

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

控制台打印:

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

8.2 post请求乱码

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

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

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

配置内容如下:

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

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

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

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

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请求

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

测试结果:

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

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,新的配置才能生效,来测试一下是否存在乱码:

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请求):

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";
}

测试结果:

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

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里配置过滤器

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、编写方法

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

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

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

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

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

CharacterEncodingFilter 中最核心的方法是:

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文件中对过滤器进行如下配置:

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,看看乱码是否能够解决?

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

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

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

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