在本地化的验证错误信息中嵌入字符串
我已经在项目中配置了本地化。我有带占位符的通用校验错误信息,以及字段名,像下面这样。
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}" 中。
编辑:下面有一张图片,解释我想要做的事情。
解决方案
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字符串为空时,验证才会失败并生成消息,因此插值后的消息将始终显示一个空字符串,在上述消息中将不可见。你也可以把它放在引号中,但即便如此,显示空字符串对用户也几乎没有价值。
