@Validated或@Valid参数注解校验、自定义手机号注解检验及优雅统一异常处理


1.@Validated和@Valid的区别和使用注意事项

1.1来源

@Validated :是Spring框架特有的注解,属于Spring的一部分,也是JSR 303的一个变种。它提供了一些
@Valid 所没有的额外功能,比如分组验证。@Valid:Java EE提供的标准注解,它是JSR 303规范的一部分,主要用于Hibernate Validation等场景。


1.2注解位置

@Validated : 用在类、方法和方法参数上,但不能用于成员属性。
@Valid:可以用在方法、构造函数、方法参数和成员属性上。


1.3是否支持分组

@Validated :支持分组验证,可以更细致地控制验证过程。此外,由于它是Spring专有的,因此可以更好地与Spring的其他功能(如Spring的依赖注入)集成。
@Valid:主要支持标准的Bean验证功能,不支持分组验证。 嵌套验证


1.4是否支持嵌套校验

@Validated :不支持嵌套验证。
@Valid:支持嵌套验证,可以嵌套验证对象内部的属性。


2.集成依赖

以下依赖推荐使用spring-boot-starter-validation,可以跟springBoot更好的集成
         <dependency>      <groupId>javax.validationgroupId>      <artifactId>validation-apiartifactId>      <version>版本号version>    dependency>
<dependency> <groupId>org.springframework.bootgroupId> <artifactId>spring-boot-starter-webartifactId> <version>xxxx.RELEASEversion> dependency>
<dependency> <groupId>org.springframework.bootgroupId> <artifactId>spring-boot-starter-validationartifactId> dependency>


3.@Valid常用注解


4.@Validated常用注解

4.自定手机号注解校验

4.1引入依赖

        <dependency>            <groupId>cn.hutoolgroupId>            <artifactId>hutool-allartifactId>            <version>5.8.8version>        dependency>

4.2实现代码

@Phone注解类

package xxxxx.validator;
import javax.validation.Constraint;import javax.validation.Payload;import java.lang.annotation.Documented;import java.lang.annotation.ElementType;import java.lang.annotation.Retention;import java.lang.annotation.RetentionPolicy;import java.lang.annotation.Target;
@Target({ElementType.FIELD, ElementType.METHOD})@Retention(RetentionPolicy.RUNTIME)@Documented// 指定约束处理器,也就是手机号格式验证是哪个类来做校验@Constraint(validatedBy = {PhoneValidator.class})public @interface Phone {
//这里不采用正则表达式做手机号的格式配置 //String pattern() default "^(?:(?:\\+|00)86)?1\\d{10}$";
String message() default "手机号格式不正确";
// groups用来指定分组,可以让校验采取不同的机制,当前默认未指定任何分组机制,默认每次都要进行校验 Class[] groups() default {};
Class[] payload() default {};
}

PhoneValidator类

package xxx.validator;
import cn.hutool.core.lang.Validator;
import javax.validation.ConstraintValidator;import javax.validation.ConstraintValidatorContext;import java.util.Objects;
/** * 校验处理器:做手机号码格式验证的核心类 */public class PhoneValidator implements ConstraintValidator<Phone, String> {
// 注解对象 private Phone phone;
// 初始化【Phone】对象 @Override public void initialize(Phone constraintAnnotation) { phone = constraintAnnotation; }
@Override public boolean isValid(String value, ConstraintValidatorContext context) { //获取【Phone】对象的手机格式验证表达式 //String pattern = phone.pattern(); //Pattern compile = Pattern.compile(pattern); //Matcher matcher = compile.matcher(value); // return matcher.matches(); if (Objects.isNull(value)) { throw new RuntimeException("手机号字段不为空"); } boolean isMobile = Validator.isMobile(value); return isMobile; }
}
这里使用hutool工具包里面的Validator.isMobile(value)方法实现一个手机号注解校验,不用自己去写正则表达是,使用hutool的更全面优雅,也可以使用hutool工具包里面的Validator.isEmail(value)方法实现一个自定义注解校验邮箱的,因为@Validated和@Valid的依赖中有实现邮箱注解校验的注解了,所以我们不需要自己去写一个邮箱注解校验。


5.优雅统一异常处理

GlobalExceptionHandler类

package xxxxx.handler;
import cn.dev33.satoken.exception.NotPermissionException;import cn.hutool.core.collection.CollectionUtil;import cn.hutool.core.convert.Convert;import xxxxx.RestResponse;import com.fasterxml.jackson.core.JsonProcessingException;import com.fasterxml.jackson.databind.JsonMappingException;import lombok.extern.slf4j.Slf4j;import org.springframework.core.MethodParameter;import org.springframework.http.MediaType;import org.springframework.http.converter.HttpMessageConverter;import org.springframework.http.converter.HttpMessageNotReadableException;import org.springframework.http.server.ServerHttpRequest;import org.springframework.http.server.ServerHttpResponse;import org.springframework.validation.BindException;import org.springframework.validation.FieldError;import org.springframework.web.HttpRequestMethodNotSupportedException;import org.springframework.web.bind.MethodArgumentNotValidException;import org.springframework.web.bind.MissingServletRequestParameterException;import org.springframework.web.bind.annotation.ExceptionHandler;import org.springframework.web.bind.annotation.RestControllerAdvice;import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;
import javax.validation.ConstraintViolation;import javax.validation.ConstraintViolationException;import javax.validation.ValidationException;import java.time.DateTimeException;import java.util.ArrayList;import java.util.HashMap;import java.util.List;import java.util.Map;import java.util.Objects;import java.util.Set;
/** * 全局异统一常处理 * */@Slf4j@RestControllerAdvicepublic class GlobalExceptionHandler implements ResponseBodyAdvice<Object> { private static final Integer STATUS_404 = 404; public static final String ERROR_MSG_404 = "接口地址不存在";
@ExceptionHandler(Exception.class) public RestResponse exceptionHandler(Exception e) { if (e instanceof MissingServletRequestParameterException) { //URL请求参数缺失异常处理带?xxx1=xxx1 & xxx2=xxx2 MissingServletRequestParameterException m1 = (MissingServletRequestParameterException) e; return RestResponse.fail("请求参数 " + m1.getParameterName() + " 不能为空"); } if (e instanceof ConstraintViolationException) { //@Valid参数校验异常处理 ConstraintViolationException e2 = (ConstraintViolationException) e; List<String> s = new ArrayList<>(); Set> constraintViolations = e2.getConstraintViolations(); for (ConstraintViolation c : constraintViolations) { String message = c.getMessage(); s.add(message); } if (CollectionUtil.isNotEmpty(s)) { return RestResponse.fail(s); } } if (e instanceof MethodArgumentNotValidException) { //@Validated参数校验异常处理 //嵌套对象字段校验 MethodArgumentNotValidException e3 = (MethodArgumentNotValidException) e; List fieldErrors = e3.getBindingResult().getFieldErrors(); if (CollectionUtil.isNotEmpty(fieldErrors)) { Map<String, Object> validError = this.getValidError(fieldErrors); return RestResponse.fail(validError); } } if (e instanceof BindException) { //POST请求@RequestBody/@Validated参数绑定校验异常处理 BindException e4 = (BindException) e; List fieldErrors = e4.getBindingResult().getFieldErrors(); if (CollectionUtil.isNotEmpty(fieldErrors)) { Map<String, Object> validError = this.getValidError(fieldErrors); return RestResponse.fail(validError); } } if (e instanceof HttpRequestMethodNotSupportedException) { //捕获请求方法异常:比如post接口使用了get HttpRequestMethodNotSupportedException e5 = (HttpRequestMethodNotSupportedException) e; String method = e5.getMethod(); return RestResponse.fail(method + "请求方法不被允许"); } if (e instanceof HttpMessageNotReadableException) { HttpMessageNotReadableException e5 = (HttpMessageNotReadableException) e; Throwable rootCause = e5.getRootCause(); //校验参数是否多余,导致不能识别 if (rootCause instanceof JsonProcessingException) { JsonMappingException mappingException = (JsonMappingException) rootCause; String fieldName = mappingException.getPath().get(0).getFieldName(); return RestResponse.fail("请求body中参数 " + fieldName + " 不能识别"); } //校验日期字段转换是否异常 if (rootCause instanceof DateTimeException) { return RestResponse.fail("请求body中日期转换异常"); } return RestResponse.fail("请求body不为空"); } if (e instanceof ValidationException) { ValidationException e6 = (ValidationException) e; Throwable cause = e6.getCause(); if (Objects.nonNull(cause)) { return RestResponse.fail(cause.getMessage()); } return RestResponse.fail("自定义字段校验注解处理异常"); } //上篇文章的sa-token的异常 if (e instanceof NotPermissionException) { NotPermissionException e7 = (NotPermissionException) e; log.error("NotPermissionException.msg:{}", e7.getMessage()); return RestResponse.fail("没有此权限!"); } //可以根据业务拓展业务异常 //也可以处理其它异常 return RestResponse.fail(e.getMessage()); }
/** * 获取校验错误信息 */ private Map<String, Object> getValidError(List fieldErrors) { Map<String, Object> result = new HashMap<>(16); for (FieldError error : fieldErrors) { result.put(error.getField(), error.getDefaultMessage()); } return result; }
// 决定是否执行beforeBodyWrite()方法 @Override public boolean supports(MethodParameter methodParameter, Classextends HttpMessageConverter> aClass) { return true; }
@Override public Object beforeBodyWrite(Object o, MethodParameter methodParameter, MediaType mediaType, Classextends HttpMessageConverter> aClass, ServerHttpRequest serverHttpRequest, ServerHttpResponse serverHttpResponse) { if (o == null) { return RestResponse.fail(); } //String类型需要特殊处理 手动转为json字符串 if (o instanceof String) { return RestResponse.success(o); } if (o instanceof RestResponse) { return o; } //boolean类型 返回对应的成功或失败 if (o instanceof Boolean) { return RestResponse.success(o); } //404时 返回特定信息 404也可以重写BasicErrorController if (is404(o)) { return RestResponse.fail(ERROR_MSG_404 + ":" + STATUS_404); } return o; }
private boolean is404(Object o) { if (o instanceof Map) { Map<String, Object> map = Convert.toMap(String.class, Object.class, o); Integer status = Convert.toInt(map.get("status")); return STATUS_404.equals(status); } return false; }}


6.总结

使用@Validated或@Valid对controller接口的参数或controller中body的参数做检验可以让代码更整洁工整,不至于写很多if前置参数校验判断逻辑,在配和上优雅全局统一异常处理,使用本文的套路可以代码更优雅简洁工整清秀,代码可读性高和可维护性强,使开发人员更加专注于业务,我的分享到此结束了,希望对你有所启发和帮助,请一键三连,么么么哒!

请使用浏览器的分享功能分享到微信等