1. 概述

Java 的 JSR 380(Bean Validation 2.0)规范支持在验证消息中使用参数插值(interpolation),允许我们在错误消息中动态填充约束的属性值,比如 {min}{max} 等。

当我们使用 Hibernate Validator 作为实现时,如果要在消息中使用 EL 表达式(如 ${validatedValue}),就必须引入 JSR 341(Expression Language API)的实现库,例如 javax.el 相关依赖。

但问题来了:如果你的项目根本不需要复杂的表达式解析,只是为了用 {min} 这种简单参数,却还得引入一整个 EL 库,属实有点重了,属于“杀鸡用牛刀”。

✅ 解决方案:使用 Hibernate Validator 内置的 ParameterMessageInterpolator,它支持参数插值但不解析 EL 表达式,因此 无需额外引入 EL 依赖,简单粗暴又高效。

本文就带你搞明白怎么用它,避开不必要的依赖踩坑。


2. 消息插值器(Message Interpolator)简介

Bean Validation 规范中的 MessageInterpolator 接口,负责将验证失败时的消息模板中的占位符(如 {min})替换为实际值。

默认情况下,Hibernate Validator 使用支持 EL 表达式的插值器(比如 ResourceBundleMessageInterpolator),但前提是项目中必须有 EL API 和实现(如 org.glassfish:jakarta.el)。

⚠️ 踩坑提示:Spring Boot 2.3+ 默认不再自带 EL 依赖,如果你用了自定义消息模板但没加 EL,运行时会报错。

ParameterMessageInterpolator 是 Hibernate Validator 提供的一个轻量实现:

  • ✅ 支持 {attribute} 形式的参数插值(如 {min}{max}{value}
  • ❌ 不支持 ${} 形式的 EL 表达式(如 ${validatedValue}
  • 🚀 无需任何额外依赖,开箱即用

所以,如果你只是想用 {min} 这种基础参数,完全可以用它替代默认插值器,省事又干净。


3. 自定义消息插值器配置方式

要启用 ParameterMessageInterpolator,只需要在创建 ValidatorFactoryValidator 时显式指定即可。以下是两种常用方式。

3.1. 配置 ValidatorFactory

在初始化 ValidatorFactory 时,通过 configure() 设置自定义插值器:

ValidatorFactory validatorFactory = Validation.byDefaultProvider()
    .configure()
    .messageInterpolator(new ParameterMessageInterpolator())
    .buildValidatorFactory();

这种方式适用于全局配置,后续所有通过该工厂创建的 Validator 实例都会使用该插值器。

3.2. 配置 Validator 实例

你也可以在已有 ValidatorFactory 的基础上,为某个特定的 Validator 实例设置插值器:

Validator validator = validatorFactory.usingContext()
    .messageInterpolator(new ParameterMessageInterpolator())
    .getValidator();

这种方式更灵活,适合需要不同插值行为的场景(比如测试中临时切换)。


4. 验证实战

接下来我们通过一个实际的 Java Bean 来验证 ParameterMessageInterpolator 的效果。

4.1. 示例 Java Bean:Person

public class Person {

    @Size(min = 10, max = 100, message = "Name should be between {min} and {max} characters")
    private String name;

    @Min(value = 18, message = "Age should not be less than {value}")
    private int age;

    @Email(message = "Email address should be in a correct format: ${validatedValue}")
    private String email;

    // standard getters and setters
}

注意看三个注解中的 message

  • {min}{max}{value}:标准参数,会被 ParameterMessageInterpolator 正常替换
  • ${validatedValue}:EL 表达式,需要 EL 支持才能解析

4.2. 测试参数插值功能

先获取我们配置好的 Validator

Validator validator = validatorFactory.getValidator();

测试 name 字段(验证 {min} 和 {max})

@Test
public void givenNameLengthLessThanMin_whenValidate_thenValidationFails() {
    Person person = new Person();
    person.setName("John Doe"); // 长度为8,小于min=10
    person.setAge(18);

    Set<ConstraintViolation<Person>> violations = validator.validate(person);
 
    assertEquals(1, violations.size());

    ConstraintViolation<Person> violation = violations.iterator().next();
 
    assertEquals("Name should be between 10 and 100 characters", violation.getMessage());
}

✅ 输出结果:

Name should be between 10 and 100 characters

说明 {min}{max} 已被正确替换为 10100

测试 age 字段(验证 {value})

@Test
public void givenAgeIsLessThanMin_whenValidate_thenValidationFails() {
    Person person = new Person();
    person.setName("John Stephaner Doe");
    person.setAge(16); // 小于18

    Set<ConstraintViolation<Person>> violations = validator.validate(person);
 
    assertEquals(1, violations.size());

    ConstraintViolation<Person> violation = violations.iterator().next();
 
    assertEquals("Age should not be less than 18", violation.getMessage());
}

✅ 输出结果:

Age should not be less than 18

{value} 被替换为 18,一切正常。

4.3. 测试表达式(${validatedValue})行为

现在我们测试 email 字段,故意传一个格式错误的邮箱:

@Test
public void givenEmailIsMalformed_whenValidate_thenValidationFails() {
    Person person = new Person();
    person.setName("John Stephaner Doe");
    person.setAge(18);
    person.setEmail("johndoe.dev"); // 明显不是合法邮箱
    
    Set<ConstraintViolation<Person>> violations = validator.validate(person);
 
    assertEquals(1, violations.size());
    
    ConstraintViolation<Person> violation = violations.iterator().next();
 
    assertEquals("Email address should be in a correct format: ${validatedValue}", violation.getMessage());
}

❌ 实际输出:

Email address should be in a correct format: ${validatedValue}

⚠️ 注意:${validatedValue} 没有被解析,原样返回。

原因很简单:ParameterMessageInterpolator 不支持 EL 表达式解析,遇到 ${} 直接跳过,保留原文。

💡 小建议:如果你确实需要 ${validatedValue} 这种功能,再引入 EL 依赖也不迟。否则,建议直接写静态提示,比如 "Email format is invalid",更简洁可靠。


5. 总结

特性 是否支持
{min}, {max}, {value} 等参数插值
${validatedValue} 等 EL 表达式
是否需要额外依赖 ❌(完全不需要 EL)
适用场景 大多数简单验证场景

📌 核心结论:

  • 如果你只是想用 {min} 这类参数替换,强烈推荐使用 ParameterMessageInterpolator
  • 它轻量、高效、无需依赖,避免项目被 EL 库“污染”
  • 一旦你用了 ${} 表达式,就必须引入 EL 实现,否则表达式不会被解析

项目代码已上传至 GitHub:https://github.com/tech-tutorial/hikaricp-guide(示例代码可参考 javaxval-2 模块)


原始标题:Guide to ParameterMessageInterpolator