0、目录
1、ResponseBody返回Json数据
在实际开发中返回JSON数据是最常见的一种请求交互方式,SpringMVC提供了@ResponseBody注解让我们从服务端响应给客户端需要的JSON数据。
该方式不涉及页面跳转,常用于Ajax异步刷新获取数据,目前主要用于企业中前后端分离的数据响应。
1.1 添加jackson依赖
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.9.3</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
<version>2.9.3</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
<version>2.9.3</version>
</dependency>
1.2 给方法添加@ResponseBody注解
package com.hliedu.springmvc.controller;
import com.hliedu.springmvc.domain.User;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.ui.ModelMap;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import java.util.*;
@Controller
@RequestMapping("/json")
public class JsonController {
/**
* 返回字符串,一定要声明produces部分,格式和编码一个不能少
* @return
*/
@RequestMapping(value = "retStrJson", produces = "application/json;charset=utf-8")
@ResponseBody
public String retStrJson(){
return "{\"uname\":\"张三\"}";
}
/**
* 返回Map对象
* @return
*/
@RequestMapping(value = "retMapJson")
@ResponseBody
public Map<String,Object> retMapJson(){
Map<String,Object> map = new HashMap<String,Object>();
map.put("uname" , "张三");
map.put("height" , 180);
return map;
}
/**
* 返回POJO对象
* @return
*/
@RequestMapping(value = "retPojoJson")
@ResponseBody
public User retPojoJson(){
User user = new User("张大" , 20);
return user;
}
/**
* 返回集合
* @return
*/
@RequestMapping(value = "retListJson")
@ResponseBody
public List<User> retListJson(){
List<User> users = new ArrayList<>();
User user1 = new User("张三丰" , 20);
User user2 = new User("张无忌" , 20);
users.add(user1);
users.add(user2);
return users;
}
}
1.3 springmvc.xml中加入配置
<mvc:annotation-driven/>
当一个处理请求的方法标记为@ResponseBody时,就说明该方法需要输出其他视图(json、xml),SpringMVC通过已定义的转化器做转化输出,默认输出json。其实是注解驱动帮我们做了这件事情。
2、JQuery表单序列化
表单序列化是借助JQuery函数库,将整个form表单直接序列化成对象,传入到Java后台。
JQuery的表单序列化有两种方法:
1、将表单中的属性序列化成字符串进行传输
var data = $('#form1').serialize();
console.log(data);
//输出结果为uname=zs&age=18....
2、将表单中的属性序列化为数组对象进行传输
var data = $('#form1').serializeArray();
//输出的结果为[Object,Object,Object]对象
注意:
案例实践
1、添加JSP
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>form serial</title>
</head>
<body>
<!--表单序列化传递参数 -->
<form id="formSerialForm">
姓名:
<input type="text" name="uname" value="">
<br>
年龄:
<input type="text" name="age" value="">
<button type="button" id="serialBtn">提交</button>
</form>
</body>
</html>
2、加入JQuery资源文件及JS函数
<script src="../static/js/jquery-1.9.1.js"></script>
<script>
$(function(){
$("#serialBtn").click(function(){
var data1 = $("#formSerialForm").serialize();
console.log(data1);
var data2 = $("#formSerialForm").serializeArray();
console.log(data2);
$.ajax({
url:"/springmvc05/form/serial",
type:"POST",
data : data1,
success:function(result){
console.log("success");
}
});
});
});
</script>
3、编写Controller的handler方法
package com.hliedu.springmvc.controller;
import com.hliedu.springmvc.domain.User;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@Controller
@RequestMapping("/form")
public class FormSerializeController {
//单值接收
@RequestMapping(value = "serial")
public String serial(String uname,Integer age){
System.out.println(uname);
System.out.println(age);
return "success";
}
//对象接收
@RequestMapping(value = "serialPojo")
public String serialArray(User user){
System.out.println(user);
return "success";
}
}
4、注意事项
- 无论是序列化成字符串还是数组对象,后台都能够使用相同的方式进行接收
- 两种方法进行表单序列化时,只对form内部含有name属性的标签进行序列化
- input的type为file、image、button、submit、reset都不会被序列化
- 表单对应属性name要和POJO类对应属性名相同
3、RequestBody接收JSON参数
在使用ajax进行请求,并传递参数时,偶尔需要把数组作为传递参数,这是就要使用@RequestBody来解决这个问题
3.1 ajax前端请求代码
- 传入参数为字符串或使用JSON.stringify(arr)需要把json对象数组转变成一个json对象字符串
- ajax的参数中设置"contentType": “application/json”
- 代码
<script>
$(function(){
$("#serialBtn").on("click",function(){
var arr = new Array();
arr.push({"uname":"zsf","age":18});
arr.push({"uname":"zwj","age":19});
arr.push({"uname":"zcs","age":20});
arr.push({"uname":"yss","age":21});
var obj = {"uname":"hliedu","age":30};
$.ajax({
"url": "/springmvc05/form/ajax_array",
"type": "post",
"data": JSON.stringify(arr),
"contentType": "application/json",
"success": function(result){
//成功
console.log(result);
},
"error": function(e){
//失败
console.log(e);
}
});
});
});
</script>
3.2 Java后端处理
- 使用@RequestBody 来接收请求参数
/**
* 需要给当前被@RequestBody修饰的POJO,添加无参构造
* @return
*/
@RequestMapping("ajax_obj")
@ResponseBody
public User ajaxObj(@RequestBody User user) {
System.out.println(user);
return user;
}
/**
* 接收数组或集合
* User对象需要提供无参的构造方法
* @param users
* @return
*/
@RequestMapping("ajax_array")
@ResponseBody
public User[] ajaxArray(@RequestBody User[] users){
System.out.println(Arrays.asList(users));
for (User user : users) {
System.out.println(user.getUname());
}
return users;
}
4、HttpMessageConverter的原理
HttpMessageConverter是Spring3.0 新添加的一个接口,负责将请求信息转换为一个对象,将对象输出为响应信息。HttpMessageConverter主要针对那些不会返回view视图的response(@ResponseBody或者返回值为HttpEntity的方法)
4.1 接口定义的方法有
public interface HttpMessageConverter<T> {
//判断当前转换器可否将请求信息转换为var1类型的对象,同时指定媒体类型
boolean canRead(Class<?> var1, @Nullable MediaType var2);
//判断当前转换器能否将var1类型的对象写到响应流中
boolean canWrite(Class<?> var1, @Nullable MediaType var2);
//获取该转换器支持的媒体类型
List<MediaType> getSupportedMediaTypes();
//在已通过canRead方法后,对参数值进行读,转换为需要的类型,
T read(Class<? extends T> var1, HttpInputMessage var2) ;
//在已通过canWrite方法后,将返回值发送给请求者,同时指定相应的媒体类型
void write(T var1, @Nullable MediaType var2, HttpOutputMessage var3) ;
}
4.2 SpringMVC针对读写提供了2个不同的接口
- HttpInputMessage
- HttpOutputMessage
public interface HttpInputMessage extends HttpMessage {
//将body中的数据转为输入流
InputStream getBody() throws IOException;
}
public interface HttpOutputMessage extends HttpMessage {
//将body中的数据转为输出流
OutputStream getBody() throws IOException;
}
public interface HttpMessage {
//父接口HttpMessage提供的方法是读取头部中的信息
HttpHeaders getHeaders();
}
4.3 继承结构关系图
4.4 流程
-
在SpringMVC进入readString方法前,会根据@RequestBody注解选择适当的HttpMessageConverter实现类来将请求参数解析到string变量中,具体来说是使用了StringHttpMessageConverter类,它的canRead()方法返回true,然后它的read()方法会从请求中读出请求参数,绑定到readString()方法的string变量中。
-
当SpringMVC执行readString方法后,由于返回值标识了@ResponseBody,SpringMVC将使用StringHttpMessageConverter的write()方法,将结果作为String值写入响应报文,当然,此时canWrite()方法返回true。
4.5 StringHttpMessageConverter分析
/**
* HttpMessageConverter 的实现类:完成请求报文到字符串和字符串到响应报文的转换
* 默认情况下,此转换器支持所有媒体类型(*/*),并使用 Content-Type 为 text/plain 的内容类型进行写入
* 这可以通过 setSupportedMediaTypes(父类 AbstractHttpMessageConverter 中的方法) 方法设置 supportedMediaTypes 属性来覆盖
*/
public class StringHttpMessageConverter extends AbstractHttpMessageConverter<String> {
// 默认字符集(产生乱码的根源)
public static final Charset DEFAULT_CHARSET;
//可使用的字符集
@Nullable
private volatile List<Charset> availableCharsets;
//标识是否输出 Response Headers:Accept-Charset(默认输出)
private boolean writeAcceptCharset;
/**
* 使用 "ISO-8859-1" 作为默认字符集的默认构造函数
*/
public StringHttpMessageConverter() {
this(DEFAULT_CHARSET);
}
/**
* 如果请求的内容类型 Content-Type 没有指定一个字符集,则使用构造函数提供的默认字符集
*/
public StringHttpMessageConverter(Charset defaultCharset) {
super(defaultCharset, MediaType.TEXT_PLAIN, MediaType.ALL);
this.writeAcceptCharset = true;
}
/**
* 标识是否输出 Response Headers:Accept-Charset
* 默认是 true
*/
public void setWriteAcceptCharset(boolean writeAcceptCharset) {
this.writeAcceptCharset = writeAcceptCharset;
}
@Override
public boolean supports(Class<?> clazz) {
return String.class == clazz;
}
/**
* 将请求报文转换为字符串
*/
@Override
protected String readInternal(Class<? extends String> clazz, HttpInputMessage inputMessage) throws IOException {
//通过读取请求报文里的 Content-Type 来获取字符集
Charset charset = getContentTypeCharset(inputMessage.getHeaders().getContentType());
//调用 StreamUtils 工具类的 copyToString 方法来完成转换
return StreamUtils.copyToString(inputMessage.getBody(), charset);
}
/**
* 返回字符串的大小(转换为字节数组后的大小)
* 依赖于 MediaType 提供的字符集
*/
@Override
protected Long getContentLength(String str, MediaType contentType) {
Charset charset = getContentTypeCharset(contentType);
try {
return (long) str.getBytes(charset.name()).length;
}
catch (UnsupportedEncodingException ex) {
// should not occur
throw new IllegalStateException(ex);
}
}
/**
* 将字符串转换为响应报文
*/
@Override
protected void writeInternal(String str, HttpOutputMessage outputMessage) throws IOException {
//输出 Response Headers:Accept-Charset(默认输出)
if (this.writeAcceptCharset) {
outputMessage.getHeaders().setAcceptCharset(getAcceptedCharsets());
}
Charset charset = getContentTypeCharset(outputMessage.getHeaders().getContentType());
//调用 StreamUtils 工具类的 copy 方法来完成转换
StreamUtils.copy(str, charset, outputMessage.getBody());
}
/**
* 返回所支持的字符集
* 默认返回 Charset.availableCharsets()
* 子类可以覆盖该方法
*/
protected List<Charset> getAcceptedCharsets() {
List<Charset> charsets = this.availableCharsets;
if (charsets == null) {
charsets = new ArrayList(Charset.availableCharsets().values());
this.availableCharsets = (List)charsets;
}
return (List)charsets;
}
/**
* 获得 ContentType 对应的字符集
*/
private Charset getContentTypeCharset(@Nullable MediaType contentType) {
if (contentType != null && contentType.getCharset() != null) {
return contentType.getCharset();
} else if (contentType != null && contentType.isCompatibleWith(MediaType.APPLICATION_JSON)) {
return StandardCharsets.UTF_8;
} else {
Charset charset = this.getDefaultCharset();
return charset;
}
}
static {
DEFAULT_CHARSET = StandardCharsets.ISO_8859_1;
}
}
5、 数字格式化
@NumberFormat可对类似数字类型的属性进行注解,该注解要求数据绑定是必须按照我们所声明的格式进行赋值,否则数据绑定失败,它拥有2个互斥的属性。
5.1 style
- 类型为枚举,格式化类型
类型为NumberFormat.Style,该Style是一个已经定义的枚举类,包括
NUMBER:正常数字类型
CURRENCY:货币类型
PERCENT:百分比类型
5.2 pattern
- 类型为String
自定义样式,例如:pattern="#,###",#号为任意数字
5.3 使用案例
- pattern:要求数字符合带小数的格式
@NumberFormat(pattern = "#,###.##")
private BigDecimal price;
- style:内置的枚举类型
@NumberFormat(style= NumberFormat.Style.CURRENCY)
private BigDecimal price;
5.4 注意
必须加入mvc:annotation标签才能生效
使用该注解默认无需指明ConversionService,若存在其他的ConversionService,则必须使用FromattingConversionService
6、日期格式化
@DateTimeFormat是用来验证输入的日期格式,用于对日期属性进行注解。该注解可对以下三种类型进行标注
- java.util.Date:日期
- java.util.Calendar:日历
- java.long.Long:时间戳
属性介绍
(1)pattern:类型为字符串,指定解析格式
格式如:“yyyy-MM-dd hh:mm:ss”
(2)iso:该注解中默认的枚举
类型为:DateTimeFormat.ISO,也是指定解析格式,默认值有4种,可查看源码观看
(3)style属性:字符串类型
通过样式指定日期时间格式,由2位字符组成,第一位表示日期,第二位表示时间
- S:短日期/时间格式
- M:中日期/时间格式
- F:完整日期/时间格式
- -:忽略日期或时间格式
7、 数据有效性验证
7.1 JSR303规范
JSR-303 是JAVA EE 6 中的一项子规范,叫做Bean Validation
1、Bean Validation 中内置的 constraint
| Constraint | 详细信息 | | :---------------------------- | :------------------------------------------------------- | | @Null | 被注释的元素必须为 null | | @NotNull | 被注释的元素必须不为 null | | @AssertTrue | 被注释的元素必须为 true | | @AssertFalse | 被注释的元素必须为 false | | @Min(value) | 被注释的元素必须是一个数字,其值必须大于等于指定的最小值 | | @Max(value) | 被注释的元素必须是一个数字,其值必须小于等于指定的最大值 | | @DecimalMin(value) | 被注释的元素必须是一个数字,其值必须大于等于指定的最小值 | | @DecimalMax(value) | 被注释的元素必须是一个数字,其值必须小于等于指定的最大值 | | @Size(max, min) | 被注释的元素的大小必须在指定的范围内 | | @Digits (integer, fraction) | 被注释的元素必须是一个数字,其值必须在可接受的范围内 | | @Past | 被注释的元素必须是一个过去的日期 | | @Future | 被注释的元素必须是一个将来的日期 | | @Pattern(value) | 被注释的元素必须符合指定的正则表达式 |
2、Spring使用JSR303
- 导入JSR303的jar包来完成
<dependency>
<groupId>javax.validation</groupId>
<artifactId>validation-api</artifactId>
<version>2.0.1.Final</version>
</dependency>
- 在POJO类中,给相应的字段属性加入JSR303验证注解
@Past
private Date birthday;
- 给处理请求的方法参数列表加入@Valid注解
@RequestMapping(value = "format")
@ResponseBody
public String retStrJson(@Valid Person person){
}
3、规范添加完成,但进行测试无效
- 需要借助实现来完成,如下Hibernate-Validator验证框架
- 我们继续学习Hibernate-Validator后才能进行验证实现
7.2 Hibernate-Validator验证框架
Hibernate Validator 是 Bean Validation 的参考实现 . Hibernate Validator 提供了 JSR 303 规范中所有内置 constraint 的实现,除此之外还有一些附加的 constraint。
1、Hibernate Validator 附加的 constraint
| Constraint | 详细信息 | | :------------- | :------------------------------------- | | @Email | 被注释的元素必须是电子邮箱地址 | | @Length | 被注释的字符串的大小必须在指定的范围内 | | @NotEmpty | 被注释的字符串的必须非空 | | @Range | 被注释的元素必须在合适的范围内 |
2、Spring使用
- jar包导入
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-validator</artifactId>
<version>6.0.13.Final</version>
</dependency>
- 属性修饰
3、案例
8、错误消息获取及回显
8.1 错误消息获取
(1)书写格式
获取错误消息的时候,在方法入参的位置, 可以加入BindingResult或者Errors作为其中的一个参数,这个入参必须与待验证的方法发入参相邻(BindingResult是Errors的子接口,所以一般采用BindingResult)
@RequestMapping("/testFormat")
public String testFormat(@Valid User user, BindingResult result) {
.....
}
如果在入参的位置还有其他参数的时候,就要注意其中的写法了:如下会报400错误:
public String testFormat(@Valid User user,Map<String, Object> map, BindingResult result) {
......
}
BindingResult应紧随@Valid所修饰的参数之后,正确写法如下
public String testFormat(@Valid User user, BindingResult result,Map<String, Object> map) {
......
}
(2)BindingResult常用方法
//通过指定的属性名称获取该属性的错误
@Nullable
FieldError getFieldError(String field);
//获取所有属性的错误集合
List<FieldError> getFieldErrors();
//获取指定的属性名获取属性值
@Nullable
Object getFieldValue(String field);
//获取错误数量
int getErrorCount();
//获取所有错误消息,包含属性错误
List<ObjectError> getAllErrors();
(3)使用案例
@RequestMapping(value = "format")
@ResponseBody
public String retStrJson(@Valid Person person,BindingResult result){
if(result.getErrorCount() > 0){
//获取所有的错误,包含属性错误之外的其他错误
List<FieldError> fieldErrors = result.getFieldErrors();
for (FieldError fieldError : fieldErrors) {
System.out.println(fieldError.getDefaultMessage());
}
}
System.out.println(person);
return "success";
}
8.2 消息回显
错误消息获取到之后,对于@ResponseBody方法,我们可以存放到一个Map中,返回给前端,对于表单提交的属性,我们可以通过form表单标签进行回显(不常用)