1. validation bean
validation bean 是基于JSR-303标准开发出来的,使用注解方式实现,及其方便,但是这只是一个接口,没有具体实现.Hibernate Validator是一个hibernate独立的包,可以直接引用,他实现了validation bean同时有做了扩展,比较强大.
2. 在Spring中如何使用
2.1 引入依赖
<!--参数校验-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
2.2 常用注解
JSR提供的校验注解:
@Null 被注释的元素必须为 null
@NotNull 被注释的元素必须不为 null
@AssertTrue 被注释的元素必须为 true
@AssertFalse 被注释的元素必须为 false
@Min(value) 被注释的元素必须是一个数字,其值必须大于等于指定的最小值
@Max(value) 被注释的元素必须是一个数字,其值必须小于等于指定的最大值
@DecimalMin(value) 被注释的元素必须是一个数字,其值必须大于等于指定的最小值
@DecimalMax(value) 被注释的元素必须是一个数字,其值必须小于等于指定的最大值
@Size(max=, min=) 被注释的元素的大小必须在指定的范围内
@Digits (integer, fraction) 被注释的元素必须是一个数字,其值必须在可接受的范围内
@Past 被注释的元素必须是一个过去的日期
@Future 被注释的元素必须是一个将来的日期
@Pattern(regex=,flag=) 被注释的元素必须符合指定的正则表达式
Hibernate Validator提供的校验注解:
@NotBlank(message =) 验证字符串非null,且trim后长度必须大于0
@Email 被注释的元素必须是电子邮箱地址
@Length(min=,max=) 被注释的字符串的大小必须在指定的范围内
@NotEmpty 被注释的字符串的必须非空
@Range(min=,max=,message=) 被注释的元素必须在合适的范围内
注意:
@NotNull 适用于任何类型被注解的元素必须不能与NULL
@NotEmpty 适用于String Map或者数组不能为Null且长度必须大于0
@NotBlank 只能用于String上面 不能为null,调用trim()后,长度必须大于0
2.3 简单使用
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import javax.validation.constraints.Email;
import javax.validation.constraints.NotBlank;
/**
* @Author: Sivan
* @Date: 2020/12/24 0:13
*/
@Validated
@RestController
@RequestMapping("/valid")
public class ValidController {
/**
* 在Controller直接使用验证注解
* 需要在类上添加@Validated
*/
@GetMapping("/email")
public String validEmail(@NotBlank(message = "邮箱不能为空") @Email(message = "邮箱格式不正确") String email) {
return email;
}
}
当处理GET请求时或只传入少量参数的时候,我们可能不会建一个bean来接收这些参数,就可以像上面这样直接在controller方法的参数中进行校验。
注意:这里一定要在方法所在的controller类上加入@Validated注解,不然没有任何效果。
这时会有这样一个错误:
javax.validation.ConstraintViolationException: validEmail.email: 邮箱不能为空
{
"timestamp": "2020-12-23T18:33:34.600+00:00",
"status": 500,
"error": "Internal Server Error",
"message": "",
"path": "/api/valid/email"
}
2.4 实体参数校验
创建一个接收参数的实体类:
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import org.hibernate.validator.constraints.Range;
import org.springframework.format.annotation.DateTimeFormat;
import javax.validation.constraints.Email;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
import java.util.Date;
/**
* @Author: Sivan
* @Date: 2020/12/23 12:51
*/
@Data
public class UserVo {
@NotBlank(message = "姓名不能为空")
private String name;
@NotNull(message = "年龄不能为空")
@Range(min = 10, max = 150, message = "年龄不正确")
private Integer age;
@NotBlank(message = "邮箱不能为空")
@Email(message = "邮箱格式不正确")
private String email;
@NotNull(message = "出生日期不能为空")
@DateTimeFormat(pattern = "yyyy-MM-dd")
private Date birthday;
}
使用:
import cn.sivan.api.model.vo.UserVo;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.validation.constraints.Email;
import javax.validation.constraints.NotBlank;
/**
* @Author: Sivan
* @Date: 2020/12/24 0:13
*/
@Validated
@RestController
@RequestMapping("/valid")
public class ValidController {
/**
* 在实体上使用验证注解
*/
@GetMapping("/user")
public UserVo validUser(@Validated UserVo userVo) {
return userVo;
}
}
2.5 嵌套使用
在原来的实体上添加一个新的属性,类型为AddressVo
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import org.hibernate.validator.constraints.Range;
import org.springframework.format.annotation.DateTimeFormat;
import javax.validation.constraints.Email;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
import java.util.Date;
/**
* @Author: Sivan
* @Date: 2020/12/23 12:51
*/
@Data
public class UserVo {
@NotBlank(message = "姓名不能为空")
private String name;
@NotNull(message = "年龄不能为空")
@Range(min = 10, max = 150, message = "年龄不正确")
private Integer age;
@NotBlank(message = "邮箱不能为空")
@Email(message = "邮箱格式不正确")
private String email;
@NotNull(message = "出生日期不能为空")
@DateTimeFormat(pattern = "yyyy-MM-dd")
private Date birthday;
@Valid
@NotEmpty(message = "请填写收货地址")
private AddressVo[] address;
}
import cn.sivan.api.core.annotation.Mobile;
import lombok.Data;
import javax.validation.constraints.NotBlank;
/**
* @Author: Sivan
* @Date: 2020/12/24 2:55
*/
@Data
public class AddressVo {
@NotBlank(message = "姓名不能为空")
private String name;
@Mobile(message = "联系方式不正确")
private String phone;
@NotBlank(message = "地址不能为空")
private String address;
}
注意:嵌套验证不要忘了添加@Valid
@Valid
@NotEmpty(message = "请填写收货地址")
private AddressVo[] address;
2.6 分组校验
分组校验,就是在不同的情况下,需要验证的情况不同,而你又不想为这点不同去建个新的类接收参数。例如:在新增用户时我们不需要校验id,但是在更新时我们需要校验id不能为空。
/**
* 分组校验
* @Author: Sivan
* @Date: 2020/12/24 2:26
*/
public interface ModifyUser {
}
import cn.sivan.api.model.interfaces.ModifyUser;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import org.hibernate.validator.constraints.Range;
import org.springframework.format.annotation.DateTimeFormat;
import javax.validation.Valid;
import javax.validation.constraints.Email;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;
import java.util.Date;
/**
* @Author: Sivan
* @Date: 2020/12/16 12:51
*/
@Data
public class UserVo {
@NotBlank(message = "userId不能为空", groups = ModifyUser.class)
private Integer userId;
@NotBlank(message = "姓名不能为空")
private String name;
@NotNull(message = "年龄不能为空")
@Range(min = 10, max = 150, message = "年龄不正确")
private Integer age;
@NotBlank(message = "邮箱不能为空")
@Email(message = "邮箱格式不正确")
private String email;
@NotNull(message = "出生日期不能为空")
@DateTimeFormat(pattern = "yyyy-MM-dd")
private Date birthday;
@Valid
@NotEmpty(message = "请填写收货地址")
private AddressVo[] address;
}
使用:
import cn.sivan.api.model.interfaces.ModifyUser;
import cn.sivan.api.model.vo.UserVo;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import javax.validation.constraints.Email;
import javax.validation.constraints.NotBlank;
/**
* @Author: Sivan
* @Date: 2020/12/24 0:13
*/
@RestController
@RequestMapping("/valid")
public class ValidController {
/**
* 嵌套验证
*/
@PostMapping("/insert")
public UserVo insert(@Validated @RequestBody UserVo userVo) {
return userVo;
}
/**
* 分组校验
*/
@PostMapping("/modify")
public UserVo validUser2(@Validated({ModifyUser.class}) @RequestBody UserVo userVo) {
return userVo;
}
}
3. 自定义校验注解
创建一个验证手机号码的注解。
import javax.validation.Constraint;
import javax.validation.Payload;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import cn.sivan.api.core.annotation.impl.*;
import static java.lang.annotation.ElementType.FIELD;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
/**
* 自定义校验手机号码注解
* @Author: Sivan
* @Date: 2020/11/16 1:02
*/
@Documented
@Constraint(validatedBy = {MobileValidatorImpl.class})
@Target({FIELD})
@Retention(RUNTIME)
public @interface Mobile {
String message() default "手机号码格式错误";
String regexp() default "^1[345678]\\d{9}$";
boolean required() default true;
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
指定其实现类:
@Constraint(validatedBy = {MobileValidatorImpl.class})
import cn.sivan.api.core.annotation.Mobile;
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
/**
* 自定义校验手机号码注解-实现类
* @Author: Sivan
* @Date: 2020/11/16 1:03
*/
public class MobileValidatorImpl implements ConstraintValidator<Mobile, String> {
private String regexp;
@Override
public void initialize(Mobile constraintAnnotation) {
regexp = constraintAnnotation.regexp();
}
@Override
public boolean isValid(String value, ConstraintValidatorContext context) {
if (value == null) {
return true;
}
if (value.matches(regexp)) {
return true;
}
return false;
}
}
4. 捕捉异常
当参数校验异常的时候,该统一异常处理类在控制台打印信息的同时把bad request的字符串和HttpStatus.BAD_REQUEST所表示的状态码400返回给调用方(用@ResponseBody注解实现,表示该方法的返回结果直接写入HTTP response body 中)。其中:
@ControllerAdvice:控制器增强,使@ExceptionHandler、@InitBinder、@ModelAttribute注解的方法应用到所有的 @RequestMapping注解的方法。
@ExceptionHandler:异常处理器,此注解的作用是当出现其定义的异常时进行处理的方法,此例中处理ValidationException异常。
如果多个请求参数都校验失败,则遇到第一个校验失败就抛出异常,接下来的异常参数不做校验,配置如下:
import org.hibernate.validator.HibernateValidator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.validation.beanvalidation.MethodValidationPostProcessor;
import javax.validation.Validation;
import javax.validation.Validator;
import javax.validation.ValidatorFactory;
/**
* @Author: Sivan
* @Date: 2020/11/5 0:00
*/
@Configuration
public class WebConfig {
@Bean
public Validator validator() {
//failFast的意思只要出现校验失败的情况,就立即结束校验,不再进行后续的校验。
ValidatorFactory validatorFactory = Validation.byProvider(HibernateValidator.class)
.configure()
.failFast(true)
.buildValidatorFactory();
return validatorFactory.getValidator();
}
@Bean
public MethodValidationPostProcessor methodValidationPostProcessor(Validator validator) {
MethodValidationPostProcessor methodValidationPostProcessor = new MethodValidationPostProcessor();
methodValidationPostProcessor.setValidator(validator);
return methodValidationPostProcessor;
}
}
import cn.sivan.api.core.exception.ApiAuthorizationException;
import cn.sivan.api.core.result.JsonResult;
import cn.sivan.api.core.result.ResultCode;
import org.springframework.context.support.DefaultMessageSourceResolvable;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.validation.BindException;
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.NoHandlerFoundException;
import javax.validation.ConstraintViolation;
import javax.validation.ConstraintViolationException;
import java.util.stream.Collectors;
/**
* 全局异常处理
* @Author: Sivan
* @Date: 2020/12/14 22:49
*/
@RestControllerAdvice
public class GlobalExceptionHandler {
/**
* 捕捉 404 异常
* #spring.mvc.throw-exception-if-no-handler-found=true
* #spring.resources.add-mappings=false
*/
@ExceptionHandler(value = NoHandlerFoundException.class)
public JsonResult noHandlerFoundException(NoHandlerFoundException e) {
return JsonResult.error(ResultCode.NOT_FOUNT);
}
/**
* 请求方式不允许
*/
@ExceptionHandler(value = HttpRequestMethodNotSupportedException.class)
public JsonResult httpRequestMethodNotSupportedException(HttpRequestMethodNotSupportedException e) {
return JsonResult.error(ResultCode.METHOD_NOT_ALLOWED);
}
/**
* 认证失败
*/
@ExceptionHandler(value = ApiAuthorizationException.class)
public JsonResult apiAuthorizationException(ApiAuthorizationException e) {
return JsonResult.error(ResultCode.UNAUTHORIZED);
}
/**
* 缺少请求参数或参数格式错误
* body为空或Json格式错误
*/
@ExceptionHandler({HttpMessageNotReadableException.class, MissingServletRequestParameterException.class})
public JsonResult apiAuthorizationException(HttpMessageNotReadableException e) {
return JsonResult.error(ResultCode.PARAMETER_FAILED.getCode(), e.getMessage());
}
/**
* 参数校验失败 @Validated UserVo userVo
*/
@ExceptionHandler(value = BindException.class)
public JsonResult BindExceptionHandler(BindException e) {
String message = e.getBindingResult().getAllErrors().stream().map(DefaultMessageSourceResolvable::getDefaultMessage).collect(Collectors.joining());
return JsonResult.error(ResultCode.PARAMETER_FAILED.getCode(), message);
}
/**
* 参数校验失败 @NotBlank @Email String email
*/
@ExceptionHandler(value = ConstraintViolationException.class)
public JsonResult constraintViolationExceptionHandler(ConstraintViolationException e) {
String message = e.getConstraintViolations().stream().map(ConstraintViolation::getMessage).collect(Collectors.joining());
return JsonResult.error(ResultCode.PARAMETER_FAILED.getCode(), message);
}
/**
* 参数校验失败 RequestBody
*/
@ExceptionHandler(value = MethodArgumentNotValidException.class)
public JsonResult methodArgumentNotValidExceptionHandler(MethodArgumentNotValidException e) {
String message = e.getBindingResult().getAllErrors().stream().map(DefaultMessageSourceResolvable::getDefaultMessage).collect(Collectors.joining());
return JsonResult.error(ResultCode.PARAMETER_FAILED.getCode(), message);
}
/**
* 未知的运行时异常
*/
@ExceptionHandler(value = RuntimeException.class)
public JsonResult runtimeException(Exception e) {
return JsonResult.error("运行时异常:" + e.getMessage());
}
/**
* 捕捉系统异常
*/
@ExceptionHandler(value = Exception.class)
public JsonResult exception(Exception e) {
return JsonResult.error("服务器错误,请联系管理员:" + e.getMessage());
}
}
{
"requestId": "20122404363388021008",
"detail": {
"path": "/api/valid/insert",
"body": {
"name": "宁川",
"age": 12455,
"email": "",
"birthday": "2020-12-24",
"addressVos": [
{
"name": "宁川",
"phone": "",
"address": "d"
}
]
},
"timestamp": 1608755793882,
"code": 412,
"message": "年龄不正确",
"data": null
}
}