初步认识
spring数据验证核心类:①:Validator ②:Errors,两者之间的纽带是Validator中定义的validate方法。
public interface Validator {
// 限定Validator的职责,不可能所有的校验全部交给一个Validator来做
boolean supports(Class<?> clazz);
// 将target校验错误信息放入Errors中
void validate(Object target, Errors errors);
}
简单使用
@Data
public class Customer {
private String name;
private String sex;
private PhoneNumber phoneNumber;
}
@Data
public class PhoneNumber {
private String number;
private String areaCode;
}
public class CustomerValidator implements Validator {
private PhoneNumberValidator phoneNumberValidator;
@Override
public boolean supports(Class<?> aClass) {
return ClassUtils.isAssignable (aClass,Customer.class);
}
@Override
public void validate(Object target, Errors errors) {
Customer customer = (Customer) target;
// 最后一个参数可以替换掉messages_zh_CN.properties中对应errorCode消息中的占位符
ValidationUtils.rejectIfEmpty (errors,"name","name.empty",new Object[]{1,2});
ValidationUtils.rejectIfEmpty (errors,"sex","sex.empty");
PhoneNumber phoneNumber = customer.getPhoneNumber ();
// 这里涉及到嵌套校验,需要改变校验对象的上下文路径
// 这个名称并不是随便写的,和你声明的对象名称一致就好了,如果是list,比如List<PhoneNumber>
// phoneNumbers ;那么这里就变成循环校验,上下文路径就应该是phoneNumbers[i],i是循环变量,1,2,3...
errors.pushNestedPath ("phoneNumber");
ValidationUtils.invokeValidator (phoneNumberValidator,phoneNumber,errors);
errors.popNestedPath ();
}
public void setPhoneNumberValidator(PhoneNumberValidator phoneNumberValidator) {
this.phoneNumberValidator = phoneNumberValidator;
}
}
public class PhoneNumberValidator implements Validator {
@Override
public boolean supports(Class<?> aClass) {
return ClassUtils.isAssignable (aClass,PhoneNumber.class);
}
@Override
public void validate(Object target, Errors errors) {
PhoneNumber phoneNumber = (PhoneNumber) target;
if(phoneNumber == null)
errors.reject ("PhoneNumber is null");
if(!StringUtils.isNumeric (phoneNumber.getAreaCode ()))
errors.rejectValue ("areaCode","areaCode.numeric","areaCode cannot be empty");
if(!StringUtils.isNumeric (phoneNumber.getNumber ()))
errors.rejectValue ("number","number.numeric");
}
}
public class TestDemo {
public static void main(String[] args) {
CustomerValidator customerValidator = new CustomerValidator ();
customerValidator.setPhoneNumberValidator (new PhoneNumberValidator ());
ResourceBundleMessageSource messageSource = new ResourceBundleMessageSource ();
messageSource.setBasename ("messages");
Customer customer = new Customer ();
PhoneNumber phoneNumber = new PhoneNumber ();
customer.setPhoneNumber (phoneNumber);
BindException errors = new BindException (customer, "customer");
ValidationUtils.invokeValidator (customerValidator,customer,errors);
if(errors.hasErrors ()){
List<ObjectError> allErrors = errors.getAllErrors ();
for (int i = 0; i < allErrors.size (); i++) {
String message = messageSource.getMessage (allErrors.get (i).getCode (), allErrors.get (i).getArguments (), Locale.CHINA);
System.out.println (message);
}
}
}
}
当然在spring中,我们没有必要自己去定义Validator,它给我们提供了相关的实现类LocalValidatorFactoryBean。
但是它并未帮助我们实现校验的相关逻辑,想想也不可能嘛,它咋知道我们想要怎样的校验,于是它委托其他类来实现,同时我们也不需要自己绑定消息源了,我们只要告诉消息源的位置就可以了。
这里委托校验的类很关键,而已知的hibernate校验框架就可以实现这个功能,它内置了很多的约束校验器,我们只需通过注解就能完成对应的校验功能,当然也可以自定义,这个放在后面讲。
springMVC中我们可以这么配置:
<bean id="validator" class="org.springframework.validation.beanvalidation.LocalValidatorFactoryBean">
<property name="providerClass" value="org.hibernate.validator.HibernateValidator"/>
<property name=''validationMessageSource'' value=""/>
</bean>
其实我们在pom.xml中引入了hibernate-validator的jar包后,provideClass这个属性我们可以不用配置,LocalValidatorFactoryBean在初始化的时候,如果发现provideClass未配置,会去jar包中找。所以如果使用springBoot,我们完全可以不用进行配置,当然如果需要指定消息源,则需要进行相关配置了。
@Configuration
public class CustomConfiguration {
@Bean
public MessageSource messageSource() {
ReloadableResourceBundleMessageSource messageSource = new ReloadableResourceBundleMessageSource();
messageSource.setBasenames("classpath:/messages");
messageSource.setUseCodeAsDefaultMessage(false);
messageSource.setCacheSeconds((int) TimeUnit.HOURS.toSeconds(1));
messageSource.setFallbackToSystemLocale(false);
return messageSource;
}
@Bean
public LocalValidatorFactoryBean validator() {
LocalValidatorFactoryBean factoryBean = new LocalValidatorFactoryBean();
factoryBean.setValidationMessageSource(messageSource());
return factoryBean;
}
}
自定义约束校验器
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
@Constraint(validatedBy = NotXListValidator.class)
public @interface NotXList {
String message() default "Should not be X";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
public class NotXListValidator implements ConstraintValidator<NotXList, List<String>> {
@Override
public void initialize(NotXList constraintAnnotation) {
}
@Override
public boolean isValid(List<String> list, ConstraintValidatorContext context) {
boolean valid = true;
for (int i = 0; i < list.size(); i++) {
if ("X".equals(list.get(i))) {
valid = false;
}
}
return valid;
}
}
public class ListContainer {
@NotXList
private List<String> list = new LinkedList<>();
public void addString(String value) {
list.add(value);
}
public List<String> getList() {
return list;
}
public static void main(String[] args) {
LocalValidatorFactoryBean validator = new LocalValidatorFactoryBean();
validator.afterPropertiesSet();
ListContainer listContainer = new ListContainer();
listContainer.addString("A");
listContainer.addString("X");
BeanPropertyBindingResult errors = new BeanPropertyBindingResult(listContainer, "listContainer");
validator.validate(listContainer, errors);
}
}
一些简单的校验器Hibernate校验框架已经提供了,大家可以参考,我用的6.0.20的包,都位于org.hibernate.validator.internal.constraintvalidators.bv包下面。
请求参数的校验
// 使用@Valid或者@Validated将会对请求参数进行校验
@Controller
@RequestMapping("/test")
public class ValidationTestController {
@ResponseBody
@RequestMapping("/test1")
public String test1(@Valid User user){
return "test1";
}
}
原理:请求参数在解析的过程中会进行校验,下面是校验的逻辑
protected void validateIfApplicable(WebDataBinder binder, MethodParameter parameter) {
Annotation[] annotations = parameter.getParameterAnnotations();
for (Annotation ann : annotations) {
Validated validatedAnn = AnnotationUtils.getAnnotation(ann, Validated.class);
if (validatedAnn != null || ann.annotationType().getSimpleName().startsWith("Valid")) {
Object hints = (validatedAnn != null ? validatedAnn.value() : AnnotationUtils.getValue(ann));
Object[] validationHints = (hints instanceof Object[] ? (Object[]) hints : new Object[] {hints});
binder.validate(validationHints);
break;
}
}
}
方法校验
public interface MyValidInterface<T> {
@NotNull Object myValidMethod(@NotNull String arg1, @Max(10) int arg2);
T myGenericMethod(@NotNull T value);
}
// 这个注解一定要有,不然不起作用
@Validated
@Service
public class MyValidBean implements MyValidInterface<String> {
@Override
public Object myValidMethod(String arg1, int arg2) {
return (arg2 == 0 ? null : "value");
}
@Override
public String myGenericMethod(String value) {
return value;
}
}
springBoot中
@Controller
public class ValidController{
@Autowired
private MyValidBean validBean;
@RequestBody
@RequestMapping("/valid")
public String testValid(String type,int age){
// 这里如果参数不满足指定方法的校验要求,就会报错
return validBean.myValidMethod(type,age);
}
}
spring中需要配置MethodValidationPostProcessor
<bean id="methodValidationPostProcessor" class="org.springframework.validation.beanvalidation.MethodValidationPostProcessor">
</bean>
MethodValidationPostProcessor的实现原理就是定义了一个切面,这有关springAOP的内容,在这篇博客里面我不打算深入讲解。
有关Validator的内容暂时先写到这里,以后有内容需要补充再记录。