# 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[] 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[] 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