212、SpringMVC学习笔记(六)【JSON及数据验证】2019.09.18

17 篇文章 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 的实现类:完成请求报文到字符串和字符串到响应报文的转换
 * 默认情况下,此转换器支持所有媒体类型(*&#47;*),并使用 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表单标签进行回显(不常用)

9、参考链接

【01】JSON及数据验证

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值