springboot中使用Hibernate-Validation

1、说明

后端接口设计时候,需要对前端请求参数进行'先校验后处理业务'情况,如果在业务代码中通过类似if这里逐个校验,会使得代码变得繁琐,开发工作者都是爱偷懒的。java中,Bean ValidationJavaBean的验证定义了相关的元数据模型和API。基于Bean-Validation封装,提供了更加丰富的Hibernate-Validation的校验包。也有开发会把这类校验交给前端来处理,但是接口暴露外网会存在直接调用情况(黄牛)。毕竟:前端校验是为了提高用户的体验度,后端校验则是为了保证数据的安全性

优点
1.验证逻辑与业务逻辑之间进行了分离,降低了程序耦合度
2.统一且规范的验证方式,无需你再次编写重复的验证代码
3.你将更专注于你的业务,将这些繁琐的事情统统丢在一边

2、Bean Validation与Hibernate Validation

2.1 Bean Validation中内置的constraint

包位置路径:javax.validation.constraints

image.png

注解 说明
@AssertFalse 注释的元素必须为False
@AssertTrue 注释的元素必须为True
@Email 注释的元素必须邮箱
@NotBlank 注释的元素不能为空,!null && size>0
@NotEmpty 注释的元素不能为空,数组,集合等
@NotNull 注释的元素必须为空,但可以为""字符串
@DecimalMin 注释的元素数字,最小不得小于Min
@DecimalMax 注释的元素为数字,最大不超过Max值

其中NotNull、NotEmpty、NotBlank区别

  • @NotNull
    适用于基本数据类型(Integer,Long,Double等等),当 @NotNull 注解被使用在 String 类型的数据上,则表示该数据不能为 Null(但是可以为 Empty)
  • @NotBlank
    适用于 String 类型的数据上,加了@NotBlank 注解的参数不能为 Null 且 trim() 之后 size > 0
  • @NotEmpty
    适用于 String、Collection集合、Map、数组等等,加了@NotEmpty 注解的参数不能为 Null 或者 长度为 0
2.1 Hibernate Validation中添加的constraint
image.png
注解 说明
@Length 注释的元素字符串长度必须为制定返回内
@Range 注释的元素必须在指定范围内
@URL 注释的元素必须为链接

3、基于Hibernate Validation的实现

(1)pom包引用
查看spring-boot-start-web中已经集成了Hibernate Validation,所以可以不用额外引用包。同时spring-boot-start-validation也完成了Hibernate Validationstart封装(校验机制更加全面)。

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

(2)Bean对象中使用注解注释

     ...
    @ApiModelProperty(value = "收货人所在省",required = true)
    @NotNull(message = "省不能为空")
    private String recipientProvince;

    @ApiModelProperty(value = "收货人所在市")
    @NotNull(message = "市不能为空")
    private String recipientCity;

    @ApiModelProperty(value = "收货人所在区")
    @NotNull(message = "区不能为空")
    private String recipientDistrict;
    ...

(3)Controller层使用@Valid或者@Validated

 @PostMapping("/add")
 public UniformResultTemplate<Boolean> addAddress(@RequestBody @Validated AddressReqDto reqDto, HttpServletRequest request){
   return null;
 }

注意:Post请求方式区别,Get@Validated注解需要加在 所在方法类前

@RestController
@RequestMapping("/api/address")
@Validated
public class AddressController extends BaseController{

  @ApiOperation("收获地址详情")
    @GetMapping("/detail")
    public UniformResultTemplate<AddressDetailRespDto> queryAddressList(@NotNull(message = "地址Id不能为空") 
@RequestParam(value = "addressId") Long addressId, HttpServletRequest request){
        return null;
    }
}

(4)使用@ControllerAdvice统一异常处理返回。

@Component
@ControllerAdvice
@Slf4j
public class GlobalExceptionHandler {
    // Post请求Bean中的校验抛出:MethodArgumentNotValidException
    @ExceptionHandler(MethodArgumentNotValidException.class)
    @ResponseBody
    public UniformResultTemplate handleBindException(MethodArgumentNotValidException ex) {
        FieldError fieldError = ex.getBindingResult().getFieldError();
        log.warn("参数校验异常:{}({})", fieldError.getDefaultMessage(),fieldError.getField());
        return new UniformResultTemplate("10002",fieldError.getDefaultMessage());
    }

  // Get请求的参数校验,抛出的是ConstraintViolationException
  @ExceptionHandler(ConstraintViolationException.class)
    @ResponseBody
    public UniformResultTemplate handleGetBindException(ConstraintViolationException ex) {
        Set<ConstraintViolation<?>>  eSet = ex.getConstraintViolations();
        StringBuffer sb = new StringBuffer();
        if(!CollectionUtils.isEmpty(eSet)) {
            Iterator<ConstraintViolation<?>> iterator = eSet.iterator();
            while (iterator.hasNext()) {
                log.warn("参数校验异常:{}({})", iterator.next().getMessage());
                sb.append(iterator.next().getMessage()).append("::");
            }
        }
        return new UniformResultTemplate("10002",sb.toString());
    }

  // 方法签名参数错误
    @ExceptionHandler(MissingServletRequestParameterException.class)
    @ResponseBody
    public UniformResultTemplate handleGetBindException(MissingServletRequestParameterException ex) {
        log.warn("参数校验异常:{}", ex.getMessage());
        return new UniformResultTemplate("10002",ex.getMessage());
    }
}

(5)结果现象

{
    "code": "10002",
    "message": "市不能为空",
    "result": null,
    "totalTimes": null,
    "interfaceTimes": null
}

4、编译器校验工具

防止因使用错误Hibernate-Validation注解而导致程序运行时报错,增加编译器校验工具,进行友好提示。

<dependency>
    <groupId>org.hibernate</groupId>
    <artifactId>hibernate-validator-annotation-processor</artifactId>
    <version>6.1.5.Final</version>
</dependency>
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,271评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,275评论 2 380
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,151评论 0 336
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,550评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,553评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,559评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,924评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,580评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,826评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,578评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,661评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,363评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,940评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,926评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,156评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,872评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,391评论 2 342

推荐阅读更多精彩内容