消息插值是为违反Jakarta Bean验证约束创建错误消息的过程。在本篇文章中,你将了解如何定义和解析这些消息,以及在默认的方法不能满足您的需求时,如何自定义消息内插器。
一、缺省的消息内插器
约束违反消息来源于所谓的消息描述器。每个约束使用这个message属性,定义它自己的消息描述器。
在声明的时候,这个缺省的描述器可以通过一个指定的值,被重写。看下面例子:
public class Car { @NotNull(message = "The manufacturer name must not be null") private String manufacturer; }
如果一个约束被违反,它的描述器会被验证引擎使用的当前配置的MessageInterpolator插入。这个插入的错误消息可以通过调用ConstraintViolation#getMessage()方法,从产生的约束违例中检索。
消息描述器可以包含容器参数,也可以包含消息表达式,它们可以在插入期间被解析。消息参数是字符串常量,存放在{}中,而消息表达式是字符串常量,并被包含在${}中使用。
下面的算法在方法插入期间被应用。
- 使用任何消息参数作为资源包ValidationMessages的键来解析它们。如果这个包包含一个给定的消息参数的实体,那么这个参数可以应用在相关的资源包中的值中。这一步可以被递归的执行以代替约束中的消息参数。这个资源包是开发者提供,因此,通过增加一个名为ValidationMessage.properties在类路径中。你也可以创建一个本地的错误消息,通过提供一个这个包的特定地区变体,例如ValidationMessages_en_US.properties. 如果不设置的话,这个JVM的默认的局部的(Locale#getDefault)将会被使用,在包中寻找消息。
- 使用任何消息参数作为资源包ValidationMessages的键来解析它们,资源包包含标准的错误信息,在内建约束中被定义在附录B中,在Jakarta Bean Validation 规范中。在Hibernate Valdiator,这个资源包叫做org.hibernate.validator.ValidationMessages. 如果这一步触发了一个替换,步骤一会被再次执行,否则步骤三会被应用。
- 解析任何消息参数,使用约束注解中相同名字的值替换他们的值。这允许推断约束中属性的值(例如,Size#mis())在错误消息中(例如必须至少大于${min})。
- 解析任何消息表达式,解析他们使用统一表达式语言。
1.1 特殊字符
既然这个“{”,"}"和"$"有一个特殊的含义,在消息表达器中。如果你想按字面意思使用它们,那么它们需要被转义。下面的规则可以使用:
- “\{”可以认为是"{"
- “\}”可以认为是"}"
- \$可以认为是$
- \\可以认为是\
1.2 内插消息表达式
由于Hibernate Validator5(Bean Validation 1.1),它可以使用Jakarta 表达式语言,在约束消息中。这允许基于条件逻辑和使用高级的形式定义错误消息。这个验证引擎使下面的选项有效在EL(表达式语言)环境中。
- 属性值和属性的名字映射对应起来。
- 当前的校验的值(属性,bean,方法参数等等)在名为validatedValue下面。
- 一个bean映射了一个名字格式器,通过解析一个参数方法format(String format, Object... args),它表现的就像java.util.Format.format(String format, Object...args).
下面的部分,提供了一些使用EL表达式的例子,在错误信息中。
1.3 例子
package org.hibernate.validator.referenceguide.chapter04.complete; public class Car { @NotNull private String manufacturer; @Size( min = 2, max = 14, message = "The license plate '${validatedValue}' must be between {min} and {max} characters long" ) private String licensePlate; @Min( value = 2, message = "There must be at least {value} seat${value > 1 ? 's' : ''}" ) private int seatCount; @DecimalMax( value = "350", message = "The top speed ${formatter.format('%1$.2f', validatedValue)} is higher than {value}" ) private double topSpeed; @DecimalMax(value = "100000", message = "Price must not be higher than ${value}") private BigDecimal price; public Car( String manufacturer, String licensePlate, int seatCount, double topSpeed, BigDecimal price) { this.manufacturer = manufacturer; this.licensePlate = licensePlate; this.seatCount = seatCount; this.topSpeed = topSpeed; this.price = price; } //getters and setters ... }
验证无效的Car实例会导致对消息的约束违反:
- 这个@NotNull约束在manufacturer成员变量上,引起了错误消息是“must not be null”,因为这个默认的消息定义在Jakarta Bean Validation规范中,并且没有指定描述器在这个消息属性上。
- 这个@Size约束应用在licensePlate成员变量上,展示了内插的消息参数({min},{max})和如何增加校验的值在错误信息中,通过使用EL表达式${validatedValue}.
- 这个@Min约束应用在seatCount上,如何使用带有三元表达式的EL表达式来动态地选择单复数形式,这取决于约束的一个属性(“There must be at 1 seat ” vs "There must be at least 2 seats")。
- 在应用在topSpeed上的@DecimalMax约束的消息展示了如何格式化校验了的值,使用格式化实例。
- 最终,这个@DecimalMax注解应用在price上展示了内插参数表达式优于表达式求值,导致$标识显示在maximum price的前面。
看下面的例子:期待的错误信息。
Car car = new Car( null, "A", 1, 400.123456, BigDecimal.valueOf( 200000 ) ); String message = validator.validateProperty( car, "manufacturer" ) .iterator() .next() .getMessage(); assertEquals( "must not be null", message ); message = validator.validateProperty( car, "licensePlate" ) .iterator() .next() .getMessage(); assertEquals( "The license plate 'A' must be between 2 and 14 characters long", message); message = validator.validateProperty( car, "seatCount" ) .iterator() .next() .getMessage(); assertEquals( "There must be at least 2 seats", message ); message = validator.validateProperty( car, "topSpeed" ) .iterator() .next() .getMessage(); assertEquals( "The top speed 400.12 is higher than 350", message ); message = validator.validateProperty( car, "price" ) .iterator() .next(.getMessage(); assertEquals( "Price must not be higher than $100000", message );