文章目录
- 一、ServletAPI获取
- 二、控制器方法形参获取
- 2.1 不使用@RequestParam注解
- 2.2 使用@RequestParam注解
- 2.2.1 简介&使用
- 2.2.2 相关注解
- 2.3 实体类类型的形参
- 三、中文乱码问题
- 3.1 在tomcat中设置编码(了解)
- 3.2 使用Spring MVC内部过滤器设置编码
学习视频🎥:https://www.bilibili.com/video/BV1Ry4y1574R
一、ServletAPI获取
💬概述:ServletAPI表示原生的Servlet接口和方法,Spring MVC可以使用原生的Servlet API来获取请求参数,直接通过request.getParameter("参数名")
获取请求参数。但Spring MVC已经对原生的Servlet API进行过封装,所以不推荐直接使用原生的Servlet API获取请求参数
🔑使用:在控制器方法中添加HttpServletRequest
类型的形参req
,该形参就是原生servlet中的请求对象,封装了当前请求的请求报文对象,可以通过它直接调用getParameter("参数名")
获取请求参数
@RequestMapping("/testParam")
public String showTestParam(HttpServletRequest req) {
String username = req.getParameter("username");
String pwd = req.getParameter("password");
System.out.println("用户名:" + username);
System.out.println("密码:" + pwd);
return "success";
}
💡 因为控制器方法都是Spring MVC底层去调用,不用我们手动调用,所以Spring MVC底层会自动找到
HttpServletRequest
类型的形参,然后将当前请求中的请求对象赋值给形参req
,因此req
就表示当前请求(或者说封装了当前请求报文信息的请求对象)
二、控制器方法形参获取
💡 Spring MVC已经对获取请求参数的方式进行过封装,所以可以很方便地直接通过控制器方法形参来获取,建立形参与请求参数的映射关系有下列三种方式
2.1 不使用@RequestParam注解
💬概述:在不使用@RequestParam
注解的情况下,可以直接在控制器方法的形参位置可添加与某个请求参数同名的形参,当浏览器发起的请求匹配到该控制器方法时,DispatcherServlet
(或者说Spring MVC)会直接根据参数名将对应的参数值赋值给形参
❓ 关于形参名和形参类型
- 当形参名与请求参数名不一样时(不使用
@RequestParam
注解的情况下),Spring MVC就无法根据请求参数名找到对应的形参,即无法建立映射关系,从而获取不到对应的参数值(获取到null),但页面不会报错。比如请求参数设置为user_name
,后台控制器方法形参设置为username
,则后台打印出来的username
值为null- 浏览器发送过来的请求参数值都是String类型,而形参类型不是String类型,但形参名与其中的请求参数同名时,Spring MVC会尝试将该请求参数值转成形参对应的类型,如果类型转化成功就能成功赋值(如"123"–>int类型👌);但如果类型转化失败(如"Key"–>int类型❌),页面就会报400错误
🔑测试1.0——获取请求参数的单个值
① 创建测试超链接,带上两个请求参数(username、password)
<a th:href="@{/testParam01(username='Key',password=123)}">测试形参获取请求参数</a>
② 创建对应的控制器方法,方法上添加两个形参,分别对应两个请求参数,同时保持形参名与请求参数名一致
@RequestMapping("/testParam01")
public String showTestParam01(String username, String password) {
System.out.println("用户名:" + username);
System.out.println("密码:" + pwd);
return "success";
}
💡 用非rest方式书写的路径,在
@RequestMapping
的value
属性值无需中使用占位符,直接写/testParam01
即可,即当前请求不用带上请求参数就能与控制器方法showTestParam()
匹配上
🔑测试2.0——获取请求参数的多个值
① 创建测试表单,表单中添加一个复选框,实现一个多值的请求参数(hobby)
<form th:action="@{/testParam02}" method="post">
用户名:<input type="text" name="username"><br/>
爱好:<input type="checkbox" name="hobby" value="a">a
<input type="checkbox" name="hobby" value="b">b
<input type="checkbox" name="hobby" value="c">c <br/>
<input type="submit" value="测试同名形参获取请求参数">
</form>
② 创建对应的控制器方法,方法中对应多值请求参数的形参有两种写法(形参名与请求参数名仍然要保持一致)
2.1 直接通过字符串类型形参获取全部请求参数值,获取的字符串结果中,多个请求参数值之间用,
隔开(隔开后拼成的还是一个字符串)
@RequestMapping("/testParam02")
public String showTestParam02(String username, String hobby) {
System.out.println("用户名:" + username);
System.out.println("爱好:" + hobby);
return "success";
}
/* 打印结果为:
用户名:Key
爱好:a,b,c
*/
2.2 通过字符串类型数组获取全部请求参数值,请求参数值为数组中的元素(跟req.getParameterValues()
获取的结果类似)
@RequestMapping("/testParam02")
public String showTestParam02(String username, String[] hobby) {
System.out.println("用户名:" + username);
System.out.println("爱好:" + Arrays.toString(hobby));
return "success";
}
/* 打印结果为:
用户名:Key
爱好:[a,b,c]
*/
2.2 使用@RequestParam注解
2.2.1 简介&使用
🔑添加的位置:在控制器方法的形参前添加
🔑作用:将请求参数与控制器方法的形参建立映射关系,注解的value
值对应请求参数名,可以与形参名不一样
💡 在添加了
@RequestParam
注解后,Spring MVC就会根据注解的value
值匹配对应的请求参数,从而匹配到注解所标识的形参,然后将请求参数值赋值给对应形参,所以在形参名与请求参数名不一致的情况同样能获取到对应的请求参数值
💡 测试
① 创建测试表单,带上请求参数(注意请求参数名设置为user_name、password)
<form th:action="@{/testParam03}" method="post">
用户名:<input type="text" name="user_name"><br/>
密码:<input type="password" name="password"><br/>
<input type="submit" value="测试@RequestParam注解">
</form>
② 创建对应的控制器方法,方法上形参前添加@RequestParam
注解,注解的value
属性值对应请求参数名,而形参名(username、pwd)与请求参数名可以不一致
@RequestMapping("/testParam03")
public String showTestParam03(
@RequestParam("user_name") String username,
@RequestParam("password") String pwd) {
System.out.println("用户名:" + username);
System.out.println("密码:" + pwd);
return "success";
}
🔑三个属性
① value:对应请求参数名,即指定给形参赋值的请求参数名
② required:设置是否必须传输value
值对应的请求参数,默认为true
❓
required
的两个属性值
- true:表示必须传输对应的请求参数,即请求参数列表中必须要有
value
属性值对应的参数。如果没有传输对应参数(且没有设置defaultValue
属性值),页面就会报400错误- false:表示可以不传输对应的请求参数,即请求参数列表中可以没有
value
属性值对应的参数。如果没有传输对应参数,后台控制器方法对应形参值为null,即获取不到值,但页面不会报错
③ defaultValue:为对应形参指定默认值,即不管required
值为true或者false,当value
属性值对应的请求参数没有被传输或者值为空(空字符串"")时,控制器方法对应形参都能获取到值,该值就是defaultValue
的属性值
2.2.2 相关注解
💡 下面两个注解使用方式与
@RequestParam
类似(请求参数、请求头信息和cookie数据都是键值对的形式),这里只简单介绍
① @RequestHeader
- 添加位置:控制器方法形参前
- 作用:将请求头信息与控制器方法形参建立映射关系,可以将请求头中某个参数的参数值赋值给对应的形参
- 三个属性(与
@RequestParam
注解的三个属性用法一样)
Ⅰ. value:对应请求头中某个参数名
Ⅱ. required:设置是否必须传输value
值对应的请求头中某个参数,默认为true
Ⅲ. defaultValue:为对应形参指定默认值
② @CookieValue
- 添加位置:控制器方法形参前
- 作用:将cookie数据与控制器方法形参建立映射关系,可以将cookie数据值(value)赋值给对应的形参
- 三个属性(与
@RequestParam
注解的三个属性用法一样)
Ⅰ. value:对应cookie中某个数据名(key)
Ⅱ. required:设置是否必须传输value
值对应的cookie数据(key),默认为true
Ⅲ. defaultValue:为对应形参指定默认值
2.3 实体类类型的形参
💬概述:在控制器方法中添加一个实体类类型的形参,当请求参数列表中有参数的名称与实体类成员变量的名称一样时,Spring MVC就会直接将对应请求参数值赋值给与其同名的实体类成员变量
❓ 关于实体类中成员变量类型:请求参数值都是String类型,而实体类中成员变量不是String类型时,但变量名与某个请求参数名一样时,此时Spring MVC会尝试将同名的请求参数值转成成员变量对应的类型,如果类型转化成功就能成功赋值;如果类型转化失败,页面就会报400错误(与普通类型的形参类型转化一样)
🔑测试
① 创建测试实体类User,成员变量有id、username、gender、age
public class User {
private Integer id;
private String username;
private String gender;
private Integer age;
// 省略其他方法...
}
② 创建测试表单,表单中每一项分别对应实体类的每一个成员变量(除了id)
<form th:action="@{/testBean}" method="post">
姓名:<input type="text" name="username"><br/>
年龄:<input type="text" name="age"><br/>
性别:<input type="radio" name="gender" value="男">男
<input type="radio" name="gender" value="女">女 <br/>
<input type="submit" value="测试实体类类型形参">
</form>
③ 创建对应的控制器方法,方法中添加User类型的形参
@RequestMapping("/testBean")
public String showTestBean(User user) {
System.out.println(user);
return "success";
}
❓ 如果再添加其他形参
- 如果再添加一个User类型的形参
userInfo
,userInfo
对象中的成员变量同样也会被赋上对应的请求参数值,即打印user
和userInfo
的结果是一样的- 如果再添加一个普通类型的形参,且与其中某个请求参数同名,则该形参仍然可以被赋上对应同名的请求参数的值。如上述测试中,控制器方法再添加一个形参
String age
的话,形参age
依然能被赋上请求参数age
的值
三、中文乱码问题
💡 对于原生的servlet API获取请求参数,还可以对获取到的参数进行重新编码——
String username = New String(req.getParameter("username").getBytes("ISO8859-1"), "utf-8")
,从而解决中文乱码问题。但太麻烦而且对于Spring MVC来说显然不适用
3.1 在tomcat中设置编码(了解)
🔑适合的请求方式:只能解决GET请求时遇到中文乱码问题,对于POST请求还是无法解决,需要使用Spring MVC内部过滤器
🔑配置方式
① tomcat7.0之前:对于tomcat7.0之前的版本,GET方式提交的参数编码只支持ISO8859-1
编码,所以需要我们手动修改——找到tomcat的server.xml 文件,在<Connector>
标签中添加URIEncodeing="UTF-8"
(或者设置为"GBK")
<Connector executor="tomcatThreadPool"
port="8080" protocol="HTTP/1.1"
connectionTimeout="20000"
redirectPort="8443"
<!-- 添加参数 -->
URIEncoding="UTF-8"
useBodyEncodingForURI="true"/>
② tomcat8.0之后:对于tomcat8.0之后的版本,GET请求方式的默认解码已经是UTF-8
。所以无需对server.xml文件中的<Connector>
标签进行改动
3.2 使用Spring MVC内部过滤器设置编码
💬概述:在tomcat中配置编码只能解决GET请求遇到的乱码问题,而POST请求需要使用Spring MVC的内部编码过滤器CharacterEncodingFilter
来设置编码方式
❓ 为什么使用过滤器设置编码:过滤器作为服务器三大组件之一,它的初始化时间在servlet之前,所以可以在前端控制器
DispatcherServlet
初始化前(获取请求参数前)设置编码方式,而且浏览器每次请求都会先经过过滤器,再到对应的处理请求方法,所以能够实现对每次请求都设置编码方式❓ 为什么不使用servletAPI或监听器来设置编码
- 如果直接使用servletAPI来设置编码,即使用
req.setCharacterEncoding("utf-8")
来设置编码,则必须保证该设置在获取请求参数前。但在Spring MVC中DispatcherServlet
在初始化时已经从浏览器请求中获取到请求参数,即使在控制器方法形参中添加HttpServletRequest
对象,再通过setCharacterEncoding()
设置编码已经是无用功,以为它是设置在获取请求参数后的。所以不使用该方法设置编码- 监听器是服务器三大组件中初始化最早的一个,可以在servlet初始化前进行一些操作,但它仅在初始化的时候执行一次,后面销毁的时候再执行一次。所以在监听器初始化时设置编码的话,它只能在初始化时设置一次,当有多次请求时,设置就无效了
🔑配置方式:在web.xml中配置CharacterEncodingFilter
过滤器,并设置相关属性
<!-- 配置Spring MVC内部编码过滤器,并设置相关属性 -->
<filter>
<filter-name>characterEncodingFilter</filter-name>
<filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
<!-- 设置编码方式为utf-8 -->
<init-param>
<param-name>encoding</param-name>
<param-value>UTF-8</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>
❓ 关于
CharacterEncodingFilter
过滤器的配置
- 必须要设置
encoding
和forceResponseEncoding
两个属性,才能成功对请求和响应时设置编码方式。如果只对请求时设置编码,则可以只设置encoding
一个属性,但如果要对响应时设置编码,就必须两个属性都设置。具体为什么可以从源码中找到原因(下面👇用🚩标记出原因)- 如果web.xml还有其他过滤器的配置,则必须把
CharacterEncodingFilter
过滤器的配置放到所有过滤器最前面,因为过滤器的初始化顺序就是web.xml中的配置顺序,而设置编码的操作必须最先设置才有效。但不必放到servlet配置的前面,因为过滤器初始化本身就比servlet早,在web.xml中的配置顺序不会影响过滤器与servlet的初始化顺序
🔑分析源码
-
打开
CharacterEncodingFilter
源码,可以看到过滤器有三个成员变量——encoding
、forceRequestEncoding
、forceResponseEncoding
① encoding:表示编码方式,没有初始化值,可以为null
② forceRequestEncoding:表示是否强制设置请求时的编码方式,初始值为false
③ forceResponseEncoding:表示是否强制设置响应时的编码方式,初始值为false
-
一般过滤器都有三个主要的方法——初始化方法
init()
、销毁方法destroy()
以及执行过滤操作的方法doFilter()
,主要看doFilter()
。而CharacterEncodingFilter
源码中的doFilterInternal()
就是执行过滤器操作的方法。因为执行过滤操作中必须有放行操作,放行操作又需要过滤器链对象filterChain
来执行,而doFilterInternal()
方法的形参中正好有filterChain
对象
-
在
doFilterInternal()
方法中,🚩可以看到给请求和响应设置编码的大前提是encoding
变量不为null,而它初始值为null,所以我们必须在web.xml中给它设置对应的编码方式,才能实现设置编码的操作。 -
设置请求编码方式还有一个小前提,即
this.isForceRequestEncoding() || request.getCharacterEncoding() == null
为true,该条件是与条件,只需满足其中一个就行,而我们之前没有设置过编码方式,所以request.getCharacterEncoding()
获取的值必定为null,即条件request.getCharacterEncoding() == null
必定为true,故即使变量forceRequestEncoding
(对应this.isForceRequestEncoding()
)初值为false,也无需修改。🚩但响应编码方式的小前提只有this.isForceResponseEncoding()
这一个条件,所以必须将forceResponseEncoding
改成true才能设置响应时的编码方式