Bean Validation是Java中的一项标准,它通过一些注解表达了对实体的限制规则。通过提出了一些API和扩展性的规范,这个规范是没有提供具体实现的,希望能够Constrain once, validate everywhere。现在它已经发展到了2.0,兼容Java8。
hibernate validation实现了Bean Validation标准,里面还增加了一些注解,在程序中引入它我们就可以直接使用。
Spring MVC也支持Bean Validation,它对hibernate validation进行了二次封装,添加了自动校验,并将校验信息封装进了特定的BindingResult类中,在SpringBoot中我们可以添加implementation('org.springframework.boot:spring-boot-starter-validation')引入这个库,实现对bean的校验功能。
导入pom依赖
org.springframework.boot
spring-boot-starter-validation
参数校验失败后,会抛出异常,我们配置全局异常进行拦截,自定义返回信息
package com.example.config;
import lombok.extern.slf4j.Slf4j;
import org.springframework.validation.BindException;
import org.springframework.validation.BindingResult;
import org.springframework.validation.FieldError;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import javax.validation.ConstraintViolation;
import javax.validation.ConstraintViolationException;
import java.util.HashMap;
import java.util.Map;
/**
* 全局异常处理
*
* @Author: hanYong
* @CreateTime: 2019-09-19
*/
@RestControllerAdvice
@Slf4j
public class HandleException {
/**
* 处理请求参数格式错误
*
* @param e 实体中校验失败后抛出的异常
* @return Object 同步返回500,异步返回JSON
*/
@ExceptionHandler(BindException.class)
public Object handleBindException(BindException e) {
BindingResult bindingResult = e.getBindingResult();
String defaultMessage = bindingResult.getAllErrors().get(0).getDefaultMessage();
return result(defaultMessage);
}
/**
* 处理请求参数格式错误
*
* @param e @RequestBody上validate失败后抛出的异常
* @return Object 同步返回500,异步返回JSON
*/
@ExceptionHandler(MethodArgumentNotValidException.class)
public Object handleMethodArgumentNotValidException(MethodArgumentNotValidException e) {
FieldError fieldError = e.getBindingResult().getFieldError();
return result(fieldError.getDefaultMessage());
}
/**
* 处理请求参数格式错误
*
* @param e @RequestParam上validate失败后抛出的此异常
* @return Object 同步返回500,异步返回JSON
*/
@ExceptionHandler(ConstraintViolationException.class)
public Object handleConstraintViolationException(ConstraintViolationException e) {
ConstraintViolation> constraintViolation = e.getConstraintViolations().iterator().next();
return result(constraintViolation.getMessage());
}
private Map result(String errorMsg) {
Map result = new HashMap<>();
result.put("code", "401");
result.put("msg", errorMsg);
return result;
}
}
@Validated
@Controller
public class IndexController {
/**
* 提交登陆信息
*
* @param username 用户名
* @param password 密码
* @param captcha 验证码
* @return 结果
*/
@PostMapping("/login1")
@ResponseBody
public Map submitLogin1(String username, @NotNull(message = "密码不能为空")String password, String captcha) {
Map resultMap = new HashMap<>(16);
resultMap.put("code", 200);
resultMap.put("msg", "登录成功");
resultMap.put("username", username);
resultMap.put("password", password);
resultMap.put("captcha", captcha);
return resultMap;
}
}
@Data
public class LoginUser {
@NotNull(message = "用户名不能为空")
private String username;
private String password;
private String captcha;
}
对实体进行校验的时候需要加上@Validated
/**
* 提交登陆信息
*
* @return 结果
*/
@PostMapping("/login3")
@ResponseBody
public Map submitLogin3(@Validated LoginUser loginUser) {
Map resultMap = new HashMap<>(16);
resultMap.put("code", 200);
resultMap.put("msg", "登录成功");
resultMap.put("username", loginUser.getUsername());
resultMap.put("password", loginUser.getPassword());
resultMap.put("captcha", loginUser.getCaptcha());
return resultMap;
}
注解 | 详细信息 |
@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(value) | 被注释的元素必须符合指定的正则表达式 |
注解 | 详细信息 |
被注释的元素必须是电子邮箱地址 | |
@Length | 被注释的字符串的大小必须在指定的范围内 |
@NotEmpty | 被注释的字符串的必须非空 |
@Range | 被注释的元素必须在合适的范围内 |
@NotBlank | 验证字符串非null,且长度必须大于0 |
@NotNull 适用于任何类型被注解的元素必须不能与NULL
@NotEmpty 适用于String Map或者数组不能为Null且长度必须大于0
@NotBlank 只能用于String上面 不能为null,调用trim()后,长度必须大于0
自定义一个equals注解
@Documented
@Target({ElementType.PARAMETER, ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = EqualsValidator.class)
public @interface Equals {
/**
* 校验值
*/
String val() default "";
/**
* 校验失败的信息
*/
String message() default "";
/**
* 分组类别
*/
Class>[] groups() default {};
/**
* 约束条件的严重级别
*/
Class extends Payload>[] payload() default {};
}
实现ConstraintValidator,添加校验规则
import org.springframework.util.ObjectUtils;
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
import java.util.Objects;
public class EqualsValidator implements ConstraintValidator {
private String value2;
/**
* Initializes the validator in preparation for
* {@link #isValid(String, ConstraintValidatorContext)} calls.
* The constraint annotation for a given constraint declaration
* is passed.
*
* This method is guaranteed to be called before any use of this instance for
* validation.
*
* The default implementation is a no-op.
*
* @param parameters annotation instance for a given constraint declaration
*/
@Override
public void initialize(Equals parameters) {
this.value2 = parameters.val();
}
/**
* Implements the validation logic.
* The state of {@code value} must not be altered.
*
* This method can be accessed concurrently, thread-safety must be ensured
* by the implementation.
*
* @param value object to validate
* @param context context in which the constraint is evaluated
* @return {@code false} if {@code value} does not pass the constraint
*/
@Override
public boolean isValid(String value, ConstraintValidatorContext context) {
return Objects.equals(value, value2);
}
}
如果需要设置约束条件的严重级别,可以在创建下面类
/**
* Severity
* 约束条件的严重级别
*
* @Author: hanYong
* @CreateTime: 2020-05-20
*/
public class Severity {
public static class Warn implements Payload {
}
public static class Error implements Payload {
}
}
进行测试
/**
* 提交登陆信息
*
* @param username 用户名
* @param password 密码
* @param captcha 验证码
* @return 结果
*/
@PostMapping("/login1")
@ResponseBody
public Map submitLogin1(@Equals(val = "zhangsan", message = "账号错误")String username, String password, String captcha) {
Map resultMap = new HashMap<>(16);
resultMap.put("code", 200);
resultMap.put("msg", "登录成功");
resultMap.put("username", username);
resultMap.put("password", password);
resultMap.put("captcha", captcha);
return resultMap;
}
在实际开发中,会遇到同一个实体,多个接口使用,每个接口的校验规则也不一样,或者校验顺序不一样的时候,我们就可以使用分组校验
创建一个分组类
/**
* Group
*
* @Author: hanYong
* @CreateTime: 2020-05-19
*/
public interface Group {
@GroupSequence({
Create.One.class,
Create.Two.class,
Create.Three.class,
})
interface Create {
interface One {
}
interface Two {
}
interface Three {
}
}
@GroupSequence({
Modify.One.class,
Modify.Two.class,
Modify.Three.class,
})
interface Modify {
interface One {
}
interface Two {
}
interface Three {
}
}
}
按照顺序进行分组校验,如果有一个校验不通过,就直接返回校验结果,不在进行后面的校验
新增的时候校验用户名和密码
修改的时候校验用户id、用户名、密码
@Data
public class SysUser {
@NotNull(message = "用户id不能为空",
groups = {
Group.Modify.class, Group.Modify.One.class
}
)
private Integer id;
@NotNull(message = "用户名不能为空",
groups = {
Group.Create.class, Group.Create.One.class,
Group.Modify.class, Group.Modify.Two.class
}
)
private String username;
@NotNull(message = "用户密码不能为空",
groups = {
Group.Create.class, Group.Create.Two.class,
Group.Modify.class, Group.Modify.Three.class
}
)
private String password;
}
编写测试接口
/**
* 新增用户
*
* @param sysUser 用户数据
* @return 结果
*/
@PostMapping("/insert")
@ResponseBody
public Map insert(@Validated(Group.Create.class) SysUser sysUser) {
Map resultMap = new HashMap<>(16);
resultMap.put("code", 200);
resultMap.put("msg", "新增成功");
resultMap.put("modifyRoleSet", sysUser.toString());
return resultMap;
}
/**
* 修改用户
*
* @param sysUser 用户数据
* @return 结果
*/
@PostMapping("/modify")
@ResponseBody
public Map modify(@Validated(Group.Modify.class) SysUser sysUser) {
Map resultMap = new HashMap<>(16);
resultMap.put("code", 200);
resultMap.put("msg", "修改成功");
resultMap.put("modifyRoleSet", sysUser.toString());
return resultMap;
}
之前我们都是在Controller进行参数校验的,实际开发中,我们在其它地方也会涉及到校验,下面是手动调用校验方法,获取校验结果的示例
新增一个Service类
import com.alibaba.druid.util.StringUtils;
import com.example.config.valid.Group;
import com.example.domain.SysUser;
import org.springframework.stereotype.Service;
import javax.validation.ConstraintViolation;
import javax.validation.Validation;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
/**
* UserService
*
* @Author: HanYong
* @CreateTime: 2021-06-05
*/
@Service
public class UserService {
public Map insert(SysUser sysUser){
String errorMsg = Validation.buildDefaultValidatorFactory()
.getValidator()
.validate(sysUser, Group.Create.class)
.stream()
.findFirst()
.map(ConstraintViolation::getMessage).orElse(null);
Map resultMap = new HashMap<>(16);
if(StringUtils.isEmpty(errorMsg)){
resultMap.put("code", 200);
resultMap.put("msg", "新增成功");
} else {
resultMap.put("code", 401);
resultMap.put("msg", errorMsg);
}
resultMap.put("sysUser", sysUser.toString());
return resultMap;
}
}
在Controller中添加下面代码
@Autowired
private UserService userService;
/**
* 新增用户
*
* @param sysUser 用户数据
* @return 结果
*/
@PostMapping("/insert")
@ResponseBody
public Map insert(SysUser sysUser) {
return userService.insert(sysUser);
}
进行测试