什么是jsr303?
JSR 303 – Bean Validation 是一个数据验证的规范,2009 年 11 月确定最终方案.
Hibernate Validator 是 Bean Validation 的最佳实践
Bean Validation 中的 constraint
Hibernate Validator 附加的 constraint
一个constraint由annotation和相应的 constraint validator 组成,一个annotation可以对应多个constraint validator,
constraint 之间也可以互相组合生成更复杂的constraint,如果还是满足不了需求则可以自定义全新的constraint
所需要jar
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-validator</artifactId>
<version>5.2.4.Final</version>
</dependency>
基本使用
定义在bean中
public class User implements Serializable {
@NotBlank
private String id;
//省略get,set
}
controller部分代码
@RequestMapping("/test")
@ResponseBody
public String test(@ModelAttribute @Valid User user, BindingResult bindingResult) {
if (bindingResult.hasErrors()) {
ObjectError objectError = bindingResult.getAllErrors().get(0);
if (objectError instanceof FieldError) {
FieldError fieldError = (FieldError) objectError;
logger.info(fieldError.getField() + fieldError.getDefaultMessage());
}
}
logger.info(user.toString());
return "";
}
当请求参数id为空时打印id不能为空
自定义消息错误消息
Hibernate Validator支持硬编码错误消息和资源文件定义错误消息
- 硬编码
@NotBlank(message = "用户id不能为空")
private String id;
controller层不变,请求打印id用户id不能为空
,这前面的id是从error获取的字段名并不是错误消息
- 从资源文件获取
在resources文件下新建messages.properties
添加user.id.null=id不能为null
,添加注解@NotBlank(message = "${user.id.null}")
,刚刚定义的错误信息会取代系统的错误信息
xml定义
<mvc:annotation-driven validator="validator">
</mvc:annotation-driven>
<bean id="validator" class="org.springframework.validation.beanvalidation.LocalValidatorFactoryBean">
<property name="providerClass" value="org.hibernate.validator.HibernateValidator"/>
<!-- 如果不加默认到 使用classpath下的 ValidationMessages.properties -->
<property name="validationMessageSource" ref="messageSource"/>
</bean>
<bean id="messageSource" class="org.springframework.context.support.ReloadableResourceBundleMessageSource">
<property name="basenames">
<list>
<!-- 在web环境中一定要定位到classpath 否则默认到当前web应用下找 -->
<value>classpath:messages</value>
</list>
</property>
<property name="useCodeAsDefaultMessage" value="false"/>
<property name="defaultEncoding" value="UTF-8"/>
<property name="cacheSeconds" value="60"/>
</bean>
定义在参数中
jsr303
是对bean的校验,不支持对参数进行校验,因此jsr309
规范产生了,spring提供了@Validated
注解对方法参数进行校验
controller层代码
@Controller
//这个注解要放在class上
@Validated
public class TestController2 {
private Logger logger = LoggerFactory.getLogger(getClass());
@RequestMapping("/test2")
@ResponseStatus(HttpStatus.OK)
public String test2(@Email @RequestParam String email) {
logger.info(email);
return "";
}
}
如果不满足格式则返回javax.validation.ConstraintViolationException
异常,这就需要定义一个ControllerAdvice
去处理
@ControllerAdvice
public class MyExceptionHandler {
@ExceptionHandler(ConstraintViolationException.class)
@ResponseBody
@ResponseStatus(HttpStatus.BAD_REQUEST)
public String handleValidationException(ConstraintViolationException e){
for(ConstraintViolation<?> s:e.getConstraintViolations()){
return s.getInvalidValue()+": "+s.getMessage();
}
return "请求参数不合法";
}
}
定义在类上
当和前端交互时经常需要使用的对前端传递的用户id和数据库中的比对,看是否存在此id,这时候将校验放在类上会减少重复代码
定义注解
@Constraint(validatedBy = {UserValidator.class})
@Documented
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface CheckUser {
String message() default "错误的用户id";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
对应校验器
public class UserValidator implements ConstraintValidator<CheckUser, User> {
@Autowired
UserService userService;
public void initialize(CheckUser constraintAnnotation) {
}
public boolean isValid(User user, ConstraintValidatorContext context) {
return userService.checkUser(user);
}
}
注解的使用
@CheckUser
public class User implements Serializable {
}
constraint组合
定义注解
@Max(10000)
@Min(8000)
@Constraint(validatedBy = {})
@Documented
@Target({ElementType.ANNOTATION_TYPE, ElementType.METHOD, ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface Price {
String message() default "错误的价格";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
定制constraint
自定义Constraint需要一个Constraint注解和对应的Validator
@Constraint(validatedBy = {StatusValidator.class})
@Documented
@Target({ElementType.ANNOTATION_TYPE, ElementType.METHOD, ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface Status {
String message() default "不正确的状态 , 应该是 'created', 'paid', shipped', closed'其中之一";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
对应的Validator
public class StatusValidator implements ConstraintValidator<Status, String> {
private final String[] ALL_STATUS = {"created", "paid", "shipped", "closed"};
public void initialize(Status status) {
}
public boolean isValid(String value, ConstraintValidatorContext context) {
if (Arrays.asList(ALL_STATUS).contains(value)) {
return true;
}
return false;
}
}
使用的话就和普通的constraint是相同的
分组验证、分组顺序
当相同的bean同一个字段需要不同的校验规则时就需要分组验证
首先定义分组接口用于标识
public interface Group1 {
}
public interface Group2 {
}
在bean添加注解
@Max(value = 50,groups = Group1.class)
@Min(value = 18,groups = Group1.class)
private Integer age;
controller层
@RequestMapping("/testGroup")
@ResponseBody
public User testGroup(@ModelAttribute @Validated(value = Group1.class) User user,BindingResult bindingResult) {
if (bindingResult.hasErrors()){
logger.info("error");
return null;
}
return user;
}
多个组则使用value = {Group1.class, Group2.class}
这里会出现一个问题就是会校验每一个条件并返回,但是每次返回的错误顺序可能是不一样的,这时要使用@GroupSequence({Group1.class,Group2.class,User.class})
来控制校验顺序,即先校验group1再校验group2