java对get参数校验失败_Spring 参数校验的异常处理

Spring参数校验异常处理
本文介绍了Spring框架中三种常见的参数校验异常:BindException、MethodArgumentNotValidException及ConstraintViolationException。针对每种异常提供了详细的抛出场景、默认处理方式及自定义处理方法。

对于不同的参数解析方式,Spring 抛出的异常也不同,而且这些异常没有继承关系,异常的内部也各不相同,只能对每种异常单独处理。感觉这块地方 spring 没有设计好,处理起来比较麻烦。

跟参数相关的异常主要有三个需要手动处理。

org.springframework.validation.BindException

org.springframework.web.bind.MethodArgumentNotValidException

javax.validation.ConstraintViolationException

一个一个说

BindException

抛出异常的场景

请求参数绑定到java bean上失败时抛出

关键词 : @Valid 、 Java bean 、表单(Content-Type: multipart/form-data)

实例3ad7b808987a0f1c2ffde50d3d3c44f8.png

38cb2b38fbadff3b6bd5c360089103e9.png

通过 post 提交表单的方式访问 /register 接口,如果参数校验不通过会抛出 BindException 异常

异常的默认处理方式

org.springframework.validation.BindException 异常由

org.springframework.web.servlet.mvc.method.annotation.handleBindException() 方法处理,如下7ec9df9a70a46e48834d3f014f053055.png

异常的默认处理结果

例子如下,密码要求8到16位,我只填了4位时ab57af8e72898681b89ec04904251905.png

完整返回值如下

ded151e1c5ffbe067fdd816f46d4a7c9.png

可以看到,返回的结果过于详细,把类的内部结构都暴露了,这样肯定是不行的,我们需要对其进行自定义处理。

自定义异常的处理

spring 支持统一异常处理,我们可以在自定义的统一异常处理类中处理 BindException 异常

关于 spring 统一异常处理,请参考 基于spring 的统一异常处理

方式1

继承 org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler 类,重写 handleBindException() 方法

注意,类上需要打 @ControllerAdvice 注解

@ControllerAdvice

public class ApplicationExceptionHandler extends ResponseEntityExceptionHandler {

private static final Logger logger = LoggerFactory.getLogger(ApplicationExceptionHandler.class);

/**

* 表单绑定到 java bean 出错时抛出 BindException 异常

*

* @param ex

* @param headers

* @param status

* @param request

* @return

*/

@Override

protected ResponseEntity handleBindException(BindException ex, HttpHeaders headers, HttpStatus status,

WebRequest request) {

logger.error("参数绑定失败", ex);

if (ex.hasErrors()) {

List> list = new ArrayList<>();

for (ObjectError objectError : ex.getAllErrors()) {

Map map = new HashMap<>();

if (objectError instanceof FieldError) {

FieldError fieldError = (FieldError) objectError;

map.put("field", fieldError.getField());

map.put("message", fieldError.getDefaultMessage());

} else {

map.put("field", objectError.getObjectName());

map.put("message", objectError.getDefaultMessage());

}

list.add(map);

}

return new ResponseEntity<>(new FailResult<>(ApplicationEnum.PARAMETER_BIND_FAIL, list), HttpStatus.OK);

}

return super.handleBindException(ex, headers, status, request);

}

}

复制代码

方式2

通过 @ExceptionHandler 注解指定要处理的异常,并在处理方法中处理。

注意,类上需要打 @ControllerAdvice 注解

@ResponseStatus 注解用来指定http的返回状态

@ResponseBody 注解将返回对象转换称json

@ControllerAdvice

public class ApplicationExceptionHandler {

private static final Logger logger = LoggerFactory.getLogger(ApplicationExceptionHandler.class);

/**

* 表单绑定到 java bean 出错时抛出 BindException 异常

*

* @param ex

* @return

*/

@ExceptionHandler({BindException.class})

@ResponseStatus(HttpStatus.OK)

@ResponseBody

protected FailResult handleBindException(BindException ex) {

logger.error("参数绑定失败", ex);

List> list = new ArrayList<>();

for (ObjectError objectError : ex.getAllErrors()) {

Map map = new HashMap<>();

if (objectError instanceof FieldError) {

FieldError fieldError = (FieldError) objectError;

map.put("field", fieldError.getField());

map.put("message", fieldError.getDefaultMessage());

} else {

map.put("field", objectError.getObjectName());

map.put("message", objectError.getDefaultMessage());

}

list.add(map);

}

return new FailResult<>(ApplicationEnum.PARAMETER_BIND_FAIL, list);

}

}

复制代码

自定义处理后的返回结果

9027705a434524c5ef75742c1caca6c1.png

MethodArgumentNotValidException

抛出异常的场景

请求体绑定到java bean上失败时抛出

关键词 : @Valid 、 @RequestBody、Java bean 、表单(Content-Type: application/json、Content-Type: application/xml)

实例88c28594b1a143d960e5aa1653ba4f82.png

c1857e020d2b887bdded3ec9ef37d1fb.png

通过 put 提交 json 的方式访问 /info 接口,如果参数校验不通过会抛出 MethodArgumentNotValidException 异常

上面的抛出异常的场景和 BindException 的很相似。

接口具体是抛出 MethodArgumentNotValidException 异常还是抛出 BindException 异常,取决于 @RequestBody,或者说是取决于对请求参数的解析方式。

异常的默认处理方式

org.springframework.web.bind.MethodArgumentNotValidException 异常由

org.springframework.web.servlet.mvc.method.annotation.handleMethodArgumentNotValid() 方法处理,如下

4aec73bb49e87bccd977c883ecb3fbe5.png

异常的默认处理结果

如下,age 要求1-99 直接,我填的是09a82ce1e47e60b7b3e8fe4830f1d15b8.png

完整错误如下

3d801dd9f6239d2d5c207b161544ff59.png

可以看的,返回的数据格式和 BindException 返回的一模一样,同样暴露了类的内部结构。

自定义异常的处理

跟 BindException 一样,MethodArgumentNotValidException 异常同样也有两种处理方式。

继承 org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler 类,重写 handleMethodArgumentNotValid() 方法

通过 @ExceptionHandler 注解指定要处理的异常,并在处理方法中处理。

我这里就只介绍其中一种,另一种参考上面的 BindException

@ControllerAdvice

public class ApplicationExceptionHandler extends ResponseEntityExceptionHandler {

private static final Logger logger = LoggerFactory.getLogger(ApplicationExceptionHandler.class);

/**

* 将请求体解析并绑定到 java bean 时,如果出错,则抛出 MethodArgumentNotValidException 异常

*

* @param ex

* @param headers

* @param status

* @param request

* @return

*/

@Override

protected ResponseEntity handleMethodArgumentNotValid(MethodArgumentNotValidException ex,

HttpHeaders headers, HttpStatus status,

WebRequest request) {

logger.error("请求体绑定失败", ex);

if (ex.getBindingResult().hasErrors()) {

List> list = new ArrayList<>();

for (ObjectError objectError : ex.getBindingResult().getAllErrors()) {

Map map = new HashMap<>();

if (objectError instanceof FieldError) {

FieldError fieldError = (FieldError) objectError;

map.put("field", fieldError.getField());

map.put("message", fieldError.getDefaultMessage());

} else {

map.put("field", objectError.getObjectName());

map.put("message", objectError.getDefaultMessage());

}

list.add(map);

}

return new ResponseEntity<>(new FailResult<>(ApplicationEnum.PARAMETER_BIND_FAIL, list), HttpStatus.OK);

} else {

return super.handleMethodArgumentNotValid(ex, headers, status, request);

}

}

}

复制代码

自定义处理后的返回结果

df56351a0e06246962278fd5b2ca0d41.png

ConstraintViolationException

对非 java bean 参数的校验是 spring 框架“额外”提供的支持,需要用到 spring的@Validated注解

抛出异常的场景

普通参数(非 java bean)校验出错时抛出

关键词 : @Validated 、 非Java bean

实例1e34af6667574ea18001c0e7da88d615.png

注意,@Validated 注解需要打在类上

异常的默认处理方式

spring 肯定是对 ConstraintViolationException 异常进行了处理,但是目前我还没有找到在那个类中。

自定义异常的处理

id 要求是不能为空的,我没有填id时2145a6a3d9a21bd7a7359cd733bf049e.png

自定义异常的处理

由于没有找到 ConstraintViolationException 异常的处理类,无法使用重写处理方法来的方式来自定义异常的处理。只能换另一种方法。

通过 @ExceptionHandler 注解指定要处理的异常,并在处理方法中处理。

@ControllerAdvice

public class ApplicationExceptionHandler extends ResponseEntityExceptionHandler {

private static final Logger logger = LoggerFactory.getLogger(ApplicationExceptionHandler.class);

/**

* 普通参数(非 java bean)校验出错时抛出 ConstraintViolationException 异常

*

* @param e

* @return

*/

@ExceptionHandler({ConstraintViolationException.class})

@ResponseStatus(HttpStatus.OK)

@ResponseBody

public Result handleConstraintViolationException(ConstraintViolationException e) {

logger.error("程序出错", e);

List> list = new ArrayList<>();

// e.getMessage() 的格式为 getUser.id: id不能为空, getUser.name: name不能为空

String[] msgs = e.getMessage().split(", ");

for(String msg : msgs){

String[] fieldAndMsg = msg.split(": ");

String field = fieldAndMsg[0].split("\\.")[1];

String message = fieldAndMsg[1];

Map map = new HashMap<>();

map.put("field", field);

map.put("message", message);

list.add(map);

}

return new FailResult<>(ApplicationEnum.PARAMETER_BIND_FAIL, list);

}

}

复制代码

自定义处理后的返回结果

3a87f702310958300bd84749b3c219be.png

总结

不同的参数校验方式会产生不同的异常

表单绑定到 java bean 出错时,会抛出 BindException 异常

将请求体解析并绑定到 java bean 时,如果出错,则抛出 MethodArgumentNotValidException 异常

普通参数(非 java bean)校验出错时,会抛出 ConstraintViolationException 异常

BindException 异常和 MethodArgumentNotValidException 异常默认的处理放在在 org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler 类中,可以通过继承该类并重写其中的处理方法到达自定义处理的目的。

ConstraintViolationException 异常也有默认的处理方法,但是我没找到,这种情况下,可以通过 @ExceptionHandler 注解指定要处理的异常,并在处理方法中处理。

Spring 框架中,参数校验是构建健壮、安全和可维护的 Web 应用程序的重要组成部分。Spring 提供了多种方式来实现参数校验,主要包括标准的 Bean Validation 注解(如 `@Valid`)以及 Spring 扩展的 `@Validated` 注解。此外,还可以结合自定义注解和 AOP 技术来实现更灵活的校验逻辑。 ### 参数校验的基本实现方式 1. **使用 `@Valid` 注解** `@Valid` 是 JSR-303(Bean Validation 1.0)和 JSR-349(Bean Validation 1.1)标准的一部分,主要用于在方法参数上进行对象图的字段级校验。 示例代码如下: ```java public class UserDTO { @NotBlank(message = "姓名不能为空") private String name; @Email(message = "邮箱格式错误") private String email; @Min(value = 18, message = "年龄必须≥18岁") private int age; } @RestController public class UserController { @PostMapping("/users") public ResponseEntity createUser(@RequestBody @Valid UserDTO user) { // 校验通过后执行业务逻辑 return ResponseEntity.ok("用户创建成功"); } } ``` 上述代码中,`@NotBlank`、`@Email` 和 `@Min` 是常用的字段约束注解,用于对 `UserDTO` 的字段进行校验[^1]。 2. **嵌套对象校验** 在处理嵌套对象时,需要在嵌套字段上添加 `@Valid` 注解以触发嵌套对象的校验逻辑: ```java public class OrderDTO { @Valid private List<OrderItem> items; } public class OrderItem { @NotNull private Long productId; @Min(1) private Integer quantity; } ``` 通过这种方式,Spring 可以递归地对整个对象图进行校验[^1]。 3. **使用 `@Validated` 注解** `@Validated` 是 Spring 提供的扩展注解,支持类级别的校验,并且可以在非控制器类中使用(如 Service 层)。它支持分组校验和方法级别的校验。 示例: ```java @Service @Validated public class UserService { public void validateUser(@Valid UserDTO user) { // 校验逻辑 } } ``` `@Validated` 可以与 `@Valid` 配合使用,实现更灵活的校验逻辑。 4. **自定义校验注解与 AOP 结合** 除了使用内置注解外,还可以定义自定义校验逻辑。例如,结合 Aviator 表达式引擎实现动态校验: 自定义注解定义如下: ```java @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) public @interface AviatorValidation { String expression(); String errorMessage() default "参数不符合要求"; } ``` 然后通过 AOP 切面实现具体的校验逻辑,例如解析表达式并验证参数值是否符合规则[^2]。 ### 常用校验注解说明 - `@NotNull`:适用于所有类型,用于确保对象不为 null。 - `@NotBlank`:仅适用于字符串类型,确保字符串不为空白(如空格、换行等)。 - `@NotEmpty`:适用于字符串、集合、数组等类型,确保对象不为 null 且不为空。 - `@Min` / `@Max`:用于数值类型,限制最小值和最大值。 - `@Email`:用于校验邮箱格式。 - `@Size`:用于集合、字符串、数组等,限制大小范围[^3]。 ### 校验异常处理Spring MVC 中,可以通过 `@ControllerAdvice` 或 `@ExceptionHandler` 捕获 `MethodArgumentNotValidException` 异常,并统一返回错误信息: ```java @ControllerAdvice public class ValidationHandler { @ExceptionHandler(MethodArgumentNotValidException.class) public ResponseEntity<String> handleValidationExceptions(MethodArgumentNotValidException ex) { return ResponseEntity.badRequest().body(ex.getBindingResult().getAllErrors().get(0).getDefaultMessage()); } } ``` 该机制可以有效提升 API 的健壮性和用户体验。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值