[Spring Validation]SpringBoot请求参数校验:分组校验

问题引入

在使用 SpringBoot 实现请求体参数校验的时候,有些字段可能是共用的,比如在创建和更新的时候共用请求体 UserDTO,但是差别就在于 userId 字段。创建时不需要 userId 字段,更新则需要。

如果直接在 userId 字段标注 @NotNull 注解,那么创建操作就会失败,所以需要使用分组校验来区分。

搭建工程

创建一个 SpringBoot 工程,导入以下依赖:


xml

体验AI代码助手

代码解读

复制代码

<parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.3.12.RELEASE</version> </parent> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-validation</artifactId> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <scope>annotationProcessor</scope> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-configuration-processor</artifactId> <scope>annotationProcessor</scope> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-devtools</artifactId> <scope>runtime</scope> </dependency> </dependencies>

然后简单编写一下 application.yml 配置:


yaml

体验AI代码助手

代码解读

复制代码

server: port: 8080 spring: application: name: spring-boot-first jackson: default-property-inclusion: non_null mvc: locale: en_US # 内置的错误详情信息返回显示为英文 locale-resolver: fixed

DTO类

案例用到的 DTO 类如下:

UserDTO


java

体验AI代码助手

代码解读

复制代码

@Builder @Data public class UserDTO { @NotNull @Range(max = Integer.MAX_VALUE) private Integer id; @Range(max = 200) private Integer age; @Length(max = 256) private String username; @Length(max = 256) private String address; @Valid private JobInfo jobInfo; }

JobInfo


java

体验AI代码助手

代码解读

复制代码

@Data @AllArgsConstructor @NoArgsConstructor public class JobInfo { @NotNull @Length(max = 64) private String company; @NotNull @Length(max = 64) private String position; }

响应结果类 R


java

体验AI代码助手

代码解读

复制代码

@Data public class R<T> { private Integer code = NORMAL_CODE; private String msg = ""; private T data; private List<ParamError> errors; public static final String SUCCESS = "操作成功"; public static final String FAILURE = "操作失败"; public static final Integer ERROR_CODE = 500; public static final Integer NORMAL_CODE = 200; public R() { } public R(T data) { this.data = data; } public R(Integer code, String msg, T data) { this.code = code; this.msg = msg; this.data = data; } public static <T> R<T> ok() { return ok(HttpStatus.OK.value(), SUCCESS, null); } public static <T> R<T> ok(Integer code, String msg, T data) { R<T> r = new R<>(); r.setCode(code); r.setMsg(msg); r.setData(data); return r; } public static <T> R<T> ok(T data) { return ok(ERROR_CODE, SUCCESS, data); } public static <T> R<T> fail(Integer code, String msg) { return ok(code, msg, null); } public static <T> R<T> fail(Integer code) { return ok(code, FAILURE, null); } public static <T> R<T> fail(String msg) { return ok(ERROR_CODE, msg, null); } public static <T> R<T> fail() { return ok(ERROR_CODE, FAILURE, null); } }

参数详情类 ParamError


java

体验AI代码助手

代码解读

复制代码

@Data @NoArgsConstructor @AllArgsConstructor public class ParamError { private String param; private String reason; }

全局异常捕获器

在 SpringBoot 工程中需要定义一个全局异常捕获器才能把详细的错误信息返回给客户端。在此不多做赘述,不清楚地可以翻看 SpringBoot 的资料:


java

体验AI代码助手

代码解读

复制代码

@RestControllerAdvice public class GlobalExceptionHandler { // 用于校验请求体字段 RequestBody 错误的方法 @ResponseStatus(HttpStatus.BAD_REQUEST) @ExceptionHandler(MethodArgumentNotValidException.class) public R<Void> handleValidationExceptions(MethodArgumentNotValidException ex) { R<Void> resp = R.fail(HttpStatus.BAD_REQUEST.value()); List<ParamError> errors = ex.getBindingResult().getAllErrors().stream().map(error -> { String paramName = ((FieldError) error).getField(); String reason = error.getDefaultMessage(); return new ParamError(paramName, reason); }).collect(Collectors.toList()); resp.setErrors(errors); return resp; } // 用于校验请求参数 RequestParam/PathVariable 错误的方法 @ResponseStatus(HttpStatus.BAD_REQUEST) @ExceptionHandler(ConstraintViolationException.class) public R<Void> handleConstraintViolation(ConstraintViolationException ex) { R<Void> resp = R.fail(HttpStatus.BAD_REQUEST.value()); List<ParamError> errors = ex.getConstraintViolations().stream().map(cv -> { String paramName = cv.getPropertyPath().toString().split("\\.")[1]; String reason = cv.getMessage(); return new ParamError(paramName, reason); }).collect(Collectors.toList()); resp.setErrors(errors); return resp; } }

开发一个UserController

开发一个简单的接口:


java

体验AI代码助手

代码解读

复制代码

@Validated @RestController @RequestMapping("/user") public class UserController { @GetMapping("/{id}") public R<UserDTO> getById(@Range(max = Integer.MAX_VALUE) @PathVariable("id") Integer id) { UserDTO user = UserDTO.builder() .id(id) .age(200) .username("奥巴马") .address("LA") .jobInfo(new JobInfo("Government", "President")) .build(); return R.ok(user); } @PostMapping("/create") public String create(@Validated @RequestBody UserDTO user) { System.out.println(user); return "creating user succeeded"; } @PutMapping("/update") public String update(@Validated @RequestBody UserDTO user) { System.out.println(user); return "updating user succeeded"; } @DeleteMapping("/del") public String delById(@Length(max = 64) @RequestParam("id") String id) { System.out.println(id); return "deleting user succeeded"; } }

使用ApiPost测试

现在使用 ApiPost 测试创建用户和更新用户的接口:

image.png

响应体显示 id 不可以为 null,因为这边创建用户和更新用户是共用的 UserDTO 类的字段,创建时不需要,更新时候则需要。如果根据不同操作类型定义不同的 DTO 对象,比如:UserCreateDTO 和 UserUpdateDTO 那么就显得太冗余了。这时候就需要使用到分组校验的特性了。

分别定义Create和Update接口

定义如下接口并继承自 javax.validation.groups.Default 接口:


java

体验AI代码助手

代码解读

复制代码

package org.codeart.validation; import javax.validation.groups.Default; public interface Create extends Default { } public interface Update extends Default { }

然后 UserController 的方法参数内部的 @Validated 注解的参数添加上分组的接口类型:


java

体验AI代码助手

代码解读

复制代码

@PostMapping("/create") public String create(@Validated(Create.class) @RequestBody UserDTO user) { System.out.println(user); return "creating user succeeded"; } @PutMapping("/update") public String update(@Validated(Update.class) @RequestBody UserDTO user) { System.out.println(user); return "updating user succeeded"; }

在 UserDTO 类的字段上添加校验的注解:


java

体验AI代码助手

代码解读

复制代码

@Null(groups = Create.class) @NotNull(groups = Update.class) @Range(max = Integer.MAX_VALUE) private Integer id;

现在就可以实现分组校验的功能了,再使用 ApiPost 来测试一下:

image.png

image.png

在创建用户时传了 id 报错,修改用户时没有传 id 也报错,最终实现了我们想要的效果。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值