Spring Boot 2 中的参数校验 spring-boot-starter-validation/Hibernate Validator

Spring Boot 2 中的参数校验 spring-boot-starter-validation/Hibernate Validator

Validation in Spring Boot

在springboot中常用的用于参数校验的注解如下:

@AssertFalse 所注解的元素必须是Boolean类型,且值为false
@AssertTrue 所注解的元素必须是Boolean类型,且值为true
@DecimalMax 所注解的元素必须是数字,且值小于等于给定的值
@DecimalMin 所注解的元素必须是数字,且值大于等于给定的值
@Digits 所注解的元素必须是数字,且值必须是指定的位数
@Future 所注解的元素必须是将来某个日期
@Max 所注解的元素必须是数字,且值小于等于给定的值
@Min 所注解的元素必须是数字,且值小于等于给定的值
@Range 所注解的元素需在指定范围区间内
@NotNull 所注解的元素值不能为null
@NotBlank 所注解的元素值有内容
@Null 所注解的元素值为null
@Past 所注解的元素必须是某个过去的日期
@PastOrPresent 所注解的元素必须是过去某个或现在日期
@Pattern 所注解的元素必须满足给定的正则表达式
@Size 所注解的元素必须是String、集合或数组,且长度大小需保证在给定范围之内
@Email 所注解的元素需满足Email格式

一、添加依赖

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-validation</artifactId>
</dependency>

这个starter依赖的是Hibernate Validator。

二、实体类参数校验

(一)实体类上加上注解

import lombok.Data;

import javax.validation.constraints.*;
import java.io.Serializable;

/**
 * @author chushiyan
 * @email  Y2h1c2hpeWFuMDQxNUAxNjMuY29t(base64)
 * @description
 */
@Data
public class User implements Serializable {

    private String id;

    @NotNull(message = "姓名不能为空")
    @Size(min = 1, max = 20, message = "姓名长度必须在1-20之间")
    private String name;

    @Min(value = 10, message = "年龄必须大于10")
    @Max(value = 150, message = "年龄必须小于150")
    private Integer age;

    @Email(message = "邮箱格式不正确")
    private String email;
}

(二)Controller中加上注解

在controller中使用@Valid 或者@Validated 注解校验

import com.chushiyan.validation_tutorial.entity.Result;
import com.chushiyan.validation_tutorial.pojo.User;
import org.springframework.web.bind.annotation.*;

import javax.validation.Valid;

/**
 * @author chushiyan
 * @email  Y2h1c2hpeWFuMDQxNUAxNjMuY29t(base64)
 * @description
 */
@RestController
@RequestMapping("/user")
public class UserController  {

    @PostMapping
    public Result test(@Valid  @RequestBody User user){
        System.out.println(user);
        return new Result(true,200,"");
    }
}

(三)测试

使用postman发送POST请求:http://localhost:10000/user

{
    "age":120,
    "email":"chushiyan"
}

控制台打印:

WARN 12476 --- [io-10000-exec-1] .w.s.m.s.DefaultHandlerExceptionResolver : Resolved [org.springframework.web.bind.MethodArgumentNotValidException: Validation failed for argument [0] in public com.chushiyan.validation_tutorial.entity.Result com.chushiyan.validation_tutorial.controller.UserController.test(com.chushiyan.validation_tutorial.pojo.User) with 2 errors: [Field error in object 'user' on field 'name': rejected value [null]; codes [NotNull.user.name,NotNull.name,NotNull.java.lang.String,NotNull]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [user.name,name]; arguments []; default message [name]]; default message [姓名不能为空]] [Field error in object 'user' on field 'email': rejected value [chushiyan]; codes [Email.user.email,Email.email,Email.java.lang.String,Email]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [user.email,email]; arguments []; default message [email],[Ljavax.validation.constraints.Pattern$Flag;@20195c7a,.*]; default message [邮箱格式不正确]] ]

响应的数据:

{
    "timestamp": "2019-12-03T09:14:00.759+0000",
    "status": 400,
    "error": "Bad Request",
    "errors": [
        {
            "codes": [
                "NotNull.user.name",
                "NotNull.name",
                "NotNull.java.lang.String",
                "NotNull"
            ],
            "arguments": [
                {
                    "codes": [
                        "user.name",
                        "name"
                    ],
                    "arguments": null,
                    "defaultMessage": "name",
                    "code": "name"
                }
            ],
            "defaultMessage": "姓名不能为空",
            "objectName": "user",
            "field": "name",
            "rejectedValue": null,
            "bindingFailure": false,
            "code": "NotNull"
        },
        {
            "codes": [
                "Email.user.email",
                "Email.email",
                "Email.java.lang.String",
                "Email"
            ],
            "arguments": [
                {
                    "codes": [
                        "user.email",
                        "email"
                    ],
                    "arguments": null,
                    "defaultMessage": "email",
                    "code": "email"
                },
                [],
                {
                    "arguments": null,
                    "defaultMessage": ".*",
                    "codes": [
                        ".*"
                    ]
                }
            ],
            "defaultMessage": "邮箱格式不正确",
            "objectName": "user",
            "field": "email",
            "rejectedValue": "chushiyan",
            "bindingFailure": false,
            "code": "Email"
        }
    ],
    "message": "Validation failed for object='user'. Error count: 2",
    "trace": "org.springframework.web.bind.MethodArgumentNotValidException: Validation failed for argument [0] in public com.chushiyan.validation_tutorial.entity.Result com.chushiyan.validation_tutorial.controller.UserController.test(com.chushiyan.validation_tutorial.pojo.User) with 2 errors: [Field error in object 'user' on field 'name': rejected value [null]; codes [NotNull.user.name,NotNull.name,NotNull.java.lang.String,NotNull]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [user.name,name]; arguments []; default message [name]]; default message [姓名不能为空]] [Field error in object 'user' on field 'email': rejected value [chushiyan]; codes [Email.user.email,Email.email,Email.java.lang.String,Email]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [user.email,email]; arguments []; default message [email],[Ljavax.validation.constraints.Pattern$Flag;@20195c7a,.*]; default message [邮箱格式不正确]] \r\n\tat  (博主进行了省略......)",
    "path": "/user"
}

(四)全局处理异常

上面响应的错误肯定是不够友好的,所以需要进行异常处理。这里定义一个全局处理函数

import com.chushiyan.validation_tutorial.entity.Result;
import org.springframework.http.HttpStatus;
import org.springframework.validation.BindException;
import org.springframework.validation.ObjectError;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestControllerAdvice;

import java.util.Date;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

/**
 * @author chushiyan
 * @email  Y2h1c2hpeWFuMDQxNUAxNjMuY29t(base64)
 * @description
 */
@RestControllerAdvice
public class GlobalExceptionHandler {
    /**
     * 处理所有校验失败的异常(MethodArgumentNotValidException异常)
     *
     * @param ex
     * @return
     */
    @ExceptionHandler(value = MethodArgumentNotValidException.class)
    // 设置响应状态码为400
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    public Result handleBindGetException(MethodArgumentNotValidException ex) {

        Map<String, Object> body = new LinkedHashMap<String, Object>();
        body.put("timestamp", new Date());

        // 获取所有异常
        List<String> errors = ex.getBindingResult()
                .getFieldErrors()
                .stream()
                .map(x -> x.getDefaultMessage())
                .collect(Collectors.toList());
        body.put("errors", errors);
        return new Result(false, 20001, "提交的数据校验失败", body);
    }
}

(五)再次测试

使用postman发送POST请求:http://localhost:10000/user

{
    "age":120,
    "email":"chushiyan"
}

响应的json数据:

{
    "flag": false,
    "code": 20001,
    "message": "提交的数据校验失败",
    "data": {
        "timestamp": "2019-12-03T09:35:02.815+0000",
        "errors": [
            "邮箱格式不正确",
            "姓名不能为空"
        ]
    }
}

三、单个参数校验

(一)直接在参数前加上校验注解:

package com.chushiyan.validation_tutorial.controller;

import com.chushiyan.validation_tutorial.entity.Result;
import com.chushiyan.validation_tutorial.pojo.User;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;

import javax.validation.Valid;
import javax.validation.constraints.NotNull;

/**
 * @author chushiyan
 * @email  Y2h1c2hpeWFuMDQxNUAxNjMuY29t(base64)
 * @description
 */
@RestController
@RequestMapping("/user")
@Validated
public class UserController  {

    @GetMapping
    public Result test2(@NotNull(message = "name不能为空")  String name){
        System.out.println(name);
        return new Result(true,200,"");
    }
}

注意:需要在类上添加@Validated注解,否则不会校验。

(二)全局处理函数

import com.chushiyan.validation_tutorial.entity.Result;
import org.springframework.http.HttpStatus;
import org.springframework.validation.BindException;
import org.springframework.validation.ObjectError;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestControllerAdvice;

import javax.validation.ConstraintViolation;
import javax.validation.ConstraintViolationException;
import javax.validation.ValidationException;
import java.util.*;
import java.util.stream.Collectors;

/**
 * @author chushiyan
 * @email  Y2h1c2hpeWFuMDQxNUAxNjMuY29t(base64)
 * @description
 */
@RestControllerAdvice
public class GlobalExceptionHandler {

    /**
     * 处理所有参数校验时抛出的异常
     *
     * @param ex
     * @return
     */
    @ExceptionHandler(value = ValidationException.class)
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    public Result handleBindException(ValidationException ex) {

        Map<String, Object> body = new LinkedHashMap<String, Object>();
        body.put("timestamp", new Date());

        // 获取所有异常
        List<String> errors = new LinkedList<String>();
        if(ex instanceof ConstraintViolationException){
            ConstraintViolationException exs = (ConstraintViolationException) ex;
            Set<ConstraintViolation<?>> violations = exs.getConstraintViolations();
            for (ConstraintViolation<?> item : violations) {
                errors.add(item.getMessage());
            }
        }
        body.put("errors", errors);
        return new Result(true, 20001, "提交的参数校验失败", body);
    }
}

(二)测试

postman 测试http://localhost:10000/user GET

{
    "flag": true,
    "code": 20001,
    "message": "提交的参数校验失败",
    "data": {
        "timestamp": "2019-12-03T09:58:45.212+0000",
        "errors": [
            "name不能为空"
        ]
    }
}

四、参数校验分组

在实际开发中经常会遇到这种情况:添加用户时,id是由后端生成的,不需要校验id是否为空,但是修改用户时就需要校验id是否为空。如果在接收参数的User实体类的id属性上添加NotNull,显然无法实现。这时候就可以定义分组,在需要校验id的时候校验,不需要的时候不校验。

(一)定义表示组别的接口类

package com.chushiyan.validation_tutorial.validate;
public interface GroupA {
}

(二)在实体类的注解中标记id使用上面定义的组

给id属性添加分组:

package com.chushiyan.validation_tutorial.pojo;

import com.chushiyan.validation_tutorial.validate.GroupA;
import lombok.Data;

import javax.validation.constraints.*;
import java.io.Serializable;

/**
 * @author chushiyan
 * @email  Y2h1c2hpeWFuMDQxNUAxNjMuY29t(base64)
 * @description
 */
@Data
public class User implements Serializable {

    @NotNull(groups = GroupA.class, message = "id不能为空")
    private String id;

    @NotNull(message = "姓名不能为空")
    @Size(min = 1, max = 20, message = "姓名长度必须在1-20之间")
    private String name;

    @Min(value = 10, message = "年龄必须大于10")
    @Max(value = 150, message = "年龄必须小于150")
    private Integer age;

    @Email(message = "邮箱格式不正确")
    private String email;
}

(三)在controller中使用@Validated指定使用哪个组

    @PostMapping
    public Result add(@Validated @RequestBody User user) {
        return new Result(true, 200, "增加用户成功");
    }

    @PutMapping("/update")
    // 指定GroupA,这样就会校验id属性是否为空
    // 注意:还得必须添加Default.class,否则不会执行其他的校验(如我们案例中的@Email)
    public Result update(@Validated({GroupA.class, Default.class}) @RequestBody User user) {
        return new Result(true, 200, "修改用户成功");
    }
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,287评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,346评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,277评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,132评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,147评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,106评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,019评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,862评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,301评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,521评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,682评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,405评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,996评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,651评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,803评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,674评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,563评论 2 352

推荐阅读更多精彩内容