4.违反约束的信息
该信息是对约束的具体描述,如果不能满足需求,也可以自定义该信息。
4.1.默认的信息
每个约束都有message属性,具有默认值,代表着约束默认的信息,在声明约束时,可以给message赋值,以此覆盖默认的信息。
使用message属性指定特定的消息描述:
package org.hibernate.validator.referenceguide.chapter04;
public class Car {
@NotNull(message = "The manufacturer name must not be null")
private String manufacturer;
//constructor, getters and setters ...
}
当违反约束时,通过调用ConstraintViolation的getMessage()可以获取该信息。
信息的描述字符串中可以包含信息参数以及信息表达式。信息参数是包含在其中的字符串文字{},而信息表达式是包含在其中的字符串文字${}。默认的message interpolator使用以下步骤:
- 从message字符串中获取参数并将其作为键去当前语言环境的ValidationMessages ResourceBundle(通常是根路径下的ValidationMessages_locale参数.properties,例如ValidationMessages_en_US.properties)中查找,如果找到该属性,message字符串中的参数将被替换属性值。该步骤会递归地执行,直到不执行任何替换(以防止属性值中再次含有参数)。
- 从message字符串中获取参数并将其作为键去当前语言环境的Bean Validation提供的内置ResourceBundle中查找,在Hibernate Validator中,这个ResourceBundle被命名为org.hibernate.validator.ValidationMessages。如果找到该属性,message字符串中的参数将被替换属性值。与步骤1相反,步骤2不是递归地处理的。
- 如果步骤2触发了替换,就再一次执行步骤1,否则执行步骤4。
- 参数是从message字符串中提取的。在约束声明中,那些匹配约束属性的名称将被该属性的值所取代。参数比消息表达式优先。例如,在message描述字符串中有
${value}
,当做参数{value}会比当做表达式${value}
要优先。 - 表达式从message字符串中提取并使用表达式语言进行解析。
4.1.1.特殊字符
由于字符{,}和$有特殊意义,所以要使用他们需要进行转义。
\{被认为是字符 {
\}被认为是字符 }
\$被认为是字符 $
\\被认为是字符 \
4.1.2. 信息表达式
在Hibernate Validator 5(Bean Validation 1.1)中,可以在违反约束的信息中可以使用Unified Expression Language(由JSR 341定义)。这将允许根据条件逻辑定义错误消息,并支持高级格式化选项。验证引擎使下列对象在EL上下文中可用:
将约束的属性值映射到属性名称
validatedValue中包含着被当前验证的值(属性、bean、方法参数等等)
format(String format, Object… args)方法,其行为就像java.util.Formatter.format(String format, Object… args)。
4.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 ...
}
违反manufacturer的@NotNull约束将得到错误信息“不能为null”,因为没有覆盖message的值,错误信息是Bean验证规范定义的默认信息。
licensePlate的@Size的message使用了参数和表达式,参数{min}, {max}将被约束中声明的值所替代,参数${validatedValue}将被违反约束的值所替代。
seatCount上的@min约束演示了如何使用一个表达式表达式来动态地选择单数或复数形式,这取决于约束的value属性(“There must be at least 1 seat“There must be at least 2 seats”)
topSpeed上的@DecimalMax约束显示如何使用formatter格式化验证的值。
最后,price上的@DecimalMax约束表明,参数优先于表达式,导致$符号出现在最高价格之前。
预期的错误消息:
Car car = new Car( null, "A", 1, 400.123456, BigDecimal.valueOf( 200000 ) );
String message = validator.validateProperty( car, "manufacturer" )
.iterator()
.next()
.getMessage();
assertEquals( "may 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 );
4.2. 自定义错误消息
如果默认的插入消息的算法不能满足需求,也可以插入自定义的MessageInterpolator实现。
自定义Interpolator必须实现接口javax.validation.MessageInterpolator。注意,必须是线程安全的实现。默认的Interpolator可以通过Configuration#getDefaultMessageInterpolator()获得。
要使用一个自定义的MessageInterpolator必须通过配置Bean Validation XML的描述文件:META-INF/validation.xml进行注册(参阅7.1. 在validation.xml中配置验证器工厂章节)或者在引导一个验证器工厂或者验证器时传递它(分别看8.2.1. MessageInterpolator跟8.3. Configuring a Validator)。
4.2.1. ResourceBundleLocator
在某些时候,需要用Bean Validation规范定义的插值算法(是指4.1章节中的那5个步骤),却从其他的ResourceBundle中检索错误信息而不是从ValidationMessages中检索,这种情况下Hibernate Validator的 ResourceBundleLocator SPI可能有所帮助。
Hibernate Validator默认的消息插值器(Interpolator)是ResourceBundleMessageInterpolator,要检索另外的资源包只需要在引导验证器工厂时传递一个PlatformResourceBundleLocator实例和资源包的名字。
Validator validator = Validation.byDefaultProvider()
.configure()
.messageInterpolator(
new ResourceBundleMessageInterpolator(
new PlatformResourceBundleLocator( "MyMessages" )
)
)
.buildValidatorFactory()
.getValidator();
当然,也可以实现一个完全不同的ResourceBundleLocator,它从数据库中返回记录。这种情况可以通过HibernateValidatorConfiguration#getDefaultResourceBundleLocator()获得默认的定位器。例如可以用自定义的定位器。
除了PlatformResourceBundleLocator,Hibernate Validator提供了另一个资源包定位器实现,即AggregateResourceBundleLocator,它允许从多个资源包中检索错误消息。例如,当你的应用中有多个模块且每个模块都有一个资源包。下面展示了如何使用AggregateResourceBundleLocator:
Validator validator = Validation.byDefaultProvider()
.configure()
.messageInterpolator(
new ResourceBundleMessageInterpolator(
new AggregateResourceBundleLocator(
Arrays.asList(
"MyMessages",
"MyOtherMessages"
)
)
)
)
.buildValidatorFactory()
.getValidator();
注意,传递给包的顺序是有影响的,例如,在几个包中都包含有同一个错误消息,那么它会取第一个包中的值。