springboot框架项目应用实践六(参数校验validation基础)

1.引言

项目开发中,参数校验是一项基础且重要的能力,一般会在框架层面去考虑这个事情。你比如说非空校验、Email地址校验、手机号码校验等。

这里我想提出一个问题:参数校验只是前端开发同学需要关注吗?是不是后端开发同学根本就不用考虑

答案是:不管前端,还是后端开发,我们都需要处理好参数校验。前端校验,某种程度上来说是为了提升用户体验;而后端校验,才是保障业务处理安全正确性的充分条件。所谓前端校验是防君子,不妨小人,讲的也是后端校验的重要性!

从今天这篇文章开始,我准备将校验框架spring validation方方面面的使用姿势分享给你,内容包括

  • spring validation基础:环境准备,基本使用,统一异常处理
  • spring validation进阶使用:分组、嵌套校验
  • spring validation高级使用:自定义校验规则

让我们开始吧!

2.案例

2.1.环境准备

做java开发的的小伙伴们,今天都非常熟悉spring-boot框架,因此我把案例环境基于spring-boot框架来搭建。

而且在使用spring-boot框架开发项目的时候,相信很多小伙伴在处理参数校验时,已经将@Validated、@Valid、@NotBlank、@NotNull等注解用的炉火纯青。但是,有时候我们仅仅停留在使用层面了,没有去深入了解背后一些原理性的东西,这其实不利于一个技术人的成长。

今天,我们就试着来看一下,我先给你看一个图

上图是我通过spring-boot搭建的项目环境,通过截图我们看到

  • spring-boot-starter-web,依赖了hibernate-validator包
  • hibernate-validator,依赖了validation-api包

这说明什么呢?其实在java API规范中,即JSR303定义了bean校验的标准,该标准就是我们当前看到的validation-api,但是你需要注意,validation-api仅仅定义了规范,并没有提供实现。

于是就有了hibernate-validator,它是validation-api规范的一个实现,相当于一个是接口,一个是接口实现,我们可以这么去理解。

到了这里spring-validation的定位就更加清晰了,它是对hibernate-validator的二次封装。你看这样一来,我们就很好的理解了它们之间的关系

  • validation-api是校验规范
  • hibernate-validator是规范的实现
  • spring validation是对hibernate-validator的二次封装

2.2.基础案例

提供api接口的时候,我们用的比较多的两类请求分别是post、get

  • post请求参数接收,有@RequestBody注解,即使用bean对象来接收参数
  • get请求参数接收,有@RequestParam、或者@PathVariable注解,即使用简单类型来接收参数

因此接下来,我将给你演示这两类应用场景下的参数校验。

2.2.1.POST请求校验案例

2.2.1.1.bean对象
java 复制代码
/**
 * 用户请求参数接收bean
 *
 * @author ThinkPad
 * @version 1.0
 */
@Data
public class UserQO {

    /**
     * 用户id
     */
    private Long userId;

    /**
     * 用户名称
     */
    @NotBlank(message = "用户名称不能为空!")
    @Length(min = 2,max = 10,message = "用户名称长度必须在2-10个字符")
    private String username;

    /**
     * 用户年龄
     */
    @NotNull(message = "年龄不能为空!")
    private Integer age;

}
2.2.1.2.controller
java 复制代码
/**
 * post请求,通过@RequestBody注解,结合bean对象接收参数
 *
 * @author ThinkPad
 * @version 1.0
 */
@RestController
@RequestMapping("/post/valid")
@Slf4j
public class PostValidController {

    @RequestMapping("save")
    public String saveUser(@RequestBody @Validated UserQO qo){

        log.info("保存用户.接收到用户信息:{}",qo);

        return "ok";
    }
}
2.2.1.3.请求验证

正常请求:参数符合要求

异常请求:参数不满足要求

2.2.2.GET请求案例校验

2.2.2.1.controller
java 复制代码
/**
 * get请求,通过@PathVairable注解接收参数
 *
 * @author ThinkPad
 * @version 1.0
 */
@RestController
@RequestMapping("/get/valid")
@Slf4j
@Validated
public class GetValidController {

    /**
     * 根据用户id查询用户,参数用户id最小值为100,最大值200
     * @param userId
     * @return
     */
    @RequestMapping("/{userId}")
    public UserQO getUserById(@PathVariable("userId")  @Min(100L) @Max(200L) Long userId){
        UserQO qo = new UserQO();
        qo.setUserId(userId);
        qo.setUsername("xiaoy");
        qo.setAge(18);

        return qo;
    }
}
2.2.2.2.请求验证

正常请求:参数符合要求

异常请求:参数不满足要求

2.3.统一异常处理

通过上面的案例,我们看到无论是通过@RequestBody注解结合bean对象接收参数,还是通过@PathVariable注解接收参数,都可以通过@Validated注解,与具体的校验规则注解@NotBlank/@Min/@Max完成参数的校验。

但是这里我们存在一个问题,当参数校验不通过的时候,直接将具体的异常信息响应给前端了,我们说这不太友好!

有时候项目中会期望定制http响应状态码,与格式化响应信息,那么我们该如何去处理呢?别忘了,spring框架给我们提供了统一异常处理的机制ControllerAdvice。

下面我们来进行统一的异常处理!

2.3.1.统一异常处理组件

java 复制代码
/**
 * 校验统一异常处理组件
 *
 * @author ThinkPad
 * @version 1.0
 */
@RestControllerAdvice
public class ValidExceptionHandler {

    @ExceptionHandler({MethodArgumentNotValidException.class})
    @ResponseStatus(HttpStatus.OK)
    @ResponseBody
    public String handleMethodArgumentException(MethodArgumentNotValidException e){
        // 获取校验结果
        BindingResult bindingResult = e.getBindingResult();
        List<FieldError> errors = bindingResult.getFieldErrors();
        StringBuilder builder = new StringBuilder("参数校验失败:[");
        errors.forEach(error ->{
            builder.append(error.getField())
                    .append(":")
                    .append(error.getDefaultMessage())
                    .append(",");
        });

        String result = builder.toString();
        result = result.substring(0, result.length() - 1);
        return result + "]";
    }

    @ExceptionHandler({ConstraintViolationException.class})
    @ResponseStatus(HttpStatus.OK)
    @ResponseBody
    public String handleConstraintException(ConstraintViolationException e){
        String msg = e.getMessage();
        return "参数校验失败:" + msg;
    }
}

2.3.2.验证异常响应结果

MethodArgumentNotValidException异常

ConstraintViolationException异常

相关推荐
Java程序之猿5 分钟前
Docker 部署Spring boot + Vue(若依为例)
vue.js·spring boot·docker
BirdMan9828 分钟前
app=Flask(__name__)中的__name__的意义
后端·python·flask
昨天今天明天好多天37 分钟前
【Scala】
开发语言·后端·scala
wmze1 小时前
AbstractQueuedSynchronizer源码分析
后端
Ethan. L2 小时前
iOS 模块化架构设计:主流方案与实现详解
ios·架构
有龍则灵2 小时前
Dubbo3.2.x 服务发现流程源码解析
后端·dubbo
阿迪卡多2 小时前
C#-Lambda
后端
八苦2 小时前
记录一下 简单udp和sni 代理 done
后端
程序猿本员2 小时前
Linux多进程
linux·后端
努力减肥的Lucas2 小时前
当lombok遇到mapstruct,会碰撞出什么样的火花
后端