检验模式
上面例子中一次性返回了所有验证不通过的集合,通常按顺序验证到第一个字段不符合验证要求时,就可以直接拒绝请求了。Hibernate Validator有以下两种验证模式:
-
普通模式(默认是这个模式):会校验完所有的属性,然后返回所有的验证失败信息。
-
快速失败模式:只要有一个验证失败,则返回。
通常在实际开发中,我们需要配置快速失败模式,快速失败模式的配置方式:
package com.morris.validator.config;
import org.hibernate.validator.HibernateValidator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.validation.beanvalidation.MethodValidationPostProcessor;
import javax.validation.Validation;
import javax.validation.Validator;
import javax.validation.ValidatorFactory;
@Configuration
public class ValidatorConfig {
@Bean
public Validator validator() {
ValidatorFactory validatorFactory = Validation.byProvider(HibernateValidator.class)
.configure()
// 快速失败模式
.failFast(true)
.buildValidatorFactory();
return validatorFactory.getValidator();
}
}
也可以如下配置:
@Bean
public Validator validator() {
ValidatorFactory validatorFactory = Validation.byProvider(HibernateValidator.class)
.configure()
// 快速失败模式
//.failFast(true)
.addProperty( "hibernate.validator.fail_fast", "true" )
.buildValidatorFactory();
return validatorFactory.getValidator();
}
参数校验
post参数校验
post参数校验需要在方法的参数前面加上注解 @Valid,然后就可以在方法参数对应的实体类的字段上面加上@NotNull、@NotEmpty注解进行校验了。
@PostMapping("add")
public R add(@Valid @RequestBody User user) {
return R.ok();
}
get参数校验
get参数校验无法使用注解@Valid,需要使用@Validated注解来使得验证生效。
注入MethodValidationPostProcessor
在ValidatorConfig注入MethodValidationPostProcessor来开启@Validated注解的校验功能。
@Bean
public MethodValidationPostProcessor methodValidationPostProcessor() {
MethodValidationPostProcessor postProcessor = new MethodValidationPostProcessor();
// 设置validator模式为快速失败返回
postProcessor.setValidator(validator());
return postProcessor;
}
Controller中的配置
Controller上面添加@Validated
注解,方法参数上面添加校验注解。
package com.morris.validator.controller;
import com.morris.validator.vo.R;
import com.morris.validator.vo.User;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.validation.Valid;
import javax.validation.constraints.NotBlank;
@RestController
@RequestMapping("user")
@Validated
public class GetController {
@PostMapping("get")
public R add(@NotBlank(message = "status不能为空") String status) {
return R.ok();
}
}
ConstraintViolationException异常处理
对get参数校验会抛出ConstraintViolationException异常,所以需要在GlobalExceptionHandler中对ConstraintViolationException异常进行处理。
@ExceptionHandler(value = ConstraintViolationException.class)
public R ConstraintViolationExceptionHandler(ConstraintViolationException ex) {
R r = null;
Set<ConstraintViolation<?>> constraintViolations = ex.getConstraintViolations();
Iterator<ConstraintViolation<?>> iterator = constraintViolations.iterator();
while (iterator.hasNext()) {
ConstraintViolation<?> cvl = iterator.next();
r = R.fail(HttpStatus.BAD_REQUEST.value(), cvl.getMessageTemplate());
break;
}
log.error("Custom exception! error={}", ex);
return r;
}
测试
# curl -X POST http://localhost:8080/user/get -H "content-type:application/json"
{"code":400,"message":"status不能为空","result":null}
# curl -X POST http://localhost:8080/user/get?status= -H "content-type:application/json"
{"code":400,"message":"status不能为空","result":null}
# curl -X POST http://localhost:8080/user/get?status=xxxx -H "content-type:application/json"
{"code":200,"message":"OK","result":null}
级联校验
级联校验需要在校验的属性上面加上@Valid注解。
public class User {
... ...
@Valid
private Address address;
}
@Data
public class Address {
@NotBlank(message = "province不能为空")
private String province;
private String city;
}
测试:
# curl -X POST http://localhost:8080/user/add -H "content-type:application/json" -d '{"userName":"xx", "age": 10, "address": {"province": "ShangHai"}}'
{"code":200,"message":"OK","result":null}
# curl -X POST http://localhost:8080/user/add -H "content-type:application/json" -d '{"userName":"xx", "age": 10, "address": {"province": ""}}'
{"code":400,"message":"province不能为空","result":null}
分组
Person类字段分组校验如下:
public interface PersonCreateGroup {
}
public interface PersonUpdateGroup {
}
@Data
public class Person {
@NotNull(message = "id不能为空", groups = PersonUpdateGroup.class)
private Integer id;
@NotNull(message = "userName不能为空", groups = {PersonCreateGroup.class, PersonUpdateGroup.class})
@NotBlank(message = "userName不能为空", groups = {PersonCreateGroup.class, PersonUpdateGroup.class})
private String userName;
@NotNull(message = "age不能为空")
private Integer age;
}
Person类中三个分组分别对应的字段如下:
-
PersonUpdateGroup:id、userName
-
PersonCreateGroup:userName
-
Default:age
PersonController代码如下:
package com.morris.validator.controller;
import com.morris.validator.vo.*;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.validation.Valid;
@RestController
@RequestMapping("person")
@Validated
public class PersonController {
@PostMapping("add")
@Validated(PersonCreateGroup.class)
public R add(@Valid @RequestBody Person person) {
return R.ok();
}
@PostMapping("update")
@Validated(PersonUpdateGroup.class)
public R update(@Valid @RequestBody Person person) {
return R.ok();
}
@PostMapping("add2")
public R add2(@Valid @RequestBody Person person) {
return R.ok();
}
}
下面分别对PersonController中的各个接口进行测试:
add:
# curl -X POST http://localhost:8080/person/add -H "content-type:application/json" -d '{"userName":"xxx", "age": 10}'
{"code":200,"message":"OK","result":null}
# curl -X POST http://localhost:8080/person/add -H "content-type:application/json" -d '{"userName":"xxx"}'
{"code":400,"message":"age不能为空","result":null}
# curl -X POST http://localhost:8080/person/add -H "content-type:application/json" -d '{"age":10}'
{"code":400,"message":"userName不能为空","result":null}
update:
# curl -X POST http://localhost:8080/person/update -H "content-type:application/json" -d '{"id":1, "userName":"xxx", "age": 10}'
{"code":200,"message":"OK","result":null}
# curl -X POST http://localhost:8080/person/update -H "content-type:application/json" -d '{"userName":"xxx", "age": 10}'
{"code":400,"message":"id不能为空","result":null}
# curl -X POST http://localhost:8080/person/update -H "content-type:application/json" -d '{"id":1, "age": 10}'
{"code":400,"message":"userName不能为空","result":null}
# curl -X POST http://localhost:8080/person/update -H "content-type:application/json" -d '{"id":1, "userName":"xxx"}'
{"code":400,"message":"age不能为空","result":null}
add2:
# curl -X POST http://localhost:8080/person/add2 -H "content-type:application/json" -d '{"age": 10}'
{"code":200,"message":"OK","result":null}
# curl -X POST http://localhost:8080/person/add2 -H "content-type:application/json" -d '{}'
{"code":400,"message":"age不能为空","result":null}
总结:
-
校验时指定了group,就会校验指定group和默认group
-
校验时没指定group,只会校验默认group
校验时可以指定多个分组,并为这些分组指定顺序:
@GroupSequence({OneGroup.class, TwoGroup.class, ThreeGroup.class})
public interface PersonOrder {
}
自定义校验器
一般情况,内置的校验器可以解决很多问题。但也有无法满足情况的时候,此时,我们可以实现validator的接口,自定义自己需要的验证器。
如下所示,实现了一个身份证规则验证器:
package com.morris.validator.validator;
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class IdCardValidator implements ConstraintValidator<IdCard, String> {
/**
* 身份证规则校验正则表达式
*/
private String reg = "^(\\d{6})(\\d{4})(\\d{2})(\\d{2})(\\d{3})([0-9]|X)$";
private Pattern pt = Pattern.compile(reg);
@Override
public void initialize(IdCard idCard) {
}
@Override
public boolean isValid(String value, ConstraintValidatorContext arg1) {
if (value == null) {
return true;
}
Matcher m = pt.matcher(value);
if (m.find()) {
return true;
}
return false;
}
}
自定义注解:
package com.morris.validator.validator;
import javax.validation.Constraint;
import javax.validation.Payload;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target({ElementType.FIELD, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy= IdCardValidator.class)
public @interface IdCard {
String message() default "身份证号码格式不对";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
使用如下:
public class Person {
... ...
@IdCard(message = "身份证号码格式不对")
private String idCardNo;
}
测试如下:
# curl -X POST http://localhost:8080/person/add -H "content-type:application/json" -d '{"userName":"morris","age":10, "idCardNo":"33"}'
{"code":400,"message":"身份证号码格式不对","result":null}