Spring MVC 数据绑定
数据绑定:在后端的业务方法中直接获取客户端 HTTP 请求中的参数,由于 HTTP 请求传输的参数都是 String 类型的,Handler 业务方法中的参数都是我们指定的数据类型,比如:Integer、Double、Long等,所以需要处理参数的转换,将请求参数映射到业务方法的形参中,Spring MVC 中数据绑定的工作是由 HandlerAdapter 组件来完成的。
1. 基本数据类型
以 int 为例,后台需要 int 类型的参数,直接在业务方法定义处添加 int 类型的形参即可,Http 请求参数名必须与形参名一致。
@Controller
@RequestMapping("/hello")
public class DataBindHandler {
@RequestMapping(value = "/baseType")
@ResponseBody
public String baseType(int id){
return "id:"+id;
}
}
@ResponseBody
表示 Spring MVC 会直接将业务方法的返回值响应给客户端。如果不加 @ResponseBody
注解,Spring MVC 会将业务方法的返回值传递给 DispatcherServlet,再由 DispatcherServlet 调用 ViewResolver 对返回值进行解析,映射到一个 JSP 资源。
异常类型1:如果 http 请求不带参数,则直接报 500 错误。错误原因:可选参数 “id” 不能转为 null,因为我们都知道,基本数据类型不能赋值 null。
异常类型2:如果 http 请求的参数类型为字符串,则会报 400 错误,错误原因:String 类型不能转换成 int 类型。
2. 包装类
@Controller
public class DataBindHandler {
@RequestMapping("/packageType")
@ResponseBody
public String packageType(Integer id){
return "id:"+id;
}
}
说明:包装类可以接收 null 值,当 HTTP 请求没有参数时,使用包装类定义形参的数据类型,程序不会抛出异常。
如果传参正确,则浏览器展示的结果也是符合预期的:
如果 HTTP 请求的参数是字符串类型,则仍然会抛出 400 的错误,错误的原因和上面相同,即:String 类型不能转化为 Integer 类型。
参数列表添加 @RequestParam 注解,可以对参数进行相关设置:
@RequestParam 参数说明:
value="id"
:将 http 请求中名为 id 的参数与形参进行映射;required=false
:设置 id 是否为必填项,true 表示必填,false 表示非必填,可省略;defaultValue="1"
:若 http 请求中没有 id 参数,默认值为1;
@Controller
public class DataBindHandler {
@RequestMapping("/packageType")
@ResponseBody
public String packageType(@RequestParam(value = "id",required = true) Integer id){
return "id:"+id;
}
}
因为 id 为必填参数,此时前端没有传入 id 参数,同时业务方法中的 id 也没有设置默认值,所以会报错。如果前端传入 id 的合法值或者 id 有 defaultValue,则程序不会报错。
3. 数组
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>复选框演示</title>
</head>
<body>
<form action="/list" method="post">
爱好:<input type="checkbox" name="hobbies" value="跑步">跑步
<input type="checkbox" name="hobbies" value="阅读">阅读
<input type="checkbox" name="hobbies" value="购物">购物<br/>
<input type="submit" value="提交">
</form>
</body>
</html>
@RestController
public class DataBindHandler {
@RequestMapping("/list")
public String hobby(String[] hobbies){
String str = Arrays.toString(hobbies);
System.out.println(str);
return str;
}
}
@RestController
表示该控制器会直接将业务方法的返回值响应到客户端,不进行视图解析;
@Controller
表示该控制器的每一个业务方法的返回值都会交给视图解析器进行解析,如果只需要将数据响应给客户端,而不需要进行视图解析,则需要在对应的业务方法定义处添加 @ResponseBody
。
所以上述的业务代码与如下代码等同:
@Controller
public class DataBindHandler {
@RequestMapping("/list")
@ResponseBody
public String hobby(String[] hobbies){
String str = Arrays.toString(hobbies);
System.out.println(str);
return str;
}
}
4. POJO
第一步:创建User 类
public class User {
private String name;
private int age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
第二步:JSP 页面 input 标签的 name 值与实体类的属性名对应
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>复选框演示</title>
</head>
<body>
<form action="/userInfo" method="post">
姓名:<input type="text" name="name"><br>
年龄:<input type="text" name="age"><br>
<input type="submit" value="提交">
</form>
</body>
</html>
第三步:业务方法
package com.trainingl.handler;
import com.trainingl.entity.User;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;
import java.util.Arrays;
//@Controller
@RestController
public class DataBindHandler {
@RequestMapping("/userInfo")
public String UserInfo(User user){
return "注册用户信息:"+user;
}
}
第四步:运行程序
之前介绍过在 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>
</filter>
<filter-mapping>
<filter-name>encodingFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
那么如何处理 @ResponseBody 的中文乱码,只需要在 springmvc.xml 中配置消息转换器即可。
<mvc:annotation-driven>
<!-- 消息转换器 -->
<mvc:message-converters register-defaults="true">
<bean class="org.springframework.http.converter.StringHttpMessageConverter">
<property name="supportedMediaTypes" value="text/html;charset=UTF-8"></property>
</bean>
</mvc:message-converters>
</mvc:annotation-driven>
5. POJO级联操作
第一步:创建 Address 类;
package com.trainingl.entity;
public class Address {
private Integer id;
private String name;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "Address{" +
"id=" + id +
", name='" + name + '\'' +
'}';
}
}
第二步:修改 User 类,添加 address 属性;
package com.trainingl.entity;
public class User {
private String name;
private int age;
private Address address;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public Address getAddress() {
return address;
}
public void setAddress(Address address) {
this.address = address;
}
@Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
", age=" + age +
", address=" + address +
'}';
}
}
第三步:修改 JSP,添加 address 信息,为 input 标签的 anme 设置属性级联,即先关联 User 的 address 属性,再级联 address 的 id 和 name;
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>复选框演示</title>
</head>
<body>
<form action="/userInfo" method="post">
姓名:<input type="text" name="name"><br>
年龄:<input type="text" name="age"><br>
邮政编码:<input type="text" name="address.id"><br>
地址:<<input type="text" name="address.name"><br>
<input type="submit" value="提交">
</form>
</body>
</html>
第四步:编写控制器的业务方法
@RestController
public class HelloHandler {
@RequestMapping("/userInfo")
public String UserInfo(User user){
return "注册用户信息:"+user;
}
}
第五步:运行程序
6. JSON
客户端发送 JSON 格式的数据,直接通过 Spring MVC 绑定到业务方法的形参中,处理 Spring MVC 无法加载静态资源,所以需要先在 web.xml 中添加配置。
<servlet-mapping>
<servlet-name>default</servlet-name>
<url-pattern>*.js</url-pattern>
</servlet-mapping>
JSP:Ajax 请求后台业务方法,并将 JSON 格式的参数传给后台
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>JSON数据传送</title>
<script type="text/javascript" src="js/jquery-2.0.0.min.js"></script>
<script type="text/javascript">
$(function () {
var btn = $("#btn");
var user = {
"id":1,
"name":"张三"
};
//触发事件
btn.click(function () {
$.ajax({
url:"/data/json",
data:JSON.stringify(user),
type:"POST",
contentType:"application/json;charset=UTF-8",
dataType:"JSON",
success:function (data) {
alert(data.id+":"+data.name);
}
})
})
})
</script>
</head>
<body>
<input type="button" id="btn" value="点击">
</body>
</html>
注意:json 数据必须用 JSON.stringify()
方法转成字符串。
业务方法
@Controller
public class HelloHandler {
@RequestMapping("/data/json")
@ResponseBody
public User jsonType(@RequestBody User user){
System.out.println(user);
user.setId(6);
user.setName("王五");
//返回前端
return user;
}
}
@RequestBody 注解
:读取 http 请求参数,通过 Spring MVC 提供的 HttpMessageConverter 接口将读取的参数转为 json、xml 格式的数据,绑定到业务方法的形参。
@ResponseBody 注解
:将业务方法返回的对象,通过 HttpMessageConverter 接口转为指定格式的数据,比如 json、xml 等,响应给客户端。
如何使用 fastjson
Spring MVC 中的 JSON 和 JavaBean 的转换需要借助于 fastjson,pom.xml 引入 fastjson 依赖的 jar 包:
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.58</version>
</dependency>
springmvc.xml 添加 fastjson 配置:
<mvc:annotation-driven>
<!-- 消息转换器 -->
<mvc:message-converters register-defaults="true">
<bean class="org.springframework.http.converter.StringHttpMessageConverter">
<property name="supportedMediaTypes" value="text/html;charset=UTF-8"></property>
</bean>
<!-- 配置fastjson -->
<bean class="com.alibaba.fastjson.support.spring.FastJsonHttpMessageConverter"></bean>
</mvc:message-converters>
</mvc:annotation-driven>
前端传给后台的数据 id=1, name="张三"
,后台对 id 和 name 进行修改,并将修改的结果返回给前端,我们在前端页面可以看到 id=6, name="王五"
,修改成功!
7. List
Spring MVC 不支持 List 类型的直接转换,需要包装成 Object
List 的自定义包装类:
public class UserList {
private List<User> users;
public List<User> getUsers() {
return users;
}
public void setUsers(List<User> users) {
this.users = users;
}
}
创建 addList.jsp,同时添加三个用户信息,input 的 name 指向自定义包装类 UserList 中的 users 属性,级联到 name 和 age 中,同时以下标区分集合中的不同对象。
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Title</title>
</head>
<body>
<form action="/listType" method="post">
用户1姓名:<input type="text" name="users[0].name"><br/>
用户1年龄:<input type="text" name="users[0].age"><br/>
用户2姓名:<input type="text" name="users[1].name"><br/>
用户2年龄:<input type="text" name="users[1].age"><br/>
用户3姓名;<input type="text" name="users[2].name"><br/>
用户3年龄:<input type="text" name="users[2].age"><br/>
<input type="submit" value="提交">
</form>
</body>
</html>
业务方法:
package com.trainingl.handler;
import com.trainingl.entity.User;
import com.trainingl.entity.UserList;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;
import java.util.Arrays;
@Controller
public class HelloHandler {
@RequestMapping("/listType")
@ResponseBody
public String listType(UserList userList){
StringBuffer bf = new StringBuffer();
for (User user:userList.getUsers()) {
bf.append(user);
}
return "用户:"+bf.toString();
}
}
执行代码,结果如下:
另外,集合框架的 Set 和 Map 处理方式与之类似,同样需要先包装成 Object,但实际开发中这两种方式相对并不常用。