【SpringMVC笔记】 - 3 - 获取请求数据
假设请求:
url
http://localhost:8080/springmvc/register?name=zhangsan&password=123&email=zhangsan@qq.com
核心问题:
- SpringMVC中如何获取请求提交的数据?
- SpringMVC中如何获取请求头信息?
- SpringMVC中如何获取客户端提交的Cookie数据?
一、准备工作
1. 创建模块,添加依赖(pom.xml)
xml
<?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.powernode.springmvc</groupId>
<artifactId>springmvc-003</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>war</packaging>
<dependencies>
<!--springmvc依赖-->
<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依赖(Jakarta EE)-->
<dependency>
<groupId>jakarta.servlet</groupId>
<artifactId>jakarta.servlet-api</artifactId>
<version>6.0.0</version>
<scope>provided</scope> <!--provided:服务器已提供,打包时不包含-->
</dependency>
<!--thymeleaf和spring6整合依赖(视图渲染)-->
<dependency>
<groupId>org.thymeleaf</groupId>
<artifactId>thymeleaf-spring6</artifactId>
<version>3.1.2.RELEASE</version>
</dependency>
</dependencies>
<properties>
<maven.compiler.source>21</maven.compiler.source> <!--JDK版本-->
<maven.compiler.target>21</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <!--编码格式-->
</properties>
</project>
2. 添加web支持(编写web.xml)
核心配置前端控制器(DispatcherServlet),指定SpringMVC配置文件路径,设置启动时初始化。
xml
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="https://jakarta.ee/xml/ns/jakartaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="https://jakarta.ee/xml/ns/jakartaee https://jakarta.ee/xml/ns/jakartaee/web-app_6_0.xsd"
version="6.0">
<!--前端控制器:SpringMVC的核心,统一处理请求-->
<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> <!--类路径下的springmvc.xml-->
</init-param>
<!--load-on-startup=1:服务器启动时初始化DispatcherServlet,提升第一次访问效率-->
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>springmvc</servlet-name>
<url-pattern>/</url-pattern> <!--拦截所有请求(除了.jsp)-->
</servlet-mapping>
</web-app>
3. 创建UserController(基础控制器)
用于跳转注册页面,后续扩展请求处理方法。
java
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
@Controller // 标识为SpringMVC控制器,交给Spring容器管理
public class UserController {
// 跳转注册页面:请求路径为"/",返回视图名为"register"(对应WEB-INF/templates/register.html)
@RequestMapping("/")
public String toRegisterPage(){
return "register";
}
}
4. 编写SpringMVC核心配置文件(springmvc.xml)
核心配置:组件扫描(扫描控制器)、视图解析器(Thymeleaf)。
xml
<?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">
<!--组件扫描:扫描指定包下的注解(@Controller、@Service等)-->
<context:component-scan base-package="com.zzz.springmvc.controller"/>
<!--Thymeleaf视图解析器:负责将视图名解析为具体的HTML页面-->
<bean id="thymeleafViewResolver" class="org.thymeleaf.spring6.view.ThymeleafViewResolver">
<!--视图渲染时的编码字符集-->
<property name="characterEncoding" value="UTF-8"/>
<!--视图解析器优先级:值越小,优先级越高(若有多个视图解析器)-->
<property name="order" value="1"/>
<!--关联Thymeleaf模板引擎-->
<property name="templateEngine">
<bean class="org.thymeleaf.spring6.SpringTemplateEngine">
<!--关联模板解析器:负责加载和解析HTML模板-->
<property name="templateResolver">
<bean class="org.thymeleaf.spring6.templateresolver.SpringResourceTemplateResolver">
<!--模板文件前缀:HTML文件所在路径-->
<property name="prefix" value="/WEB-INF/templates/"/>
<!--模板文件后缀:HTML文件扩展名-->
<property name="suffix" value=".html"/>
<!--模板类型:HTML-->
<property name="templateMode" value="HTML"/>
<!--模板读取时的编码字符集-->
<property name="characterEncoding" value="UTF-8"/>
</bean>
</property>
</bean>
</property>
</bean>
</beans>
5. 编写register.html(注册页面,视图层)
放在 /WEB-INF/templates/ 目录下(与springmvc.xml中模板前缀对应),后续会逐步完善表单。
html
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org"> <!--引入Thymeleaf命名空间-->
<head>
<meta charset="UTF-8">
<title>用户注册</title>
</head>
<body>
<h3>用户注册</h3>
<hr>
</body>
</html>
6. 部署测试
将模块部署到Tomcat服务器(建议Tomcat10+),启动服务器后访问http://localhost:8080/springmvc/,能正常显示注册页面即部署成功。
二、获取请求提交的数据(4种核心方式,含完整测试)
方式1:使用原生Servlet API获取(不推荐)
核心原理
SpringMVC会自动将当前请求对象(HttpServletRequest)传递给控制器方法的形参,通过该对象调用 getParameter()、getParameterValues()等方法获取请求参数。
步骤1:完善register.html表单(添加提交控件)
html
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>用户注册</title>
</head>
<body>
<h3>用户注册</h3>
<hr>
<!--Thymeleaf的action写法:@{/register} 对应控制器的请求路径-->
<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:在UserController中添加请求处理方法
java
import jakarta.servlet.http.HttpServletRequest;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.stereotype.Controller;
import java.util.Arrays;
@Controller
public class UserController {
@RequestMapping("/")
public String toRegisterPage(){
return "register";
}
// 处理注册请求:请求路径/register,请求方式post
@PostMapping(value="/register")
public String register(HttpServletRequest request){
// 1. 获取单个请求参数(用户名、密码、性别、简介)
String username = request.getParameter("username");
String password = request.getParameter("password");
String sex = request.getParameter("sex");
String intro = request.getParameter("intro");
// 2. 获取多个同名参数(爱好,checkbox)
String[] hobbies = request.getParameterValues("hobby");
// 3. 控制台打印测试
System.out.println(username + "," + password + "," + sex + "," + Arrays.toString(hobbies) + "," + intro);
// 返回成功页面(需创建success.html,路径/WEB-INF/templates/success.html)
return "success";
}
}
步骤3:创建success.html(成功页面)
html
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>注册成功</title>
</head>
<body>
<h1>注册成功</h1>
</body>
</html>
步骤4:测试结果
- 访问注册页面,填写表单并提交;
- 打开浏览器F12,查看Network,确认请求已提交所有参数;
- 查看IDEA控制台,能正常打印所有提交的参数(如:zhangsan,123,1,[smoke,drink],我是张三)。
注意事项(不推荐使用的原因)
- 控制器方法依赖Servlet原生API(HttpServletRequest),导致控制器无法单独测试,必须依赖WEB服务器;
- 违背SpringMVC"解耦"的核心思想,若使用原生API,无需依赖SpringMVC框架。
方式2:使用@RequestParam注解标注(推荐,灵活)
核心作用
将请求参数(表单name、URL参数)与控制器方法的形参进行强制映射,无需依赖Servlet API。
1. 基本使用(形参名与请求参数名不一致)
java
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.stereotype.Controller;
import java.util.Arrays;
@Controller
public class UserController {
// 省略toRegisterPage()方法...
@PostMapping(value = "/register")
public String register(
// @RequestParam(value="请求参数名"):将请求参数映射到形参
@RequestParam(value="username") String a, // 请求参数name=username → 形参a
@RequestParam(value="password") String b, // 请求参数name=password → 形参b
@RequestParam(value="sex") String c, // 请求参数name=sex → 形参c
@RequestParam(value="hobby") String[] d, // 请求参数name=hobby(多个)→ 形参数组d
@RequestParam(name="intro") String e // name属性与value属性作用完全一致
) {
// 控制台打印测试
System.out.println(a);
System.out.println(b);
System.out.println(c);
System.out.println(Arrays.toString(d));
System.out.println(e);
return "success";
}
}
测试注意
若@RequestParam的value(或name)与请求参数名不一致,会抛出400错误(参数缺失),例如:将value="username"改为value="uname",但表单中name仍为username,会报错:Required parameter 'uname' is not present。
2. required属性(控制参数是否必需)
默认值:true(表示该请求参数必须提交,否则报错400);可设置为false(参数非必需,未提交时形参为null)。
java
@PostMapping(value = "/register")
public String register(
@RequestParam(value="username") String username,
@RequestParam(value="password") String password,
@RequestParam(value="age", required = false) String age // age非必需
) {
System.out.println("用户名:" + username);
System.out.println("密码:" + password);
System.out.println("年龄:" + age); // 未提交age时,输出null
return "success";
}
测试结果
- 不提交age参数:控制台输出"年龄:null",无报错;
- required默认true(不写required),不提交age:报错400,提示"Required parameter 'age' is not present"。
3. defaultValue属性(设置形参默认值)
作用:当请求参数未提交,或提交的参数值为空字符串("")时,形参使用默认值(优先级高于required=false)。
java
@PostMapping(value = "/register")
public String register(
@RequestParam(value="username") String username,
@RequestParam(value="email", required = false, defaultValue = "default@163.com") String email
) {
System.out.println("用户名:" + username);
System.out.println("邮箱:" + email);
return "success";
}
测试场景及结果
- 场景1:未提交email参数 → 邮箱:default@163.com;
- 场景2:提交email参数,但值为空(表单中输入框留空) → 邮箱:default@163.com;
- 场景3:提交email参数(如zhangsan@powernode.com) → 邮箱:zhangsan@powernode.com。
方式3:省略@RequestParam注解(简化,推荐)
核心前提
控制器方法的形参名 ,必须与请求参数的name完全一致(大小写敏感)。
补充:Spring6+版本,需在pom.xml中配置maven-compiler-plugin的-parameters参数(保留形参名,否则编译后形参名会变为arg0、arg1...);Spring5及以下版本无需配置。
步骤1:配置pom.xml(Spring6+必需)
xml
<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> <!--保留形参名,Spring6+必需-->
</compilerArgs>
</configuration>
</plugin>
</plugins>
</build>
步骤2:控制器方法编写(省略@RequestParam)
java
@PostMapping(value="/register")
public String register(String username, String password, String sex, String[] hobby, String intro){
// 形参名(username、password等)与表单name完全一致
System.out.println(username + "," + password + "," + sex + "," + Arrays.toString(hobby) + "," + intro);
return "success";
}
测试结果
填写表单提交后,控制台正常打印所有参数,与方式2效果一致。
补充细节
- 形参名与请求参数名不一致 → 形参值为null(无报错,除非参数必需);
- 多个同名参数(如hobby),可使用String数组接收(String[] hobby),也可使用String接收(此时多个值用逗号拼接,如"smoke,drink"):
java
// String接收多个同名参数
@PostMapping(value="/register")
public String register(String username, String password, String sex, String hobby, String intro){
System.out.println(hobby); // 输出:smoke,drink(选中两个爱好时)
return "success";
}
方式4:使用POJO/JavaBean接收(推荐,参数较多时)
核心前提
JavaBean的属性名 ,必须与请求参数的name完全一致(底层通过反射机制赋值)。
步骤1:创建User类(JavaBean)
java
import java.util.Arrays;
// 必须提供无参构造器(Spring反射赋值必需),有参构造器可选
public class User {
// 属性名与请求参数name完全一致(username、password、sex、hobby、intro)
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;
}
// 所有属性的getter和setter方法(必需,Spring通过setter赋值)
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;
}
// toString方法(用于控制台打印测试)
@Override
public String toString() {
return "User{" +
"id=" + id +
", username='" + username + '\'' +
", password='" + password + '\'' +
", sex='" + sex + '\'' +
", hobby=" + Arrays.toString(hobby) +
", intro='" + intro + '\'' +
'}';
}
}
步骤2:控制器方法编写(形参为User对象)
java
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.stereotype.Controller;
@Controller
public class UserController {
// 省略toRegisterPage()方法...
// 形参为User对象,Spring自动将请求参数赋值到User的对应属性
@PostMapping("/register")
public String register(User user){
System.out.println(user); // 调用User的toString()方法打印
return "success";
}
}
测试结果
填写表单提交后,控制台打印User对象信息(如:User{id=null, username='zhangsan', password='123', sex='1', hobby=[smoke,drink], intro='我是张三'}),说明赋值成功。
关键细节(底层原理)
Spring赋值的核心是setter方法,而非属性名:
- 场景1:属性名与请求参数名不一致,但setter方法名对应(如属性名uname,setter方法setUsername()) → 赋值成功;
- 场景2:属性名与请求参数名一致,但setter方法名错误(如属性名username,setter方法setUname()) → 赋值失败(属性值为null)。
测试验证(修改User类,验证setter的作用):
java
public class User {
// 属性名改为uname(与请求参数username不一致)
private String uname;
// setter方法名仍为setUsername(与请求参数username一致)
public void setUsername(String username) {
this.uname = username;
}
// getter方法名改为getUsername
public String getUsername() {
return uname;
}
// 其他属性和方法不变...
}
测试结果:username参数能正常赋值到uname属性(控制台打印uname的值为提交的用户名)。
三、获取请求头信息(@RequestHeader注解)
核心作用
将请求头信息(如Referer、User-Agent)与控制器方法的形参映射,用法与@RequestParam完全一致,包含3个属性:value(请求头名)、required(是否必需)、defaultValue(默认值)。
测试代码
java
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.stereotype.Controller;
@Controller
public class UserController {
// 省略toRegisterPage()方法...
@PostMapping("/register")
public String register(
User user,
// 获取请求头Referer(当前请求的来源页面)
@RequestHeader(value="Referer", required = false, defaultValue = "") String referer
) {
System.out.println("用户信息:" + user);
System.out.println("请求来源:" + referer); // 输出:http://localhost:8080/springmvc/
return "success";
}
}
测试结果
提交表单后,控制台打印请求头Referer的值(即注册页面的URL),若直接访问/register(无来源),则打印默认值(空字符串)。
四、获取Cookie数据(@CookieValue注解)
1. 核心作用
将客户端提交的 Cookie 数据 (通过请求头 Cookie 字段携带)与控制器方法的形参进行映射。
用法与 @RequestParam、@RequestHeader 保持一致,同样支持三个属性。
2. 可使用属性
value / name:指定 Cookie 的 keyrequired:是否必须携带该 Cookie,默认truedefaultValue:Cookie 不存在时使用的默认值
3. 步骤 1:前端页面添加发送 Cookie 的代码(register.html)
在注册页面中通过 JS 设置 Cookie,并发送到服务器:
html
<!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>
<!-- JS 设置并发送 Cookie -->
<script type="text/javascript">
function sendCookie(){
// 设置Cookie:key=id,value=123456789,设置有效期和路径
document.cookie = "id=123456789; expires=Thu, 18 Dec 2025 12:00:00 UTC; path=/";
// 跳转到注册接口,携带Cookie
document.location = "/springmvc/register";
}
</script>
<br>
<button onclick="sendCookie()">向服务器端发送 Cookie</button>
</body>
</html>
4. 步骤 2:控制器使用 @CookieValue 获取 Cookie
在 UserController 中编写方法,使用 @CookieValue 接收 Cookie:
java
@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:" + user);
System.out.println("Referer:" + referer);
System.out.println("Cookie中的id:" + id);
return "success";
}
5. 测试结果说明
- 点击【向服务器端发送 Cookie】按钮后,JS 设置 Cookie 并跳转
- 后端成功获取到
id=123456789 - 若客户端未携带该 Cookie,则使用
defaultValue="2222222222" - 若未设置默认值且
required=true,缺少 Cookie 会报 400 错误
六、请求中文乱码问题解决
1. GET 请求乱码
-
原因 :GET 请求参数拼接在 URI 中,低版本 Tomcat 默认使用
ISO-8859-1编码 -
高版本 Tomcat(9/10):
-
URI 编码默认已配置为
UTF-8,无需处理 -
低版本 Tomcat(8 及以下):
修改
server.xml的<Connector>标签,添加:xmlURIEncoding="UTF-8"
2. POST 请求乱码
(1)为什么 Controller 中设置编码无效
java
request.setCharacterEncoding("UTF-8");
- 在 Controller 方法中执行时,SpringMVC 已经通过
request.getParameter()获取过参数 - 编码设置晚于参数获取,因此无效
(2)解决方案:使用 Spring 提供的字符编码过滤器
SpringMVC 内置 CharacterEncodingFilter,在 DispatcherServlet 之前执行,统一设置编码。
(3)在 web.xml 中配置过滤器
xml
<!-- 字符编码过滤器 -->
<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>
(4)关键说明
encoding=UTF-8:指定使用 UTF-8 编码forceRequestEncoding=true:强制覆盖请求编码,即使已有编码也会被替换forceResponseEncoding=true:同时强制设置响应编码- 过滤器路径
/*:对所有请求生效 - 配置后,POST 中文乱码彻底解决