Spring Boot参数验证

tech2024-07-25  44

前言

参数验证是一个应用中必不可少的一部分操作,参数验证又可以分为前端验证和后端验证。如果没有参数验证的话,我们的逻辑可能就会报错,例如:空指针异常,更严重的可能造成系统的瘫痪。 参数验证又可以分为前端验证和后端验证,前端验证可以通过各种手段进行跳过,例如直接调用接口等等。为了防护系统出现各种异常,后端验证是不可取少的一部分。本文将主要描述使用Spring Boot项目的时候,后端如何进行参数验证。

一、参数传递的方式

请求路径中携带的参数,一般是GET请求在请求体中携带的参数,一般是POST、PUT请求

二、参数验证注解

我们主要是使用javax.validation.constraints和org.hibernate.validator.constraints包下的注解进行参数验证。常见的注解如下:

@NotNull:表示参数不能为Null,如果为空的话,就无法通过验证@Max:一般用于数字,表示不能超过最大值

三、参数验证——请求路径中携带的参数

一般用于GET请求中。

例1:希望name不能为空,age不能大于100的限制条件。

@RestController @Validated public class TestController { @GetMapping("/testget") public JSONObject testget(@NotNull(message = "姓名不能为空") String name, @Max(value = 100, message = "不能大于100") Integer age) { JSONObject jsonObject = new JSONObject(); return jsonObject; } }

如果我们访问localhost:8080/testget或者localhost:8080/testget?age=101或者localhost:8080/testget?age=1,就会抛出一个ConstraintViolationException的异常信息。前端就会收到一个500的错误信息。我们可以进行异常统一捕获,在后面进行描述,从而向前端返回统一的错误信息。

在使用路径中携带参数的注意事项:
必须在类上添加@Validated注解在需要验证的参数中添加验证注解。使用路径中携带参数如果没有通过验证,就会抛出ConstraintViolationException的异常信息。

四、参数验证——请求体中携带的参数

一般用于POST、PUT请求

我们使用Restful的格式传递参数。参数使用JSON格式传递。 使用请求体中携带参数的方式主要有三种形式。

下面是使用的实体:包含一个嵌套实体的ChildDTO类

public class AreaDTO { @NotNull(groups = {UpdateValidation.class}, message = "区域ID不能为空") private Long id; @NotNull(message = "父级ID不能为空", groups = {AddValidation.class}) private Long parentId; @NotNull(message = "片区名称不能为空") private String name; @Valid private ChildDTO childDTO; } public class ChildDTO { @NotNull(message = "不能为空") private String childName; @NotNull(groups = {AddValidation.class}, message = "不能为空") private Long childId; @Max(groups = {UpdateValidation.class}, value = 100 ,message = "不能为空") @Min(groups = {AddValidation.class}, value = 50, message = "最小值") private Long age; } //分组一 public interface AddValidation { } //分组二 public interface UpdateValidation { }

方式1:抛出异常方式

例2:

@PostMapping("/testpost") public JSONObject testpost(@Validated @RequestBody AreaDTO areaDTO) { JSONObject jsonObject = new JSONObject(); return jsonObject; }

例3

@PostMapping("/testpost1") public JSONObject testpost1(@Validated(value = {AddValidation.class}) @RequestBody AreaDTO areaDTO) { JSONObject jsonObject = new JSONObject(); return jsonObject; }

上面两个例子中,第一个例子使用了默认的分组(即Default),而第二个例子使用了AddValidation的分组。我们在添加验证条件的时候,可以为其指定分组限制,使用groups属性,例如:@NotNull(message = "父级ID不能为空", groups = {AddValidation.class}),这个验证限制只有在@Validated(value = {AddValidation.calss})才会起作用。只有符合验证条件的分组才会进行验证。

例如:例2中,没有添加分组,默认使用Default分组,就会验证AreaDTO.name,AreaDTO.ChildDTO.childName两个属性。例3中,添加了AddValidation的验证分组,就会验证AreaDTO.parentId,AreaDTO.ChildDTO.childId,AreaDTO.ChildDTO.age三个属性。

当无法通过验证条件的时候,就会抛出一个MethodArgumentNotValidException的异常信息。里面包含了所有所有没有通过验证的属性。

注意:没有添加分组,则默认使用Default的分组。

在使用请求体中抛出异常的注意事项:
在需要验证参数前面添加@Validation注解,在需要验证属性前面添加@Max等验证条件如果验证无法通过,就会抛出一个MethodArgumentValidException的异常

方式二:使用实体封装方式

例4

@PostMapping("/testpost4") public JSONObject testpost4(@Validated @RequestBody AreaDTO areaDTO, BindingResult result) { //手动验证是否有没有通过的参数 if (result.hasErrors()) { for (FieldError fieldError : result.getFieldErrors()) { System.out.println(fieldError.getField() + ":" + fieldError.getDefaultMessage()); } //这里返回我们自定义的返回结果 } JSONObject jsonObject = new JSONObject(); return jsonObject; }
在使用请求体中实体封装的注意事项:
在需要验证的参数前面添加@Validated注解在需要验证的参数后面,添加BindingResult的实体在需要验证的属性前面,添加@Max等验证属性没有通过验证,就会绑定到BindingResult实体中,不会抛出异常,必须手动进行判断

方式三:手动验证

例5:

@Autowired private Validator validator; @PostMapping("/testpost5") public JSONObject testpost5(@RequestBody AreaDTO areaDTO) { Set<ConstraintViolation<AreaDTO>> validate = validator.validate(areaDTO, AddValidation.class, Default.class); if (!validate.isEmpty()) { String message = validate.iterator().next().getMessage(); logger.error(message); } JSONObject jsonObject = new JSONObject(); return jsonObject; }
在使用请求体中手动验证的注意事项:
在需要验证的属性前面,添加@Max等验证属性使用Validator实例调用validate方法验证可以在任何地方进行参数验证,更加灵活

到此,就完成了参数验证的全部内容。各种方式各有不同。

附录一:添加统一异常处理
@RestControllerAdvice public class CommonRuntimeExceptionHandle { @ExceptionHandler(ConstraintViolationException.class) public JSONObject methodArgumentNotValidExceptionHandle(ConstraintViolationException methodArgumentNotValidException) { Set<ConstraintViolation<?>> constraintViolations = methodArgumentNotValidException.getConstraintViolations(); String message = ""; for (ConstraintViolation<?> next : constraintViolations) { message += next.getMessage()+" , "; } JSONObject responseResult = new JSONObject(); responseResult.put("message", "参数不正确:" + message); responseResult.put("code", "-1"); return responseResult; } @ExceptionHandler(MethodArgumentNotValidException.class) public JSONObject methodArgumentNotValidExceptionHandle(MethodArgumentNotValidException exception) { FieldError fieldError = exception.getBindingResult().getFieldError(); MethodParameter parameter = exception.getParameter(); logger.error("在执行{}.{}的时候出现绑定JSON数据参数异常,字段名称为:{},提示消息:{}", parameter.getDeclaringClass().getName(), parameter.getMethod().getName(), fieldError.getField(), fieldError.getDefaultMessage()); JSONObject responseResult = new JSONObject(); responseResult.put("message", "参数不正确:" + fieldError.getDefaultMessage()); responseResult.put("code", "-1"); return responseResult; } }

我们使用@RestControllerAdvice的注解,标识这个类为控制器的增强类。

附录二:快速失败

默认情况下,Validator会验证所有需要验证的参数。如果有多个错误,就会出现多个字段异常信息。如果希望,当出现一个参数异常的时候,直接退出,不在继续验证其他参数的正确性。需要配置快速退出

@Configuration public class ValidateConfig { @Bean public Validator validator() { ValidatorFactory validatorFactory = Validation.byProvider(HibernateValidator.class) .configure().failFast(true).buildValidatorFactory(); return validatorFactory.getValidator(); } }
最新回复(0)