引入maven依赖(可选)
如果我们的项目使用了Spring Boot,hibernate validator框架已经集成在 spring-boot-starter-web中,所以无需再添加其他依赖。如果不是Spring Boot项目,则需要添加如下依赖:
<dependency>
<groupId>org.hibernate.validator</groupId>
<artifactId>hibernate-validator</artifactId>
<version>6.0.8.Final</version>
</dependency>
常用注解说明
单参数校验
controller类上必须添加@Validated注解,如下所示:
@RestController
@RequestMapping("/user")
@Validated // 需要添加的注解
public class UserController {
// do something
方法参数前面加上注解即可
public Result deleteUser(@NotNull(message = "id不能为空") Long id) {
// do something
}
对象参数校验
对象参数校验使用时,需要先在对象的校验属性上添加注解,然后在Controller方法的对象参数前添加@Validated注解
public Result addUser(@RequestBody @Validated UserDto userDto) {
// do something
}
public class UserDto {
@NotBlank(message="名称不能为空")
private String name;
@NotNull(message="年龄不能为空")
private Integer age;
……
}
嵌套对象校验
如果需要校验的参数对象中还嵌套有一个对象属性,而该嵌套的对象属性也需要校验,那么就需要在该对象属性上增加@Valid注解
public class UserDto {
@NotNull
private Long id;
@NotBlank
private String name;
@NotNull
private Integer age;
@Valid
private Phone phone;
……
}
public class Phone {
@NotBlank
private String type;
@NotBlank
private String phoneNum;
}
在对象参数校验场景下,有一种特殊场景,同一个参数对象在不同的场景下有不同的校验规则。比如,在创建对象时不需要传入id字段(id字段是主键,由系统生成,不由用户指定),但是在修改对象时就必须要传入id字段。在这样的场景下就需要对注解进行分组。
1)组件默认有个分组Default.class, 所以我们可以再创建一个分组例如UpdateAction.class,如下所示:
public interface UpdateAction {
}
2)在参数类中需要校验的属性上,在注解中添加groups属性(表示Default.class默认情况下会校验name,age参数,而在UpdateAction.class情况下会校验id参数)
public class UserDto {
@NotNull(groups = {UpdateAction.class}, message = "id不能为空")
private Long id;
@NotBlank
private String name;
@NotNull
private Integer age;
……
}
3)在controller的方法中,在@Validated注解里指定哪种场景即可(没有指定就代表采用Default.class,采用其他分组则需要显示指定)。如下代码便表示在addUser()接口中按照默认情况进行参数校验,在updateUser()接口中按照默认情况和UpdateAction分组对参数进行共同校验。
public Result addUser(@Validated UserDto userDto) {
// do something
}
public Result updateUser(@Validated({Default.class, UpdateAction.class}) UserDto userDto) {
// do something
}
枚举校验
枚举校验hibernate validator并不支持。需要自己扩展实现。
1) 定义EnumChck注解,如下所示:
@Documented
@Constraint(validatedBy = EnumConstraintValidator.class)
@Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE })
@Retention(RUNTIME)
@Repeatable(EnumCheck.List.class)
public @interface EnumCheck {
String message() default "{javax.validation.constraints.EnumCheck.message}";
Class<?>[] groups() default { };
Class<? extends Payload>[] payload() default { };
/**
* 枚举类
*
*/
Class<? extends EnumValidator> clazz();
/**
* 调用的方法名称
*/
String method() default "getValue";
@Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE })
@Retention(RUNTIME)
@Documented
@interface List {
EnumCheck[] value();
}
}
2) 定义EnumValidator接口,让需要校验的枚举类实现其接口的getValue()方法,
public interface EnumValidator {
Object getValue();
}
3)自定义实现EnumConstraintValidator,需要实现ConstraintValidator接口,
public class EnumConstraintValidator implements ConstraintValidator<EnumCheck, Object> {
/**
* 注解对象
*/
private EnumCheck annotation;
/**
* 初始化方法
*
* @param constraintAnnotation 注解对象
*/
@Override
public void initialize(EnumCheck constraintAnnotation) {
this.annotation = constraintAnnotation;
}
@Override
public boolean isValid(Object value, ConstraintValidatorContext context) {
if (Objects.isNull(value)) {
return false;
}
Object[] enumConstants = annotation.clazz().getEnumConstants();
try {
Method method = annotation.clazz().getMethod(annotation.method());
for (Object enumConstant : enumConstants) {
if (value.equals(method.invoke(enumConstant))) {
return true;
}
}
} catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
throw new RuntimeException(e);
}
return false;
}
}
4) 具体使用步骤如下:
具体枚举类实现上面定义的EnumValidator接口:
public enum RefNodeType implements EnumValidator {
PROJECT("project", "项目"),
FIELD("field", "变量"),
FUNC("func", "函数");
private final String code;
private final String label;
RefNodeType(String code, String label) {
this.code = code;
this.label = label;
}
public String getCode() {
return code;
}
public String getLabel() {
return label;
}
@Override // 需要实现getValue方法
public Object getValue() {
return code;
}
}
参数校验加上@EnumCheck枚举校验注解
public class TestDto {
@NotBlank(message = "变量类型不能为空")
@EnumCheck(clazz = RefNodeType.class, message = "变量类型不合法") // 加上枚举校验注解
private String sourceType;
}
正则通用校验
1)定义RegRule接口,将需要用到的正则表达式定义为接口属性,
public interface RegRule {
String MOBILE = "^(((13[0-9])|(14[579])|(15([0-3]|[5-9]))|(16[6])|(17[0135678])|(18[0-9])|(19[89]))\\d{8})$";
}
2)对象属性加上@Pattern注解
public class UserDto {
@Pattern(regexp = RegRule.MOBILE, message = "手机号格式不正确")
private String mobile;
}
各类异常捕获处理
参数校验失败后会抛出异常,我们只需要在全局异常处理类中捕获参数校验的失败异常,然后将错误消息添加到返回值中即可。捕获异常的方法如下所示,返回值Result是我们系统自定义的返回值类。
@RestControllerAdvice
public class GlobalExceptionHandler {
/**
* 全局异常处理
*/
@ExceptionHandler(Exception.class)
public Result handleException(Exception ex, HttpServletRequest request, HttpServletResponse response) {
// do something
}
}
1)缺少参数抛出的异常是MissingServletRequestParameterException,如下所示:
if (e instanceof MissingServletRequestParameterException){
Result result = Result.buildErrorResult(ErrorCodeEnum.PARAM_ILLEGAL);
String msg = MessageFormat.format("缺少参数{0}", ((MissingServletRequestParameterException) e).getParameterName());
result.setMessage(msg);
return result;
}
2)单参数校验失败后抛出的异常是ConstraintViolationException,如下所示:
if (e instanceof ConstraintViolationException){
Result result = Result.buildErrorResult(ErrorCodeEnum.PARAM_ILLEGAL);
Set<ConstraintViolation<?>> sets = ((ConstraintViolationException) e).getConstraintViolations();
if(CollectionUtils.isNotEmpty(sets)){
StringBuilder sb = new StringBuilder();
sets.forEach(error -> {
if (error instanceof FieldError) {
sb.append(((FieldError)error).getField()).append(":");
}
sb.append(error.getMessage()).append(";");
});
String msg = sb.toString();
msg = StringUtils.substring(msg, 0, msg.length() -1);
result.setMessage(msg);
}
return result;
}
3)get请求的对象参数校验失败后抛出的异常是BindException
if (e instanceof BindException){
Result result = Result.buildErrorResult(ErrorCodeEnum.PARAM_ILLEGAL);
List<ObjectError> errors = ((BindException) e).getBindingResult().getAllErrors();
String msg = getValidExceptionMsg(errors);
if (StringUtils.isNotBlank(msg)){
result.setMessage(msg);
}
return result;
}
private String getValidExceptionMsg(List<ObjectError> errors) {
if(CollectionUtils.isNotEmpty(errors)){
StringBuilder sb = new StringBuilder();
errors.forEach(error -> {
if (error instanceof FieldError) {
sb.append(((FieldError)error).getField()).append(":");
}
sb.append(error.getDefaultMessage()).append(";");
});
String msg = sb.toString();
msg = StringUtils.substring(msg, 0, msg.length() -1);
return msg;
}
return null;
}
4)post请求的对象参数校验失败后抛出的异常是MethodArgumentNotValidException
if (e instanceof MethodArgumentNotValidException){
Result result = Result.buildErrorResult(ErrorCodeEnum.PARAM_ILLEGAL);
List<ObjectError> errors = ((MethodArgumentNotValidException) e).getBindingResult().getAllErrors();
String msg = getValidExceptionMsg(errors);
if (StringUtils.isNotBlank(msg)){
result.setMessage(msg);
}
return result;
}