了解了这些之后,想自定义失败消息message
,就简直不要太简单了好不好,例子如下:
@Min(value = 10, message = "{com.fsx.my.min.message}") private Integer age;
写一个资源属性文件,命名为ValidationMessages.properties
放在类路径下,文件内容如下:
// 此处可以使用占位符{value}读取注解对应属性上的值 com.fsx.my.min.message=[自定义消息]最小值必须是{value}
运行测试用例,打印输出如下失败消息:
age [自定义消息]最小值必须是10: -1
完美(自定义的生效了)
说明:因为我的平台是中文的,因此文件命名为ValidationMessages_zh_CN.properties的效果也是一样的,因为Hibernate Validation提供了Locale国际化的支持
Spring环境下自定义国际化消息
上面使用的是Hibernate Validation内置的对国际化的支持,由于大部分情况下我们都是在Spring环境下使用数据校验,因此有必要讲讲Spring加持情况下的国家化做法。我们知道Spring MVC是有专门做国际化的模块的,因此国际化这个动作当然也是可以交给Spring自己来做的,此处我也给一个Demo吧:
说明:即使在Spring环境下,你照常使用Hibernate Validation的国际化方案,依旧是没有问题的~
1、向容器内配置验证器(含有自己的国际化资源文件):
@Configuration public class RootConfig { @Bean public LocalValidatorFactoryBean localValidatorFactoryBean() { LocalValidatorFactoryBean localValidatorFactoryBean = new LocalValidatorFactoryBean(); // 使用Spring加载国际化资源文件 //ResourceBundleMessageSource messageSource = new ResourceBundleMessageSource(); //messageSource.setBasename("MyValidationMsg"); ReloadableResourceBundleMessageSource messageSource = new ReloadableResourceBundleMessageSource(); messageSource.setBasename("MyValidationMsg"); // 注意此处名字就随意啦,毕竟交给spring了`.properties`就不需要了哦 messageSource.setCacheSeconds(120); // 缓存时长 // messageSource.setFileEncodings(); // 设置编码 UTF-8 localValidatorFactoryBean.setValidationMessageSource(messageSource); return localValidatorFactoryBean; } }
运行单测:
@Slf4j @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(classes = {RootConfig.class}) public class TestSpringBean { @Autowired private LocalValidatorFactoryBean localValidatorFactoryBean; @Test public void test1() { Person person = new Person(); person.setAge(-5); Validator validator = localValidatorFactoryBean.getValidator(); Set<ConstraintViolation<Person>> result = validator.validate(person); // 输出错误消息 result.stream().map(v -> v.getPropertyPath() + " " + v.getMessage() + ": " + v.getInvalidValue()) .forEach(System.out::println); } }
打印校验失败消息如下(完美生效):
age [自定义消息]最小值必须是10: -5
说明:若是Spring应用,如果你还需要考虑国际化的话,我个人建议使用Spring来处理国际化,而不是Hibernate~(有种Spring的脑残粉感觉有木有,当然这不是强制的)
Spring MVC中如何自定义全局校验器Validator
Spring MVC默认配置的(使用的)校验器的执行代码如下:
public class WebMvcConfigurationSupport implements ApplicationContextAware, ServletContextAware {\ ... @Bean public Validator mvcValidator() { Validator validator = getValidator(); if (validator == null) { if (ClassUtils.isPresent("javax.validation.Validator", getClass().getClassLoader())) { Class<?> clazz; try { String className = "org.springframework.validation.beanvalidation.OptionalValidatorFactoryBean"; clazz = ClassUtils.forName(className, WebMvcConfigurationSupport.class.getClassLoader()); } catch (ClassNotFoundException | LinkageError ex) { throw new BeanInitializationException("Failed to resolve default validator class", ex); } validator = (Validator) BeanUtils.instantiateClass(clazz); } else { validator = new NoOpValidator(); } } return validator; } ... }
代码很简答,就不逐行解释了。我归纳如下:
- Spring MVC中校验要想自动生效,必须导入了javax.validation.Validator才行,否则是new NoOpValidator()它木有校验行为
- Spring MVC最终默认使用的校验器是OptionalValidatorFactoryBean(LocalValidatorFactoryBean的子类)~
- 显然,要想校验生效@EnableWebMvc也是必须的(SpringBoot环境另说)
那如何自定义一个全局的校验器呢?最佳做法如下:
@Configuration @EnableWebMvc public class WebMvcConfig extends WebMvcConfigurerAdapter { ... @Override public Validator getValidator() { // return "global" validator return new LocalValidatorFactoryBean(); } ... }
当然,你还可以使用@InitBinder来设置,甚至可以细粒度设置到只与当前Controller绑定的校验器都是可行的(比如你可以使用自定校验器实现各种私有的、比较复杂的逻辑判断)
说到这自定义Validator了,此处再说一下自定义MessageCodesResolver消息状态码解析器吧。
MessageCodesResolver:Spring进行数据校验失败时,会通过MessageCodesResolver生成错误码放入Errors错误对象。Spring默认使用的逻辑完全同上~
public interface MessageCodesResolver { String[] resolveMessageCodes(String errorCode, String objectName); String[] resolveMessageCodes(String errorCode, String objectName, String field, @Nullable Class<?> fieldType); }
它的唯一实现类是:DefaultMessageCodesResolver。它的两个方法做的事情比较简单,效果如下(注意:此处所谓的错误码就是这些字符串):
需要注意的是:这两个组件虽然都是在Spring里的,但是如果你向如上方式来提供,它就单属于Spring MVC容器的(SpringBoot另说)
自定义约束
JSR和Hibernate支持的约束条件已经足够强大,应该是能满足我们绝大部分情况下的基础验证的。如果还是不能满足业务需求,我们还可以自定义约束,也很简单一事。
JSR和Hibernate提供的约束注解解释说明:【小家Java】深入了解数据校验(Bean Validation):从深处去掌握@Valid的作用(级联校验)以及常用约束注解的解释说明
自定义一个约束分如下三步(说是2步也成):
- 自定义一个约束注解
- 实现一个校验器(实现接口:ConstraintValidator)
- 定义默认的校验错误信息
给个Demo:此处以自定义一个约束注解来校验集合的长度范围:@CollectionRange
1、自定义注解(此处使用得比较高级)
@Documented @Constraint(validatedBy = {}) @SupportedValidationTarget(ValidationTarget.ANNOTATED_ELEMENT) @Target({METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE}) @Retention(RUNTIME) @Repeatable(value = CollectionRange.List.class) @Size // 校验动作委托给Size去完成 所以它自己并不需要校验器~~~ @ReportAsSingleViolation // 组合组件一般建议标注上 public @interface CollectionRange { // 三个必备的基本属性 String message() default "{com.fsx.my.collection.message}"; Class<?>[] groups() default {}; Class<? extends Payload>[] payload() default {}; // 自定义属性 @OverridesAttribute这里有点方法覆盖的意思~~~~~~ 子类属性覆盖父类的默认值嘛 @OverridesAttribute(constraint = Size.class, name = "min") int min() default 0; @OverridesAttribute(constraint = Size.class, name = "max") int max() default Integer.MAX_VALUE; // 重复注解 @Target({METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE}) @Retention(RUNTIME) @Documented public @interface List { CollectionRange[] value(); } }
2、实现一个校验器
此例用不着(下面会有)
3、自定义错误消息
当然,你可以写死在message属性上,但是本处使用配置的方式来~
com.fsx.my.collection.message=[自定义消息]你的集合的长度必须介于{min}和{max}之间(包含边界值)