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

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

前言


一般来说,对于web项目我们都有必要对请求参数进行校验,有的前端使用JavaScript校验,但是为了安全起见后端的校验都是必须的。因此数据校验不仅仅是在web下,在方方面面都是一个重要的点。前端校验有它的JS校验框架(比如我之前用的jQuery Validation Plugin),后端自然也少不了。


前面洋洋洒洒已经把数据校验Bean Validation讲了很多了,如果你已经运用在你的项目中,势必将大大提高生产力吧,本文作为完结篇(不是总结篇)就不用再系统性的介绍Bean Validation他了,而是旨在介绍你在使用过程中不得不关心的周边、细节~


如果说前面是用机,那么本文就有点玩机的意思~

BV(Bean Validation)的使用范围


本次再次强调了这一点(设计思想是我认为特别重要的存在):使用范围。

Bean Validation并不局限于应用程序的某一层或者哪种编程模型, 它可以被用在任何一层, 除了web程序,也可以是像Swing这样的富客户端程序中(GUI编程)。


我抄了一副业界著名的图给大家:


image.png



Bean Validation的目标是简化Bean校验,将以往重复的校验逻辑进行抽象和标准化,形成统一API规范;


说到抽象统一API,它可不是乱来的,只有当你能最大程度的得到公有,这个动作才有意义,至少它一般都是与业务无关的。抽象能力是对程序员分级的最重要标准之一

约束继承


如果子类继承自他的父类,除了校验子类,同时还会校验父类,这就是约束继承(同样适用于接口)。

// child和person上标注的约束都会被执行
public class Child extends Person {
  ...
}


注意:如果子类覆盖了父类的方法,那么子类和父类的约束都会被校验。


约束级联(级联校验)


如果要验证属性关联的对象,那么需要在属性上添加@Valid注解,如果一个对象被校验,那么它的所有的标注了@Valid的关联对象都会被校验,这些对象也可以是数组、集合、Map等,这时会验证他们持有的所有元素。


Demo:

@Getter
@Setter
@ToString
public class Person {
    @NotNull
    private String name;
    @NotNull
    @Positive
    private Integer age;
    @Valid
    @NotNull
    private InnerChild child;
    @Valid // 让它校验List里面所有的属性
    private List<InnerChild> childList;
    @Getter
    @Setter
    @ToString
    public static class InnerChild {
        @NotNull
        private String name;
        @NotNull
        @Positive
        private Integer age;
    }
}


校验程序:


    public static void main(String[] args) {
        Person person = new Person();
        person.setName("fsx");
        Person.InnerChild child = new Person.InnerChild();
        child.setName("fsx-age");
        child.setAge(-1);
        person.setChild(child);
        // 设置childList
        person.setChildList(new ArrayList<Person.InnerChild>(){{
            Person.InnerChild innerChild = new Person.InnerChild();
            innerChild.setName("innerChild1");
            innerChild.setAge(-11);
            add(innerChild);
            innerChild = new Person.InnerChild();
            innerChild.setName("innerChild2");
            innerChild.setAge(-12);
            add(innerChild);
        }});
        Validator validator = Validation.byProvider(HibernateValidator.class).configure().failFast(false)
                .buildValidatorFactory().getValidator();
        Set<ConstraintViolation<Person>> result = validator.validate(person);
        // 输出错误消息
        result.stream().map(v -> v.getPropertyPath() + " " + v.getMessage() + ": " + v.getInvalidValue())
                .forEach(System.out::println);
    }


打印校验失败的消息:


age 不能为null: null
childList[0].age 必须是正数: -11
child.age 必须是正数: -1
childList[1].age 必须是正数: -12


约束失败消息message自定义

每个约束定义中都包含有一个用于提示验证结果的消息模版message,并且在声明一个约束条件的时候,你可以通过这个约束注解中的message属性来重写默认的消息模版(这是自定义message最简单的一种方式)。


如果在校验的时候,这个约束条件没有通过,那么你配置的MessageInterpolator插值器会被用来当成解析器来解析这个约束中定义的消息模版, 从而得到最终的验证失败提示信息。

默认使用的插值器是org.hibernate.validator.messageinterpolation.ResourceBundleMessageInterpolator,它借助org.hibernate.validator.spi.resourceloading.ResourceBundleLocator来获取到国际化资源属性文件从而填充模版内容~


资源解析器默认使用的实现是PlatformResourceBundleLocator,在配置Configuration初始化的时候默认被赋值:


  private ConfigurationImpl() {
    this.validationBootstrapParameters = new ValidationBootstrapParameters();
    // 默认的国际化资源文件加载器USER_VALIDATION_MESSAGES值为:ValidationMessages
    // 这个值就是资源文件的文件名~~~~
    this.defaultResourceBundleLocator = new PlatformResourceBundleLocator(
        ResourceBundleMessageInterpolator.USER_VALIDATION_MESSAGES
    );
    this.defaultTraversableResolver = TraversableResolvers.getDefault();
    this.defaultConstraintValidatorFactory = new ConstraintValidatorFactoryImpl();
    this.defaultParameterNameProvider = new DefaultParameterNameProvider();
    this.defaultClockProvider = DefaultClockProvider.INSTANCE;
  }


这个解析器会尝试解析模版中的占位符( 大括号括起来的字符串,形如这样{xxx})。

它解析message的核心代码如下(比如此处message模版是{javax.validation.constraints.NotNull.message}为例):


public abstract class AbstractMessageInterpolator implements MessageInterpolator {
  ...
  private String interpolateMessage(String message, Context context, Locale locale) throws MessageDescriptorFormatException {
    // 如果message消息木有占位符,那就直接返回  不再处理了~
    // 这里自定义的优先级是最高的~~~
    if ( message.indexOf( '{' ) < 0 ) {
      return replaceEscapedLiterals( message );
    }
    // 调用resolveMessage方法处理message中的占位符和el表达式
    if ( cachingEnabled ) {
      resolvedMessage = resolvedMessages.computeIfAbsent( new LocalizedMessage( message, locale ), lm -> resolveMessage( message, locale ) );
    } else {
      resolvedMessage = resolveMessage( message, locale );
    } 
    ...
  }
  private String resolveMessage(String message, Locale locale) {
    String resolvedMessage = message;
    // 获取资源ResourceBundle三部曲
    ResourceBundle userResourceBundle = userResourceBundleLocator.getResourceBundle( locale );
    ResourceBundle constraintContributorResourceBundle = contributorResourceBundleLocator.getResourceBundle( locale );
    ResourceBundle defaultResourceBundle = defaultResourceBundleLocator.getResourceBundle( locale );
    ...
  }
}


对如上message的处理步骤大致总结如下:


  1. 若没占位符符号{需要处理,直接返回(比如我们自定义message属性值全是文字,就直接返回了)~有占位符或者EL,交给resolveMessage()方法从资源文件里拿内容来处理~
  2. 拿取资源文件,按照如下三个步骤寻找:1. userResourceBundleLocator:去用户自己的classpath里面去找资源文件(默认名字是ValidationMessages.properties,当然你也可以使用国际化名)2. contributorResourceBundleLocator:加载贡献的资源包3. defaultResourceBundle:默认的策略。去这里于/org/hibernate/validator加载ValidationMessages.properties
  3. 需要注意的是,如上是加载资源的顺序。无论怎么样,这三处的资源文件都会加载进内存的(并无短路逻辑)。进行占位符匹配的时候,依旧遵守这规律:1. 最先用自己当前项目classpath下的资源去匹配资源占位符,若没匹配上再用下一级别的资源~~~2. 规律同上,依次类推,递归的匹配所有的占位符(若占位符没匹配上,原样输出,并不是输出null哦~)


需要注意的是,因为{在此处是特殊字符,若你就想输出{,请转义:\{



相关文章
|
26天前
|
XML 安全 Java
|
4天前
|
XML Java 数据格式
使用idea中的Live Templates自定义自动生成Spring所需的XML配置文件格式
本文介绍了在使用Spring框架时,如何通过创建`applicationContext.xml`配置文件来管理对象。首先,在resources目录下新建XML配置文件,并通过IDEA自动生成部分配置。为完善配置,特别是添加AOP支持,可以通过IDEA的Live Templates功能自定义XML模板。具体步骤包括:连续按两次Shift搜索Live Templates,配置模板内容,输入特定前缀(如spring)并按Tab键即可快速生成完整的Spring配置文件。这样可以大大提高开发效率,减少重复工作。
使用idea中的Live Templates自定义自动生成Spring所需的XML配置文件格式
|
4天前
|
设计模式 XML Java
【23种设计模式·全精解析 | 自定义Spring框架篇】Spring核心源码分析+自定义Spring的IOC功能,依赖注入功能
本文详细介绍了Spring框架的核心功能,并通过手写自定义Spring框架的方式,深入理解了Spring的IOC(控制反转)和DI(依赖注入)功能,并且学会实际运用设计模式到真实开发中。
【23种设计模式·全精解析 | 自定义Spring框架篇】Spring核心源码分析+自定义Spring的IOC功能,依赖注入功能
|
3天前
|
存储 Java Spring
【Spring】获取Bean对象需要哪些注解
@Conntroller,@Service,@Repository,@Component,@Configuration,关于Bean对象的五个常用注解
|
3天前
|
存储 Java 应用服务中间件
【Spring】IoC和DI,控制反转,Bean对象的获取方式
IoC,DI,控制反转容器,Bean的基本常识,类注解@Controller,获取Bean对象的常用三种方式
|
11天前
|
NoSQL Java Redis
Spring Boot 自动配置机制:从原理到自定义
Spring Boot 的自动配置机制通过 `spring.factories` 文件和 `@EnableAutoConfiguration` 注解,根据类路径中的依赖和条件注解自动配置所需的 Bean,大大简化了开发过程。本文深入探讨了自动配置的原理、条件化配置、自定义自动配置以及实际应用案例,帮助开发者更好地理解和利用这一强大特性。
56 14
|
9天前
|
XML Java 数据格式
Spring容器Bean之XML配置方式
通过对以上内容的掌握,开发人员可以灵活地使用Spring的XML配置方式来管理应用程序的Bean,提高代码的模块化和可维护性。
42 6
|
10天前
|
XML Java 数据格式
🌱 深入Spring的心脏:Bean配置的艺术与实践 🌟
本文深入探讨了Spring框架中Bean配置的奥秘,从基本概念到XML配置文件的使用,再到静态工厂方式实例化Bean的详细步骤,通过实际代码示例帮助读者更好地理解和应用Spring的Bean配置。希望对你的Spring开发之旅有所助益。
60 3
|
24天前
|
安全 Java 开发者
Spring容器中的bean是线程安全的吗?
Spring容器中的bean默认为单例模式,多线程环境下若操作共享成员变量,易引发线程安全问题。Spring未对单例bean做线程安全处理,需开发者自行解决。通常,Spring bean(如Controller、Service、Dao)无状态变化,故多为线程安全。若涉及线程安全问题,可通过编码或设置bean作用域为prototype解决。
32 1
|
Java Spring
Spring中使用自定义的注解校验器的实现
转自:http://blog.csdn.net/hu2010shuai/article/details/52925545
813 0