一、相关依赖
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-validator</artifactId>
<version>6.0.10.Final</version>
</dependency>
@Validated 是 Spring 框架提供的注解,是对 @Valid 的扩展,提供了更灵活的分组验证功能。
二、简单实例
添加一个普通的接口信息,限制传入的参数是 id不能小于 10。
@Validated
@RestController
@RequestMapping("/example")
public class ExampleController {
/**
* @param id id数不能小于10 @RequestParam类型的参数需要在Controller上增加@Validated
* @return
*/
@RequestMapping(value = "/info",method = RequestMethod.GET)
public String test(@Min(value = 10, message = "id最小只能是10") @RequestParam("id")
Integer id){
return "恭喜你拿到参数了";
}
}
在全局异常拦截中添加验证异常的处理
@Slf4j
@ControllerAdvice
@Component
public class GlobalExceptionHandler {
@ExceptionHandler
@ResponseBody
@ResponseStatus(HttpStatus.BAD_REQUEST)
public String handle(ConstraintViolationException exception, HttpServletRequest request) {
Set<ConstraintViolation<?>> violations = exception.getConstraintViolations();
StringBuffer errorInfo = new StringBuffer();
for (ConstraintViolation<?> item : violations) {
/**打印验证不通过的信息*/
errorInfo.append(item.getMessage());
errorInfo.append(",");
}
log.error("{}接口参数验证失败,内容如下:{}",request.getRequestURI(),errorInfo.toString());
return "您的请求失败,参数验证失败,失败信息如下:"+ errorInfo.toString();
}
}
三、按照vo的验证
添加一个 vo 的实体信息
@Data
public class ExampleVo {
@NotBlank(message = "用户名不能为空")
private String userName;
@Range(min = 18,max = 60,message = "只能填报年龄在18~60岁的")
private String age;
}
添加一个 POST 请求的接口
/**
* @param vo 按照vo的验证
* @return
*/
@RequestMapping(value = "/info1",method = RequestMethod.POST)
public String test1(@Valid @RequestBody ExampleVo vo){
return "success";
}
在全局异常拦截中添加验证处理的结果
@ResponseBody
@ResponseStatus(HttpStatus.BAD_REQUEST)
@ExceptionHandler(MethodArgumentNotValidException.class)
public String handle(MethodArgumentNotValidException exception,HttpServletRequest request) {
StringBuffer errorInfo=new StringBuffer();
List<ObjectError> errors = exception.getBindingResult().getAllErrors();
for(int i=0;i<errors.size();i++){
errorInfo.append(errors.get(i).getDefaultMessage()+",");
}
log.error("{},接口参数验证失败:{}",request,errorInfo.toString());
return "您的请求失败,参数验证失败,失败信息如下:"+errorInfo.toString();
}
四、自定义注解
自定义注解实现,vo 中的属性必须符合枚举类中的枚举。
4.1 添加自定义注解
@Target({ElementType.METHOD, ElementType.FIELD, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = EnumCheckValidator.class)
public @interface EnumCheck {
/**
* 是否必填 默认是必填的
* @return
*/
boolean required() default true;
/**
* 验证失败的消息
* @return
*/
String message() default "枚举的验证失败";
/**
* 分组的内容
* @return
*/
Class<?>[] groups() default {};
/**
* 错误验证的级别
* @return
*/
Class<? extends Payload>[] payload() default {};
/**
* 枚举的Class
* @return
*/
Class<? extends Enum<?>> enumClass();
/**
* 枚举中的验证方法
* @return
*/
String enumMethod() default "validation";
}
4.2 注解的校验逻辑实现类
public class EnumCheckValidator implements ConstraintValidator<EnumCheck,Object> {
private EnumCheck enumCheck;
@Override
public void initialize(EnumCheck enumCheck) {
this.enumCheck =enumCheck;
}
@Override
public boolean isValid(Object value, ConstraintValidatorContext constraintValidatorContext) {
// 注解表明为必选项 则不允许为空,否则可以为空
if (value == null) {
return this.enumCheck.required()?false:true;
}
//最终的返回结果
Boolean result=Boolean.FALSE;
// 获取 参数的数据类型
Class<?> valueClass = value.getClass();
try {
Method method = this.enumCheck.enumClass().getMethod(this.enumCheck.enumMethod(), valueClass);
result = (Boolean)method.invoke(this.enumCheck.enumClass(), value);
result= result == null ? false : result;
//所有异常需要在开发测试阶段发现完毕
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}finally {
return result;
}
}
}
4.3 枚举类
public enum Sex{
MAN("男",1),WOMAN("女",2);
private String label;
private Integer value;
public String getLabel() {
return label;
}
public void setLabel(String label) {
this.label = label;
}
public Integer getValue() {
return value;
}
public void setValue(Integer value) {
this.value = value;
}
Sex(String label, int value) {
this.label = label;
this.value = value;
}
/**
* 判断值是否满足枚举中的value
* @param value
* @return
*/
public static boolean validation(Integer value){
for(Sex s:Sex.values()){
if(Objects.equals(s.getValue(),value)){
return true;
}
}
return false;
}
}
4.4 使用
@EnumCheck(message = "只能选男:1或女:2",enumClass = Sex.class)
private Integer sex;
4.5 基于已有注解自定义注解
import jakarta.validation.Constraint;
import jakarta.validation.Payload;
import jakarta.validation.constraints.Pattern;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import java.lang.annotation.ElementType;
import java.lang.annotation.RetentionPolicy;
@Documented
@Constraint(validatedBy = {})
@Target({ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Pattern(regexp = "^[a-zA-Z0-9]+$", message = "用户名只能包含字母和数字")
public @interface Username {
String message() default "用户名只能包含字母和数字";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
class User {
@Username
private String username;
// 省略getter和setter方法
}
五、分组验证
@Data
public class ExampleVo {
@NotNull(message = "主键不允许为空",groups = ValidGroupA.class)
private Integer id;
@NotBlank(message = "用户名不能为空",groups = Default.class)
private String userName;
@Range(min = 18,max = 60,message = "只能填报年龄在18~60岁的",groups = Default.class)
private String age;
@EnumCheck(message = "只能选男:1或女:2",enumClass = Sex.class,groups = Default.class)
private Integer sex;
}
@RequestMapping(value = "/info1",method = RequestMethod.POST)
public String test1(@Validated({ValidGroupA.class,Default.class}) @RequestBody ExampleVo vo) {
return "success";
}
六、字段嵌套验证
在 User 类中,@Valid 注解在 address 字段上,表示对 Address 类的字段进行嵌套验证
import jakarta.validation.Valid;
import jakarta.validation.constraints.NotNull;
class Address {
@NotBlank(message = "城市不能为空")
private String city;
@NotBlank(message = "街道不能为空")
private String street;
// 省略getter和setter方法
}
class User {
@NotBlank(message = "用户名不能为空")
private String username;
@Valid
@NotNull(message = "地址不能为空")
private Address address;
// 省略getter和setter方法
}
七、手动进行验证
7.1 方法一
@Service
public class UserServiceImpl implements UserService {
@Autowired
private Validator validator;
@Override
public void register(UserDto userDto) {
// 手动验证
Set<ConstraintViolation<UserDto>> violations = validator.validate(userDto);
if (!violations.isEmpty()) {
throw new ConstraintViolationException(violations);
}
// 业务逻辑处理
System.out.println("注册用户: " + userDto.getUsername());
}
}
7.2 方法二
import org.springframework.validation.BeanPropertyBindingResult;
import org.springframework.validation.ValidationUtils;
import org.springframework.validation.Validator;
public class MyController {
@Autowired
private Validator validator;
public void validateManually(MyObject object) {
BeanPropertyBindingResult errors = new BeanPropertyBindingResult(object, "object");
validator.validate(object, errors);
if (errors.hasErrors()) {
// 验证失败
System.out.println("验证失败");
} else {
// 验证通过
System.out.println("验证通过");
}
}
}
八、配合全局异常使用
@ControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(ConstraintViolationException.class)
public ResponseEntity<Object> handleConstraintViolation(ConstraintViolationException ex) {
List<String> errors = ex.getConstraintViolations().stream()
.map(violation -> violation.getPropertyPath() + ": " + violation.getMessage())
.collect(Collectors.toList());
return new ResponseEntity<>(errors, HttpStatus.BAD_REQUEST);
}
}