Bean Validation完结篇:你必须关注的边边角角(约束级联、自定义约束、自定义校验器、国际化失败消息...)【享学Spring】(中)

简介: Bean Validation完结篇:你必须关注的边边角角(约束级联、自定义约束、自定义校验器、国际化失败消息...)【享学Spring】(中)

了解了这些之后,想自定义失败消息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;
  }
  ...
}


代码很简答,就不逐行解释了。我归纳如下:


  1. Spring MVC中校验要想自动生效,必须导入了javax.validation.Validator才行,否则是new NoOpValidator()它木有校验行为
  2. Spring MVC最终默认使用的校验器是OptionalValidatorFactoryBean(LocalValidatorFactoryBean的子类)~
  3. 显然,要想校验生效@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。它的两个方法做的事情比较简单,效果如下(注意:此处所谓的错误码就是这些字符串):


image.png


需要注意的是:这两个组件虽然都是在Spring里的,但是如果你向如上方式来提供,它就单属于Spring MVC容器的(SpringBoot另说)


自定义约束


JSR和Hibernate支持的约束条件已经足够强大,应该是能满足我们绝大部分情况下的基础验证的。如果还是不能满足业务需求,我们还可以自定义约束,也很简单一事。


JSR和Hibernate提供的约束注解解释说明:【小家Java】深入了解数据校验(Bean Validation):从深处去掌握@Valid的作用(级联校验)以及常用约束注解的解释说明


自定义一个约束分如下三步(说是2步也成):


  1. 自定义一个约束注解
  2. 实现一个校验器(实现接口:ConstraintValidator)
  3. 定义默认的校验错误信息


给个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}之间(包含边界值)



相关文章
|
4天前
|
Java uml Spring
手写spring第四章-完善bean实例化,自动填充成员属性
手写spring第四章-完善bean实例化,自动填充成员属性
12 0
|
28天前
|
缓存 Java Spring
Spring 框架中 Bean 的生命周期
Spring 框架中 Bean 的生命周期
34 1
|
1月前
|
XML Java 开发者
Spring Boot中的bean注入方式和原理
Spring Boot中的bean注入方式和原理
81 0
|
1月前
|
Java Spring 容器
【Java】Spring如何扫描自定义的注解?
【Java】Spring如何扫描自定义的注解?
36 0
|
3天前
|
Java Spring
Spring Boot脚手架集成校验框架
Spring Boot脚手架集成校验框架
11 0
|
4天前
|
XML Java 数据格式
手写spring第七章-完成便捷实现bean对象初始化和销毁方法
手写spring第七章-完成便捷实现bean对象初始化和销毁方法
6 0
|
5天前
|
XML 人工智能 Java
Spring Bean名称生成规则(含源码解析、自定义Spring Bean名称方式)
Spring Bean名称生成规则(含源码解析、自定义Spring Bean名称方式)
|
10天前
|
JSON Java 数据格式
Spring Boot实现各种参数校验
这些是Spring Boot中实现参数校验的一些常见方法,你可以根据项目需求选择适合的方式来进行参数校验。
13 0
|
13天前
|
Java 数据库连接 开发者
浅谈Spring的Bean生命周期
浅谈Spring的Bean生命周期
20 1
|
18天前
|
XML Java 数据格式
Bean工厂探秘:解析Spring底层工厂体系BeanFactory的神奇之道
Bean工厂探秘:解析Spring底层工厂体系BeanFactory的神奇之道
21 0
Bean工厂探秘:解析Spring底层工厂体系BeanFactory的神奇之道