在本地化的验证错误信息中嵌入字符串

后端开发 2026-07-09

我已经在项目中配置了本地化。我有带占位符的通用校验错误信息,以及字段名,像下面这样。

src/main/resources/lang/messages.properties:

field.NotBlank=Polje {field} je obavezno.

# ...

email=imejl adresa

一个DTO类,其字段带有用于校验的注解。

src/main/java/com.example.realestate/dtos/auth/LoginDTO:

@Data
@ConfirmPassword
public class RegisterDTO {
    @NotBlank(message = "{email.NotBlank}")
    @Email(message = "{email.Email}")
    private String email;
    @NotBlank(message = "{password.NotBlank}")
    @Password
    private String password;
    @NotBlank(message = "{password2.NotBlank}")
    private String password2;
    @NotBlank(message = "{phone.NotBlank}")
    @Pattern(regexp = "^\\+381 \\d{2} \\d{6,7}$", message = "{phone.Pattern}")
    private String phone;
    @NotBlank(message = "{firstName.NotBlank}")
    @Pattern(regexp = "^\\p{L}*$", message = "{firstName.Alpha}")
    private String firstName;
    @NotBlank(message = "{lastName.NotBlank}")
    @Pattern(regexp = "^\\p{L}*$", message = "{lastName.Alpha}")
    private String lastName;
    @NotNull(message = "{birthDate.NotNull}")
    @Adult
    @DateTimeFormat(pattern = "yyyy-MM-dd")
    private LocalDate birthDate;
}

有一个格式很乱的控制器方法,它使用了这个DTO对象,因为在这个站点上我不能用TAB键重新缩进代码(它会跳到下一个输入框)。

        @PostMapping("/registracija")
public String register(Model model, HttpServletRequest request, RedirectAttributes attributes, @ModelAttribute("dto") @Valid RegisterDTO dto, BindingResult result) {
    // 2 Input validation & sanitization
    if (result.hasErrors()) {
        model.addAttribute("dto", dto);

        // 5 Security logging
        logger.error("Registration failed due to validation errors.");
        return "auth/register";
    }

    if (userServ.existsByEmail(dto.getEmail())) {
        String fail = "Korisnik sa ovom imejl adresom već postoji.";
        attributes.addFlashAttribute("fail", fail);

        // 5 Security logging
        logger.error("Registration failed because an user with the provided email address already exists.");
        return "redirect:/registracija";
    }

    if (userServ.existsByPhone(dto.getPhone())) {
        String fail = "Korisnik sa ovim brojem telefona već postoji.";
        attributes.addFlashAttribute("fail", fail);

        // 5 Security logging
        logger.error("Registration failed because an user with the provided phone number already exists.");
        return "redirect:/registracija";
    }

    CustomUser user = userServ.create(dto);

    try {
        request.login(user.getEmail(), user.getPassword());

        // 5 Security logging
        logger.info("User {} registered successfully.", user);
        return "redirect:/oglasi";
    } catch (ServletException e) {
        throw new RuntimeException(e);
    }
} // [1]

我如何把messages.properties里的email插入到DTO类中的field.NotBlank(来自messages.properties)? 我的意思是在 @NotBlank注解的属性中直接把 "{email}" 嵌入到 "{field.NotBlank}" 中。

编辑:下面有一张图片,解释我想要做的事情。

enter image description here

解决方案

Bean Validation API只使用一个普通的 ResourceBundle。ResourceBundle没有实现你想要的功能的能力。

然而,Bean Validation API允许你 [设置一个自定义的消息插值器类] 你自己编写的。这个类几乎可以实现你想要的任何功能。

在Spring中,你可以编写一个类似这样的类:

import org.springframework.context.annotation.Configuration;
import org.springframework.boot.validation.autoconfigure.ValidationConfigurationCustomizer;

@Configuration
public class TermMessageInterpolatorInstaller
implements ValidationConfigurationCustomizer {
    @Override
    public void customize(jakarta.validation.Configuration<?> config) {
        config.messageInterpolator(new TermMessageInterpolator(
            config.getDefaultMessageInterpolator()));
    }
}

而TermMessageInterpolator类看起来可能是这样的:

import java.util.Locale;
import java.util.Objects;
import java.util.regex.Pattern;
import java.util.regex.Matcher;
import jakarta.validation.MessageInterpolator;

public class TermMessageInterpolator
implements MessageInterpolator {
    private final Pattern substitutionPattern =
        Pattern.compile("\\$?\\{([^}]+)\\}");

    private final MessageInterpolator defaultInterpolator;

    public TermMessageInterpolator(
                        MessageInterpolator defaultInterpolator) {

        this.defaultInterpolator =
            Objects.requireNonNull(defaultInterpolator,
                "Default interpolator cannot be null.");
    }

    @Override
    public String interpolate(String messageTemplate,
                              Context context) {

        return interpolate(messageTemplate, context, Locale.getDefault());
    }

    @Override
    public String interpolate(String messageTemplate,
                              Context context,
                              Locale locale) {

        ResourceBundle res =
            ResourceBundle.getBundle("lang.messages", locale);

        StringBuilder replacedMessage = new StringBuilder();

        Matcher matcher = substitutionPattern.matcher(messageTemplate);
        while (matcher.find()) {
            String paramName = matcher.group(1);
            if (res.containsKey(paramName)) {
                String paramValue = res.getString(paramName);
                paramValue = Matcher.quoteReplacement(paramValue);

                matcher.appendReplacement(replacedMessage, paramValue);
            }
        }
        matcher.appendTail(replacedMessage);

        return defaultInterpolator.interpolator(replaceMessage.toString(),
            context, locale);
    }
}

免责声明:我实际上并没有尝试过这些,因为我无法获取所需的资源。

当然,你也有很多变体方式。你的术语,比如 email=imejl adresa,如果你愿意,可以放在不同的ResourceBundle中。你也可以为术语发明你自己的语法(比如用 [[email]] 代替 {email}),这样替换就不会与标准的Bean Validation参数/EL替换冲突。


(我把原回答中的这一部分保留在这里,以防有人觉得有帮助,但它回答的问题与原问题不同。我误解了问题,以为问题是问如何替换已校验的值,而不是任意附加的ResourceBundle条目。)

这其实并不是一个Spring问题。这是一个 [Bean Validation API] 问题。Bean Validation是 Jakarta EE(前身为Java EE)的一部分,由Sun/Oracle创建。它不是Spring的一部分,也从来不是Spring的一部分,尽管Spring应用可以使用它。

Bean Validation规范的第6.3.1.3节指出,有若干值会提供给EL表达式使用,包括约束注解本身的属性值,以及“映射到名称 validatedValue 的已校验值”。

因此,你可以在你的ResourceBundle属性中写成类似这样的内容:

field.NotBlank=Polje ${validatedValue} je obavezno.

值得注意的是,在 @NotBlank 约束的特定情况下,只有当email字符串为空时,验证才会失败并生成消息,因此插值后的消息将始终显示一个空字符串,在上述消息中将不可见。你也可以把它放在引号中,但即便如此,显示空字符串对用户也几乎没有价值。

站内所有文章版权归属LeftHeroAI导航站,无授权禁止任何主体转载、抄袭、复制内容,亦不得私自架设镜像站点。一经侵权,本站将通过法律途径追责。

相关文章