@requestparam 参数null_帮你少些一半参数校验代码的小技巧(重置版)

本文介绍了Spring MVC中利用@Validated和@Valid进行参数校验的方法,包括基本注解的使用、分组校验和嵌套校验,帮助减少代码中的校验逻辑。通过实例展示了如何改造注册接口,实现更高效的数据验证。
摘要由CSDN通过智能技术生成
0011fb2a38184d9381f6b8a9c7cee3f9.png

介绍

github地址:https://github.com/erlieStar/spring-boot-validation-demo

几乎每个web网站都会对用户提交的参数进行校验,前端要做,后端也要做。防止用户直接通过接口调用的方式来请求或保存数据,从而导致产生脏数据等其他严重的后果。

因为有些校验的逻辑也很繁琐,为了减轻开发者的负担,Java发布了 JSR303/JSR-349数据校验规范

JSR303 是一项标准,JSR-349 是其的升级版本,添加了一些新特性,他们规定一些校验规范即校验注解,如 @Null,@NotNull,@Pattern,他们位于 javax.validation.constraints 包下,只提供规范不提供实现。而 hibernate validation 是对这个规范的实践(不要将 hibernate 和数据库 orm 框架联系在一起),他提供了相应的实现,并增加了一些其他校验注解,如 @Length,@Range 等等,他们位于 org.hibernate.validator.constraints 包下。而万能的 spring 为了给开发者提供便捷,对 hibernate validation 进行了二次封装,显示校验 validated bean 时,你可以使用 spring validation 或者 hibernate validation,而 spring validation 另一个特性,便是其在 springmvc 模块中添加了自动校验,并将校验信息封装进了特定的类中。这无疑便捷了我们的 web 开发

在spring boot项目中只要加入如下依赖即可使用校验注解

 org.springframework.boot spring-boot-starter-web

查看子依赖会发现有如下依赖

 org.hibernate hibernate-validator

JSR提供的部分校验注解如下

注解 解释 @Null 被注释的元素必须为 null @NotNull 被注释的元素必须不为 null @AssertTrue 被注释的元素必须为 true @AssertFalse 被注释的元素必须为 false @Min 被注释的元素必须是一个数字,其值必须大于等于指定的最小值 @Max 被注释的元素必须是一个数字,其值必须小于等于指定的最大值 @Pattern 被注释的元素必须符合指定的正则表达式,如验证手机号的正则表达式子,"^1[0-9]{10}$" @Email 被注释的元素必须是电子邮箱地址

经常被搞混的3个注解

注解 解释 @NotNull 不能为null,但可以为empty @NotEmpty 不能为null,而且长度必须大于0 @NotBlank 只能作用在String上,不能为null,而且调用trim()后,长度必须大于0

我来举一个org.apache.commons.lang3.StringUtils中的例子,你就能理解NotBlank的意思了,如下断言都能测试通过

assertEquals(true, StringUtils.isBlank(null));assertEquals(true, StringUtils.isBlank(""));// 空格assertEquals(true, StringUtils.isBlank(" "));// 回车assertEquals(true, StringUtils.isBlank("    "));

改造一个注册的接口

先定义一下状态枚举类

public enum ResponseCode {    SUCCESS(0, "成功"),    ERROR(1, "失败"),    ILLEGAL_ARGUMENT(2, "参数错误"),    EMPTY_RESULT(3, "结果为空"),    NEED_LOGIN(10, "需要登录");    private final int code;    private final String desc;    ResponseCode(int code, String desc) {        this.code = code;        this.desc = desc;    }    public int getCode() {        return code;    }    public String getDesc() {        return desc;    }}

定义项目的返回对象

@Data/** 注解的作用是序列化json时,如果是null对象,key也会消失 */@JsonInclude(JsonInclude.Include.NON_NULL)public class ServerResponse implements Serializable {    /** 状态值 **/    private int status;    /** 描述 **/    private String msg;    /** 数据 **/    private Object data;    public ServerResponse(int status, String msg) {        this.status = status;        this.msg = msg;    }    public static ServerResponse success() {        return new ServerResponse(ResponseCode.SUCCESS.getCode(), ResponseCode.SUCCESS.getDesc());    }    public static ServerResponse illegalArgument(String msg) {        return new ServerResponse(ResponseCode.ILLEGAL_ARGUMENT.getCode(), msg);    }}

注册接口,这里省略了一部分校验

@RequestMapping("regist")public ServerResponse regist(@RequestParam("name") String name,        @RequestParam("phone") String phone,        @RequestParam("phone") String email) { if (StringUtils.isBlank(name)) {  return ServerResponse.illegalArgument("用户名不能为空"); } // 其他一堆校验过程,调用service return ServerResponse.success();}

当不满足条件时返回如下

{    "status": 2,    "msg": "用户名不能为空"}

当参数较多,校验的逻辑也越来越多,这时可以直接将前端传过来参数直接转为对象

@Datapublic class Student {    @NotBlank(message = "用户名不能为空")    private String name;    @NotBlank(message = "手机号不能为空")    @Pattern(regexp = "^1(3|4|5|7|8)d{9}$", message = "手机号码格式错误")    private String phone;    @NotBlank(message = "邮箱不能为空")    @Email(message = "邮箱格式错误")    private String email;}
@RequestMapping("regist")public ServerResponse regist(@Valid Student student, BindingResult bindingResult) { if (bindingResult.hasErrors()) {  List errorList = bindingResult.getFieldErrors();  // list不为空  if (CollectionUtils.isNotEmpty(errorList)) {   return ServerResponse.illegalArgument(errorList.get(0).getDefaultMessage());  } } // 调用service return ServerResponse.success();}

代码将不满足条件的字段的描述取一个出来返回,类似如下。当都满足时才会返回成功

{    "status": 2,    "msg": "用户名不能为空"}

需要注意的地方如下

参数 Student 前需要加上@Valid或@Validated 注解(下文说这2个注解的区别),表明需要 spring 对其进行校验,而校验的信息会存放到其后的 BindingResult 中。注意,必须相邻,如果有多个参数需要校验,形式可以如下。foo(@Validated Student student, BindingResult studentBindingResult ,@Validated Bar bar, BindingResult barBindingResult); 即一个校验类对应一个校验结果。

@Validated比@Valid的功能更强大

@Validated比@Valid的功能更强大,主要体现在@Validated可以进行分组校验和嵌套校验。如果同一个类,在不同的使用场景下有不同的校验规则,那么可以使用分组校验。

分组校验

定义分组校验的组

public interface ValidateGroup {    interface RouteValidStart {}    interface RouteValidEnd {}}

使用demo如下 RouteValidStart组只校验始发省id和详细地址 RouteValidEnd组只校验目的省id和详细地址

@Data@Builder@NoArgsConstructor@AllArgsConstructorpublic class Route {    @NotNull(groups = {RouteValidStart.class}, message = "始发地省id不能为空")    private Integer startProvinceId;    @NotNull(groups = {RouteValidEnd.class}, message = "目的地省id不能为空")    private Integer endProvinceId;    @NotNull(groups = {RouteValidStart.class, RouteValidEnd.class}, message = "详细地址不能为空")    private String address;}

测试类如下

@Testpublic void test() {    Route route = Route.builder().build();    String errorMsg = CommonUtil.getErrorResult(route, ValidateGroup.RouteValidStart.class);    // 始发地省id不能为空 详细地址不能为空    System.out.println(errorMsg);    route = Route.builder().startProvinceId(1).address("详细地址").build();    errorMsg = CommonUtil.getErrorResult(route, ValidateGroup.RouteValidStart.class);    // ""    System.out.println(errorMsg);    route = Route.builder().address("详细地址").build();    errorMsg = CommonUtil.getErrorResult(route, ValidateGroup.RouteValidEnd.class);    // 目的地省id不能为空    System.out.println(errorMsg);}

校验工具类如下

public class CommonUtil {    public static String getErrorResult(Object o, Class>... groups) {        StringBuilder errorMsg = new StringBuilder();        ValidatorFactory vf = Validation.buildDefaultValidatorFactory();        Validator validator = vf.getValidator();        Set> set = validator.validate(o, groups);        for (ConstraintViolation constraintViolation : set) {            errorMsg.append(constraintViolation.getMessage()).append(" ");        }        return errorMsg.toString();    }}

当然在Controller中也可以使用,形式如下

@RequestMapping("addRoute")public ServerResponse addRoute(@Validated({RouteValidStart.class}) Route route, BindingResult bindingResult) 

嵌套校验

假如我们还要求学生填写父亲和母亲的名字(并且不能为空),而后端的设计中父亲和母亲的名字被放在另一个类Relation中,这就要求我们能进行嵌套校验。

我们要做如下2个事情

  1. 将方法中的@Valid注解改为@Validated
  2. 在relation属性上加@Valid注解
@Datapublic class Student {    @NotBlank(message = "用户名不能为空")    private String name;    @NotBlank(message = "手机号不能为空")    @Pattern(regexp = "^1(3|4|5|7|8)d{9}$", message = "手机号码格式错误")    private String phone;    @NotBlank(message = "邮箱不能为空")    @Email(message = "邮箱格式错误")    private String email;    @NotNull(message = "父母名字不能为空")    @Valid    private Relation relation;}
@Datapublic class Relation {    @NotBlank(message = "父亲的姓名不能为空")    private String fatherName;    @NotBlank(message = "父亲的姓名不能为空")    private String motherName;}

那Relation类的fatherName属性如何被赋值呢? 很简单,前端传入的参数名是如下即可

xxxx?relation.fatherName=li&relation.motherName=liu
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值