参考资料
⏹配置文件,指定国际化文件存储的路径
spring:
messages:
# 指定国际化信息存储的路径(resources/i18n/messages开头的文件)
basename: i18n/messages
encoding: UTF-8
⏹配置类,国际化相关配置
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.LocaleResolver;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import org.springframework.web.servlet.i18n.LocaleChangeInterceptor;
import org.springframework.web.servlet.i18n.SessionLocaleResolver;
import java.util.Locale;
@Configuration
public class InternationalConfig implements WebMvcConfigurer {
// 默认解析器,用来设置当前会话默认的国际化语言
@Bean
public LocaleResolver localeResolver() {
SessionLocaleResolver sessionLocaleResolver = new SessionLocaleResolver();
// 指定当前项目的默认语言是中文
sessionLocaleResolver.setDefaultLocale(Locale.SIMPLIFIED_CHINESE);
return sessionLocaleResolver;
}
// 默认拦截器,用来指定切换国际化语言的参数名
@Bean
public LocaleChangeInterceptor localeChangeInterceptor() {
LocaleChangeInterceptor localeChangeInterceptor = new LocaleChangeInterceptor();
/*
设置国际化请求参数为language
设置完成之后,URL中的 ?language=zh 表示读取国际化文件messages_zh.properties
*/
localeChangeInterceptor.setParamName("language");
return localeChangeInterceptor;
}
// 将我们自定义的国际化语言参数拦截器放入Spring MVC的默认配置中
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(localeChangeInterceptor());
}
}
⏹国际化信息
messages_zh.properties
# エラーE
1001E=请输入{msgArgs}。
1002E=请选择{msgArgs}。
1003E=请输入{msgArgs}全角假名。
1004E=输入的{msgArgs}日期格式不正确。
1005E=请输入半角数字。
1006E={msgArgs}最多不能超过{max}文字。
1007E={0}と{1}的大小关系不正确。
1008E=年龄fromと年龄to的大小关系不正确。
# item
1001Item=日期from
1002Item=日期to
1003Item=中文系统
messages_jp.properties
# エラーE
1001E={msgArgs}を入力してください。
1002E={msgArgs}を選択してください。
1003E={msgArgs}は全角カタカナまたは半角アルファベットで入力してください。
1004E=入力された{msgArgs}日付は妥当ではありません。
1005E=半角数字を入力してください。
1006E={msgArgs}を{max}文字以内で入力してください。
1007E={0}と{1}の大小関係が逆らいました。
1008E=年齢fromと年齢toの大小関係が逆らいました。
# item
1001Item=日付from
1002Item=日付to
1003Item=日本語システム
⏹国际化消息获取共通方法
import org.springframework.context.MessageSource;
import org.springframework.context.i18n.LocaleContextHolder;
import org.springframework.context.support.ReloadableResourceBundleMessageSource;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
@Component
public class LocaleMessageSourceService {
@Resource
private MessageSource messageSource;
/**
* 根据code获取对应的message
* @param code 信息code
* @return message
*/
public String getMessage(String code) {
return getMessage(code, null);
}
/**
* 根据code和参数获取对应的message
* @param code 信息code
* @param args 参数
* @return message
*/
public String getMessage(String code, Object[] args) {
return getMessage(code, args, "");
}
/**
* 根据code和参数获取对应的message
* @param code 信息code
* @param args 参数
* @param defaultMessage 默认信息
* @return message
*/
public String getMessage(String code, Object[] args, String defaultMessage) {
// 这里使用比较方便的方法,不依赖request.
Locale locale = LocaleContextHolder.getLocale();
return messageSource.getMessage(code, args, defaultMessage, locale);
}
/**
* 当不在配置文件中指定messages.basename,而在配置类中指定的时候
* MessageSource会无法自动注入,此时使用此方法获取message
* @param code 信息code
* @param args 参数
* @return message
*/
public String getMsg(String code, Object[] args) {
ReloadableResourceBundleMessageSource messageSource = new ReloadableResourceBundleMessageSource();
messageSource.setCacheSeconds(-1);
messageSource.setDefaultEncoding(StandardCharsets.UTF_8.name());
// 设置国际化信息文件存在的路径,i18n文件夹下messages开头的文件
messageSource.setBasenames("/i18n/messages");
String message = "";
try {
message = messageSource.getMessage(code, args, LocaleContextHolder.getLocale());
} catch (Exception e) {
e.printStackTrace();
return "";
}
return message;
}
/**
* 根据code获取页面的项目名称数组
* @param args 信息code
* @return 项目名称数组
*/
public Object[] getItemName(String...args) {
List<Object> objects = new ArrayList<>();
for (String arg : args) {
objects.add(getMessage(arg));
}
return objects.toArray();
}
}
⏹前台
DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<script type="text/javascript" th:src="@{/js/public/jquery-3.6.0.min.js}">script>
<script type="text/javascript" th:src="@{/js/common/common.js}">script>
<title>国际化校验title>
head>
<body>
<button id="btn1">发送请求,通过自定义类注解进行国际化校验button>
<button id="btn2">发送请求,通过@ScriptAssert进行非国际化校验button>
<hr>
<label for="fromAge">年龄fromlabel><input id="fromAge" type="number" /><br>
<label for="toAge">tolabel><input id="toAge" type="number" /><br>
<hr>
<label for="fromDate">开始日期label><input id="fromDate" type="number" /><br>
<label for="toDate">结束日期label><input id="toDate" type="number" /><br>
<hr>
<hr>
<div>[[#{1003Item}]]div>
body>
<script>
let language1 = false;
$("#btn1").click(() => {
const param = {
fromAge: $("#fromAge").val(),
toAge: $("#toAge").val(),
fromDate: $("#fromDate").val(),
toDate: $("#toDate").val(),
};
// 切换语言状态,中日文语言切换
language1 = !language1;
const url = `http://localhost:8080/test6/validateCustomAnnotation?language=${language1 ? 'zh' : 'jp'}`;
doAjax(url, param, function(data) {
console.log(data);
});
});
let language2 = false;
$("#btn2").click(() => {
const param = {
fromAge: $("#fromAge").val(),
toAge: $("#toAge").val(),
fromDate: $("#fromDate").val(),
toDate: $("#toDate").val(),
newPassword: "110120",
oldPassword: "110120"
};
language2 = !language2;
const url = `http://localhost:8080/test6/validateScriptAssert?language=${language2 ? 'zh' : 'jp'}`;
doAjax(url, param, function(data) {
console.log(data);
});
});
script>
html>
⏹待校验的form
import com.example.jmw.common.validation.ValidateNotEmpty;
import com.example.jmw.form.validation.ValidTest6Form1;
import lombok.Data;
@Data
// 自定义的类注解,用于类中的多个属性联合校验
@ValidTest6Form1
public class Test6Form1 {
// 属性校验注解
@ValidateNotEmpty(msgArgs = "id")
private String id;
private Integer fromAge;
private Integer toAge;
private Integer fromDate;
private Integer toDate;
}
⏹自定义一个注解,该注解作用于类上
import com.example.jmw.common.utils.LocaleMessageSourceService;
import com.example.jmw.form.Test6Form1;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.ObjectUtils;
import javax.validation.Constraint;
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
import javax.validation.Payload;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.util.ArrayList;
import java.util.List;
@Target({ElementType.TYPE, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = {ValidTest6Form1.ValidTest6Form1Validator.class})
public @interface ValidTest6Form1 {
String message() default "";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
// 内部类
class ValidTest6Form1Validator implements ConstraintValidator<ValidTest6Form1, Test6Form1> {
// 注入自定义的国际化信息获取方法
@Autowired
private LocaleMessageSourceService localeMessageSourceService;
@Override
public void initialize(ValidTest6Form1 constraintAnnotation) {
}
@Override
public boolean isValid(Test6Form1 value, ConstraintValidatorContext context) {
if (ObjectUtils.isEmpty(value)) {
return true;
}
// 取消默认的校验提示消息
context.disableDefaultConstraintViolation();
List<Boolean> checkResultList = new ArrayList<>();
// 校验from日期和to日期的大小关系
Integer fromDate = value.getFromDate();
Integer toDate = value.getToDate();
if (!ObjectUtils.isEmpty(fromDate) && !ObjectUtils.isEmpty(toDate) && fromDate > toDate) {
// 获取from日期和to日期对应的国际化项目名称
Object[] args = localeMessageSourceService.getItemName("1001Item", "1002Item");
// 获取校验信息
String message = localeMessageSourceService.getMessage("1007E", args);
// 将校验信息添加到模板中
context.buildConstraintViolationWithTemplate(message).addConstraintViolation();
checkResultList.add(false);
}
// 校验from年龄和to年龄的大小关系
Integer fromAge = value.getFromAge();
Integer toAge = value.getToAge();
if (!ObjectUtils.isEmpty(fromAge) && !ObjectUtils.isEmpty(toAge) && fromAge > toAge) {
// 如果校验信息不需要传参数的话,可直接写国际化文件中对应的code
context.buildConstraintViolationWithTemplate("{1008E}")
.addConstraintViolation();
checkResultList.add(false);
}
return !checkResultList.contains(false);
}
}
}
⏹controller层校验
@Controller
@RequestMapping("/test6")
public class Test6Controller {
// 注入校验类
@Resource
private LocalValidatorFactoryBean validator;
@GetMapping("/init")
public ModelAndView init() {
ModelAndView modelAndView = new ModelAndView();
modelAndView.setViewName("test6");
return modelAndView;
}
@PostMapping("/validateCustomAnnotation")
@ResponseBody
public void validateCustomAnnotation(@RequestBody Test6Form1 form) {
Set<ConstraintViolation<Test6Form1>> validate = validator.validate(form);
for (ConstraintViolation<Test6Form1> bean : validate) {
// 获取当前的校验信息
String message = bean.getMessage();
System.out.println(message);
}
System.out.println(validate);
}
}
⏹效果
点击发送请求,通过自定义类注解进行国际化校验,此时返回中文校验信息
再次点击发送请求,通过自定义类注解进行国际化校验,此时返回日文校验信息
上述两次点击的请求url
⏹待校验的form
import lombok.Data;
import org.hibernate.validator.constraints.ScriptAssert;
@Data
// 当有多个业务校验的时候使用 .List()
@ScriptAssert.List({
/*
* 使用javascript脚本来完成校验
* 默认_this相当于当前类对象
* 可以通过alias取一个别名
*/
@ScriptAssert(lang = "javascript"
, alias = "_"
, script = "_.fromAge <= _.toAge"
, message="From年龄值不能比to年龄大"),
// 因为使用javascript脚本来完成校验,因此比较相等才可以使用js中的 !==
@ScriptAssert(lang = "javascript"
, script = "_this.oldPassword !== _this.newPassword"
, message="新旧密码不能相同"),
@ScriptAssert(lang = "javascript"
, script = "_this.fromDate <= _this.tomDate"
, message="From日期不能比to日期大"),
})
public class Test6Form2 {
private Integer fromAge;
private Integer toAge;
private Integer fromDate;
private Integer toDate;
private String oldPassword;
private String newPassword;
}
⏹controller层校验
@Controller
@RequestMapping("/test6")
public class Test6Controller {
@Resource
private LocalValidatorFactoryBean validator;
@GetMapping("/init")
public ModelAndView init() {
ModelAndView modelAndView = new ModelAndView();
modelAndView.setViewName("test6");
return modelAndView;
}
@PostMapping("/validateScriptAssert")
@ResponseBody
public void validateScriptAssert(@RequestBody Test6Form2 form) {
Set<ConstraintViolation<Test6Form2>> validate = validator.validate(form);
for (ConstraintViolation<Test6Form2> bean : validate) {
// 获取当前的校验信息
String message = bean.getMessage();
System.out.println(message);
}
System.out.println(validate);
}
}
指定国际化文件存储的路径的方式一共有两种
一种是在配置文件中指定
spring:
messages:
# 指定国际化信息存储的路径(resources/i18n/messages开头的文件)
basename: i18n/messages
encoding: UTF-8
另外一种是通过配置类的方式指定
@Configuration
public class InternationalConfig implements WebMvcConfigurer {
// 自定义国际化环境下要显示的校验消息
@Bean
public LocalValidatorFactoryBean localValidatorFactoryBean() {
LocalValidatorFactoryBean localValidatorFactoryBean = new LocalValidatorFactoryBean();
// 使用Spring加载国际化资源文件
ResourceBundleMessageSource messageSource = new ResourceBundleMessageSource();
messageSource.setBasename("messages");
messageSource.setDefaultEncoding("UTF-8");
localValidatorFactoryBean.setValidationMessageSource(messageSource);
return localValidatorFactoryBean;
}
}