JSR-303 是 JAVA EE 6 中的一项子规范,叫做 Bean Validation,官方参考实现是Hibernate Validator。
使用步骤:
1. 基本使用
1)给java bean上需要做校验的属性添加校验注解
例如:
@NotBlank
private String name;
java给我们提供了很多数据校验注解,统一定义在 包 javax.validation.constraints 下
2)在Controller层,给需要校验的地方加上校验注解@Valid,开启校验功能
例如:
@RequestMapping("/save")
public R save(@Valid @RequestBody BrandEntity brand){
brandService.save(brand);
return R.ok();
}
在controller处理函数的实体bean上添加注解@Valid校验注解之后,在提交请求的时候,就会对提交的数据进行数据校验
3)使用自定义的校验不通过提示信息
当校验不通过的时候,java定义了默认的校验不通过提示信息,如果我们不想使用默认提示信息,可以添加message属性。例如:
@URL(message = "logo必须是一个合法的URL地址")
private String logo;
4)获取校验结果信息
在开发规范中,我们一般希望系统可以统一响应对象,如果对校验不做处理,那么返回的信息与我们规范不符合,这时候,我们可以使用BindingResult,BindingResult封装了校验结果,定义紧跟在@Valid修饰的实体bean的后面
例如:
@RequestMapping("/save")
public R save(@Valid @RequestBody BrandEntity brand, BindingResult result){
if(result.hasErrors()){
Map<String,String> map = new HashMap<>();
result.getFieldErrors().stream().forEach(item -> {
map.put(item.getField(),item.getDefaultMessage()); // 如果有自定义消息,那么getDefaultMessage获取的是自定义的,否则就是默认的消息
});
return R.error(400,"提交的数据不合法").put("data",map);
}
brandService.save(brand);
return R.ok();
}
如果每一个校验都这样处理,会很麻烦,所以,我们可以定义一个统一异常处理器类。
例如:
package com.bjc.gulimall.product.exception;
import com.bjc.common.enums.BizCodeEnume;
import com.bjc.common.utils.R;
import lombok.extern.slf4j.Slf4j;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import java.util.HashMap;
import java.util.Map;
//@ControllerAdvice
//@ResponseBody
@RestControllerAdvice(basePackages="com.bjc.gulimall.product.controller")
@Slf4j
public class GulimallExceptionControllerAdvice {
//处理JSR303数据校验抛出的异常
@ExceptionHandler(value = MethodArgumentNotValidException.class)
public R handleValidException(MethodArgumentNotValidException e){
// 打印日志
log.error("提交的数据不合法",e);
BindingResult bindingResult = e.getBindingResult();
Map<String,String> errMap = new HashMap<>();
bindingResult.getFieldErrors().stream().forEach(item -> {
errMap.put(item.getField(),item.getDefaultMessage());
});
return R.error(BizCodeEnume.VAILD_EXCEPTION.getCode(),BizCodeEnume.VAILD_EXCEPTION.getMsg()).put("data",errMap);
}
@ExceptionHandler(value = Throwable.class)
public R handleException(Throwable e){
log.error("出现异常:",e);
return R.error(BizCodeEnume.UNKNOW_EXCEPTION.getCode(),BizCodeEnume.UNKNOW_EXCEPTION.getMsg());
}
}
这样,在controller中,就不需要处理异常了,可以放心的抛了。
注意:对于某一种验证规则是适用于一种数据类型的,简单说来,正则则表达式的验证对象可以为String类型的,但是不可以为Integer类型的数据,那么当我们使用正则表达式进行验证的时候就会出现错误
2. 分组校验groups
有时候,我们有这样的需求,同一个属性字段,在不同的情况下有不同的校验规则,这时候,我们就需要用到分组校验了。
使用步骤:
1)新建两个用于标注分组的接口(接口为空接口即可)
例如:
添加分组接口:
package com.bjc.common.validation;
public interface AddGroup {
}
更新分组接口:
package com.bjc.common.validation;
public interface UpdateGroup {
}
注意:为什么需要该类型的接口,是因为分组校验的时候,需要传递不同的分组接口
2)在实体类上添加groups分组,并制定分组接口
例如:
2.1 id在新增的时候需要为空,在更新的时候不能为空
@NotNull(message = "更新id不能为空",groups = UpdateGroup.class)
@Null(message = "新增id必须为空",groups = AddGroup.class)
private Long brandId;
2.2 name在新增和更新都不能为空
@NotBlank(message = "品牌名必须不能为空",groups = {UpdateGroup.class, AddGroup.class})
private String name;
3)在controller上添加注解@Validated,并指定分组接口
例如:
public R update(@Validated(value={UpdateGroup.class}) @RequestBody BrandEntity brand){}
注意:
3.1 这里校验注解为@Validated,该注解是spring提供的,是对@Valid的实现
3.2 如果开启了分组校验,那么校验属性字段都需要指定分组,否则没指定分组的属性校验会失效
3.3 如果在controller中校验注解没有指定分组,那么java bean中指定了分组的校验会失效,没有指定生效
3. 自定义校验注解
有时候@Pattern正则匹配不能满足业务需求,这时候就需要使用自定义校验注解了。
3.1 自定义校验注解
可以参看官方提供的校验注解,校验注解必须包含三个属性message、groups、payload。可以参考官方提供的@NotBlank注解,自定义我们自己的注解。例如:
package com.bjc.common.validation.selfvalidator;
import javax.validation.Constraint;
import javax.validation.Payload;
import java.lang.annotation.*;
/*
* 自定义校验注解
*
* */
@Documented
@Constraint(validatedBy = {})
@Target({ElementType.METHOD, ElementType.FIELD, ElementType.ANNOTATION_TYPE, ElementType.CONSTRUCTOR, ElementType.PARAMETER, ElementType.TYPE_USE})
@Retention(RetentionPolicy.RUNTIME)
public @interface ListIntegerValue {
/*
* 校验注解中,必须要有message groups payload 三个属性 可以直接拷贝JDK提供的校验注解中提供的
* */
String message() default "{javax.validation.constraints.ListValue.message}";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
/*
* 需要指定的值
* */
int[] vals() default {};
}
注意:message的默认值,可以参考官方提供的配置文件,新建我们自己的配置文件,写入我们自定义的错误描述信息,例如:
怎么查看官方配置文件,直接搜索官方提供的校验注解的message的默认值,例如:搜索 javax.validation.constraints.Min.message即可找到对应的文件。
然后还需要添加校验依赖
<!-- 添加校验api -->
<dependency>
<groupId>javax.validation</groupId>
<artifactId>validation-api</artifactId>
<version>2.0.1.Final</version>
</dependency>
3.2 自定义校验器
校验器的作用是校验注解标注的字段。从上面的注解中,我们可以看到有一个注解@Constraint,其有一个属性validateBy,该属性接收校验器,进入@Constraint注解,发现其属性validateBy是一个Class数组,泛型为ConstraintValidator,如图:
进入ConstraintValidator该,发现其是一个接口,如图:
所以,我们自定义校验器只需要实现 ConstraintValidator接口即可。例如:
package com.bjc.common.validation.selfvalidator;
import org.apache.commons.lang.ArrayUtils;
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;
/**
* 自定义Integer类型指定数值校验注解校验器
* */
public class ListValueConstraintValidatorForInteger implements ConstraintValidator<ListValue,Integer> {
Set<Integer> set = new HashSet<>();
/*
* 初始化方法,用于获取自定义注解的详细信息
* */
@Override
public void initialize(ListValue constraintAnnotation) {
int[] vals = constraintAnnotation.vals();
if(ArrayUtils.isNotEmpty(vals)){
Arrays.stream(vals).forEach(set::add);
}
}
/*
* 校验值是否符合要求
* */
@Override
public boolean isValid(Integer val, ConstraintValidatorContext constraintValidatorContext) {
return set.contains(val);
}
}
3.3 使用自定义校验注解
在自定义注解上加上validatedBy的属性值为校验器的class,如图:
然后,在java bean中需要使用到自定义注解的字段上添加上自定义注解,例如:
@ListValue(vals = {0,1},groups = {UpdateStatusGroup.class})
// @ListIntegerValue(vals = {0,1},message = "状态值不是指定的值!")
private Integer showStatus;
完整的自定义注解定义:
package com.bjc.common.validation.selfvalidator;
import javax.validation.Constraint;
import javax.validation.Payload;
import java.lang.annotation.*;
/*
* 自定义校验注解
*
* */
@Documented
@Constraint(validatedBy = {ListValueConstraintValidatorForInteger.class})
@Target({ElementType.METHOD, ElementType.FIELD, ElementType.ANNOTATION_TYPE, ElementType.CONSTRUCTOR, ElementType.PARAMETER, ElementType.TYPE_USE})
@Retention(RetentionPolicy.RUNTIME)
public @interface ListValue {
/*
* 校验注解中,必须要有message groups payload 三个属性 可以直接拷贝JDK提供的校验注解中提供的
* */
String message() default "{javax.validation.constraints.ListValue.message}";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
/*
* 需要指定的值
* */
int[] vals() default {};
}
注意:我们可以在validatedBy 中指定多个校验器,适配不同类型的数据校验。