# spring-boot-validator
**Repository Path**: hweiyu/spring-boot-validator
## Basic Information
- **Project Name**: spring-boot-validator
- **Description**: spring boot参数校验框架validator的使用以及优雅的异常处理方式
- **Primary Language**: Java
- **License**: MulanPSL-2.0
- **Default Branch**: master
- **Homepage**: None
- **GVP Project**: No
## Statistics
- **Stars**: 2
- **Forks**: 1
- **Created**: 2021-03-05
- **Last Updated**: 2025-03-22
## Categories & Tags
**Categories**: Uncategorized
**Tags**: None
## README
# 概述
* spring boot参数校验框架validator的使用以及优雅的异常处理方式。
* 在spring boot项目中,为了让rest接口更加的稳定,健壮,避免非法参数造成的系统未知异常,因此对传入的参数进行校验是非常有必要的。接下来分享一下比较友好的校验方式。
# 校验框架[hibernate-validator]
### 介绍
* 来自官网的介绍 Express validation rules in a standardized way using annotation-based constraints and benefit from transparent integration with a wide variety of frameworks.
* 大概翻译过来就是,使用基于注解的标准化的式表达式验证规则,并受益于与各种框架的透明集成。
### spring boot集成
* 1.maven添加依赖,如果spring boot在2.3.0以下,忽略此步骤,因为2.3.0以下的版本已经集成有validator模块
```maven
org.springframework.boot
spring-boot-starter-validation
```
* 2.添加代码
```java
//传输对象dto
@Data
public class DemoDTO implements Serializable {
private static final long serialVersionUID = 1019466745376831818L;
@NotNull(message = "字段a不允许为空")
private Integer a;
@NotBlank(message = "字段b不允许为空")
private String b;
}
//统一结果返回
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Response implements Serializable {
private static final long serialVersionUID = 4921114729569667431L;
//状态码,200为成功,其它为失败
private Integer code;
//消息提示
private String message;
//数据对象
private T data;
//成功状态码
public static final int SUCCESS = 200;
//失败状态码
public static final int ERROR = 1000;
public static Response success(R data) {
return new Response<>(SUCCESS, "success", data);
}
public static Response error(String msg) {
return new Response<>(ERROR, msg, null);
}
}
//控制器
@RestController
public class DemoController {
@PostMapping(value = "/check")
public Response check(@Valid @RequestBody DemoDTO dto) {
return Response.success(true);
}
}
```
* 3.启动服务,访问 http://localhost:8080/check
```json
//请求参数:
{
"a": null,
"b": null
}
//返回结果:
{
"timestamp": "2021-03-05T09:05:35.780+00:00",
"status": 500,
"error": "Internal Server Error",
"message": "",
"path": "/check"
}
```
说明能正常拦截,但是这个提示不友好,该如何优化呢?往下会具体说明。
### 自定义校验器
* 1.默认的校验器,并不能满足所有的需求,比如我要限制传入的字符串长度在1到20之间,但是要求中文一个汉字按两个字符计算,即最多只能输入10个汉字,默认的@Size无法解决这问题;又比如,传入的参数只能是固定的枚举值,又该怎么办呢?所以,就需要到自定义注解出马了。
* 2.添加代码
```java
//字符长度校验注解(中文占两个字符)
@Target({METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER})
@Retention(RUNTIME)
@Documented
@Constraint(validatedBy = LengthValidator.class)
public @interface Length {
boolean required() default true;
String message() default "字符串长度不在范围内";
Class>[] groups() default {};
Class extends Payload>[] payload() default {};
int min() default 0;
int max() default Integer.MAX_VALUE;
}
//字符长度校验器(中文占两个字符)
public class LengthValidator implements ConstraintValidator {
private Length length;
@Override
public void initialize(Length constraintAnnotation) {
this.length = constraintAnnotation;
}
@Override
public boolean isValid(String s, ConstraintValidatorContext constraintValidatorContext) {
int len = this.calLen(s);
return len >= length.min() && len <= length.max();
}
private int calLen(String s) {
if (null == s) {
return 0;
}
//中文占两个字符长度
return s.trim()
.replaceAll("[^\\x00-\\xff]", "**")
.length();
}
}
//范围枚举值
@Target({METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER})
@Retention(RUNTIME)
@Documented
@Constraint(validatedBy = RangeEnumValidator.class)
public @interface RangeEnum {
boolean required() default true;
String message() default "枚举值错误";
Class>[] groups() default {};
Class extends Payload>[] payload() default {};
String[] allowableValues() default {};
}
//范围枚举值校验器
public class RangeEnumValidator implements ConstraintValidator {
private RangeEnum range;
@Override
public void initialize(RangeEnum constraintAnnotation) {
this.range = constraintAnnotation;
}
@Override
public boolean isValid(Object o, ConstraintValidatorContext constraintValidatorContext) {
String[] allowableValues = range.allowableValues();
if (null == o || allowableValues.length == 0) {
return false;
}
String s = o.toString();
for (String allowableValue : allowableValues) {
if (s.equals(allowableValue)) {
return true;
}
}
return false;
}
}
//传输对象dto
@Data
public class Demo2DTO implements Serializable {
private static final long serialVersionUID = 1019466745376831818L;
@Length(min = 1, max = 5, message = "字段c长度在1到5个字符之间")
private String c;
@RangeEnum(allowableValues = {"x", "y"}, message = "字段d允许取值为:x,y")
private String d;
}
//控制器
@RestController
public class DemoController {
@PostMapping(value = "/check2")
public Response check2(@Valid @RequestBody Demo2DTO dto) {
return Response.success(true);
}
}
```
* 3.启动服务,访问 http://localhost:8080/check2
```json
//请求参数:
{
"c": null,
"d": null
}
//返回结果:
{
"timestamp": "2021-03-05T09:05:35.780+00:00",
"status": 500,
"error": "Internal Server Error",
"message": "",
"path": "/check2"
}
```
### 优雅的异常处理
* 1.前面我们已经看到,参数非法,提示十分不友好,请求方根本不知道哪里出了问题,为了解决这个问题,我们需要使用到`org.springframework.web.bind.annotation.RestControllerAdvice`,此注解允许我们捕获全局通知,配合`org.springframework.web.bind.annotation.ExceptionHandler`拦截异常,并对异常做自定义处理,然后响应请求。
* 2.添加代码
```java
@RestControllerAdvice
@Slf4j
public class GlobalExceptionHandler {
/**
* 处理参数校验异常
*/
@ExceptionHandler(MethodArgumentNotValidException.class)
public Response validExceptionHandler(MethodArgumentNotValidException e) {
print(e);
StringBuilder buf = new StringBuilder();
for (ObjectError error : e.getBindingResult().getAllErrors()) {
buf.append(",").append(error.getDefaultMessage());
}
return Response.error(buf.length() > 0 ? buf.substring(1) : "参数校验失败");
}
/**
* 处理Exceptionn异常
* @param e
* @return
*/
@ExceptionHandler(Exception.class)
public Response handleException(Exception e) {
print(e);
String message = e.getMessage();
if (null == message || "".equals(message)) {
message = e.getClass().getSimpleName();
}
return Response.error(message);
}
private void print(Exception e) {
log.error(" Exception : {}", e.getClass().getSimpleName(), e);
}
}
```
参数校验不通过,会抛出`MethodArgumentNotValidException`异常,我们通过添加`@ExceptionHandler(MethodArgumentNotValidException.class)`注解,即可以对参数校验异常进行处理
* 3.启动服务,访问 http://localhost:8080/check2
```json
//请求参数:
{
"c": null,
"d": null
}
//返回结果:
{
"code": 1000,
"message": "字段d允许取值为:x,y,字段c长度在1到5个字符之间",
"data": null
}
```
* 码云 https://gitee.com/hweiyu/spring-boot-validator.git