1. 概述

Message interpolation,直译为”消息插值“。是Java Bean验证参数约束并创建错误提示消息的过程。

本文我们将学习如何使用 Spring 默认的插值器来格式化错误消息,以及如何实现自己的插值器。

2. 默认Message Interpolation

开始之前,我们先看一下Spring默认的HTTP 400响应示例,其中违反了 @NotNull 约束:

{
    ....
    "status": 400,
    "error": "Bad Request",
    "errors": [
        {
            ....
            "defaultMessage": "must not be null",
            ....
        }
    ],
    "message": "Validation failed for object='notNullRequest'. Error count: 1",
    ....
}

每个约束都具有一个 message 字段用于描述错误,Spring从中读取违法约束的错误详情,我们可以自定义,否则使用系统默认生成的。

例如下面的例子,我们创建一个POST接口:

@RestController
public class RestExample {

    @PostMapping("/test-not-null")
    public void testNotNull(@Valid @RequestBody NotNullRequest request) {
        // ...
    }
}

并对请求参数NotNullRequestString字段添加@NotNull约束:

public class NotNullRequest {

    @NotNull(message = "stringValue不能为空")
    private String stringValue;

    // getters, setters
}

当POST请求为空时时,我们将看到我们的自定义错误消息:

{
    ...
    "errors": [
        {
            ...
            "defaultMessage": "stringValue不能为空",
            ...
        }
    ],
    ...
}

唯一变化的是defaultMessage。但我们仍然会得到很多关于错误代码、对象名称、字段名称等的信息。要限制显示的值的数量,我们可以实现REST API的自定义错误消息处理

3. 使用表达式格式化消息

Spring 支持 使用统一表达语言来定义消息描述,包括 使用逻辑条件表达式,等高级格式功能。

为了更清楚地理解,让我们看几个例子。

在每个约束注解中,我们可以读取正在验证的字段的实际输入的值:

@Size(
  min = 5,
  max = 14,
  message = "作者邮箱 '${validatedValue}' 必须介于 {min} 到 {max} 个字符之间"
)
private String authorEmail;

上面的错误消息模板中读取了参数的实际输入值以及@Size注解的minmax参数:

"defaultMessage": "作者邮箱 'toolongemail@baeldung.com' 必须介于 5 到 14 个字符之间"

注意,对于访问外部变量,我们使用${}语法,而对于访问验证注解中的其他属性,我们使用{}符号。

三元运算符也是支持的:

@Min(
  value = 1,
  message = "There must be at least {value} test{value > 1 ? 's' : ''} in the test case"
)
private int testCount;

输出结果:

"defaultMessage": "There must be at least 2 tests in the test case"

我们还可以调用外部变量上的方法:

@DecimalMin(
  value = "50",
  message = "The code coverage ${formatter.format('%1$.2f', validatedValue)} must be higher than {value}%"
)
private double codeCoverage;

上面的消息格式化结果示例:

"defaultMessage": "The code coverage 44.44 must be higher than 50%"

如果消息中本身包含了 {、}、$ 等特殊字符,需要先使用发斜杠进行转义。如:{, }, $, \。

4. 自定插值器

在某些情况下,我们想要实现自定义的消息插值引擎,以更好的控制错误输出。首先我们需要实现jakarta.validation.MessageInterpolator接口:

public class MyMessageInterpolator implements MessageInterpolator {
    private final MessageInterpolator defaultInterpolator;

    public MyMessageInterpolator(MessageInterpolator interpolator) {
        this.defaultInterpolator = interpolator;
    }

    @Override
    public String interpolate(String messageTemplate, Context context) {
        messageTemplate = messageTemplate.toUpperCase();
        return defaultInterpolator.interpolate(messageTemplate, context);
    }

    @Override
    public String interpolate(String messageTemplate, Context context, Locale locale) {
        messageTemplate = messageTemplate.toUpperCase();
        return defaultInterpolator.interpolate(messageTemplate, context, locale);
    }
}

这个例子很简单,我们只是将错误消息改为大写字母。这样,我们的错误消息将看起来像:

"defaultMessage": "THE CODE COVERAGE 44.44 MUST BE HIGHER THAN 50%"

然后,我们还需要在javax.validation.Validation工厂中注册我们的插值器

Validation.byDefaultProvider().configure().messageInterpolator(
  new MyMessageInterpolator(
    Validation.byDefaultProvider().configure().getDefaultMessageInterpolator())
);

5. 总结

在这篇文章中,我们了解了默认的Spring消息插值的工作原理,以及如何创建自定义的消息插值引擎。

如往常一样,所有的源代码都可以在GitHub上找到。