国际化(Internationalization 简称 I18n,其中“I”和“n”分别为首末字符,18 则为中间的字符数)是指软件开发时应该具备支持多种语言和地区的功能。换句话说就是,开发的软件需要能同时应对不同国家和地区的用户访问,并根据用户地区和语言习惯,提供相应的、符合用具阅读习惯的页面和数据,例如,为中国用户提供汉语界面显示,为美国用户提供提供英语界面显示。
国际化应用的场景也比较多,比如各种异常提示信息国际化、页面显示字段的国际化、参数校验提示信息国际化等等,下面将详细讲解Spring Boot国际化配置及其使用方法。
1.Spring Boot使用 ResourceBundleMessageSource 管理国际化资源文件
通过国际化的自动化配置MessageSourceAutoConfiguration 对 ResourceBundleMessageSource 提供了默认配置,其部分源码如下:
@Configuration(proxyBeanMethods = false)
@ConditionalOnMissingBean(name = AbstractApplicationContext.MESSAGE_SOURCE_BEAN_NAME, search = SearchStrategy.CURRENT)
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE)
@Conditional(org.springframework.boot.autoconfigure.context.MessageSourceAutoConfiguration.ResourceBundleCondition.class)
@EnableConfigurationProperties
public class MessageSourceAutoConfiguration {
private static final Resource[] NO_RESOURCES = {};
// 将 MessageSourceProperties 以组件的形式添加到容器中
// MessageSourceProperties 下的每个属性都与以 spring.messages 开头的属性对应
@Bean
@ConfigurationProperties(prefix = "spring.messages")
public MessageSourceProperties messageSourceProperties() {
return new MessageSourceProperties();
}
//Spring Boot 会从容器中获取 MessageSourceProperties
// 读取国际化资源文件的 basename(基本名)、encoding(编码)等信息
// 并封装到 ResourceBundleMessageSource 中
@Bean
public MessageSource messageSource(MessageSourceProperties properties) {
ResourceBundleMessageSource messageSource = new ResourceBundleMessageSource();
//读取国际化资源文件的 basename (基本名),并封装到 ResourceBundleMessageSource 中
if (StringUtils.hasText(properties.getBasename())) {
messageSource.setBasenames(StringUtils
.commaDelimitedListToStringArray(StringUtils.trimAllWhitespace(properties.getBasename())));
}
//读取国际化资源文件的 encoding (编码),并封装到 ResourceBundleMessageSource 中
if (properties.getEncoding() != null) {
messageSource.setDefaultEncoding(properties.getEncoding().name());
}
messageSource.setFallbackToSystemLocale(properties.isFallbackToSystemLocale());
Duration cacheDuration = properties.getCacheDuration();
if (cacheDuration != null) {
messageSource.setCacheMillis(cacheDuration.toMillis());
}
messageSource.setAlwaysUseMessageFormat(properties.isAlwaysUseMessageFormat());
messageSource.setUseCodeAsDefaultMessage(properties.isUseCodeAsDefaultMessage());
return messageSource;
}
...
}
从源码中可以窥见国际化的基本细节:
查看 MessageSourceProperties 类源码如下:
public class MessageSourceProperties {
private String basename = "messages";
private Charset encoding;
@DurationUnit(ChronoUnit.SECONDS)
private Duration cacheDuration;
private boolean fallbackToSystemLocale;
private boolean alwaysUseMessageFormat;
private boolean useCodeAsDefaultMessage;
public MessageSourceProperties() {
this.encoding = StandardCharsets.UTF_8;
this.fallbackToSystemLocale = true;
this.alwaysUseMessageFormat = false;
this.useCodeAsDefaultMessage = false;
}
...
}
通过阅读以上,可以得到以下 3 点:
通过以上源码分析,Spring Boot 已经对国际化资源文件的管理提供了默认自动配置,其中不包含"_XXX"区域代码的文件为默认语言配置,我们这里只需要在 Spring Boot 全局配置文件中,使用配置参数“spring.messages.basename”指定我们自定义的国际资源文件的基本名即可,代码如下(当指定多个资源文件时,用逗号分隔):
spring.messages.basename=i18n.messages
2. 区域信息解析器自动配置
我们知道,Spring MVC 进行国际化时有 2 个十分重要的对象,Locale是区域信息对象,LocaleResolver是区域信息解析器,容器中的组件,负责获取区域信息对象,通过这两个对象可以对区域信息进行切换,从而达到切换语言的目的。
Spring Boot 在 WebMvcAutoConfiguration 中为区域信息解析器(LocaleResolver)进行了自动配置,源码如下:
@Bean
@ConditionalOnMissingBean(name = DispatcherServlet.LOCALE_RESOLVER_BEAN_NAME)
@SuppressWarnings("deprecation")
public LocaleResolver localeResolver() {
if (this.webProperties.getLocaleResolver() == WebProperties.LocaleResolver.FIXED) {
return new FixedLocaleResolver(this.webProperties.getLocale());
}
if (this.mvcProperties.getLocaleResolver() == WebMvcProperties.LocaleResolver.FIXED) {
return new FixedLocaleResolver(this.mvcProperties.getLocale());
}
AcceptHeaderLocaleResolver localeResolver = new AcceptHeaderLocaleResolver();
Locale locale = (this.webProperties.getLocale() != null) ? this.webProperties.getLocale()
: this.mvcProperties.getLocale();
localeResolver.setDefaultLocale(locale);
return localeResolver;
}
从以上源码可知:
Springboot国际化存在三种使用方式:
spring:
messages:
basename: i18n/messages #配置国际化资源文件路径
unknown.exception=未知异常,请联系管理员!
user.login.notExists={0} 用户不存在!
英文 messages_en_US.properties
unknown.exception=Unknown error,Please contact the administrator!
user.login.notExists={0} user not exists!
messages.properties文件内容就和简体中文文件一致,如果未设置Locale参数,默认就为该文件内容,此文件也可不用
unknown.exception=未知异常,请联系管理员!
user.login.notExists={0} 用户不存在!
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.MessageSource;
import org.springframework.context.i18n.LocaleContextHolder;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
/**
* @author Mr.bin
* @version 1.0.0
* @ClassName MessageUtils.java
* @Description 国际化支持工具类
* @createTime 2022年04月24日 16:53:00
*/
@Component
public class MessageUtils {
private static MessageSource messageSource;
@Autowired
private MessageSource messageSourceCopy;
@PostConstruct
private void init() {
setMessageSource(messageSourceCopy);
}
private static synchronized void setMessageSource(MessageSource ms) {
messageSource = ms;
}
public static String getMessage(String code, Object ... arg) {
Object[] args = arg.clone();
return messageSource.getMessage(code, args, LocaleContextHolder.getLocale());
}
}
java代码中使用MessageUtils实现国际化操作
Assert.isTrue(count == 0, MessageUtils.getMessage("message.gateway.publish.api.url.exist", requestUrl));
所谓自定义包含两层意思,第一是指改变获取语言参数的方式,例如从session中获取,或者从cookie中获取,第二种是指改变语言参数的名称,例如Spring Boot默认的解析器是获取请求头中的“Accept-Language”参数,如果不想用这个参数想再自定义一个,就采用这种方式。
无论是哪一种自定义LocalResolver都需要定义LocaleChangeInterceptor,用于拦截请求中的语言参数(不配置的话默认是 locale),这个参数则指定了当前的环境信息,例如现在要从"language"这个参数中获取语言信息:
@Bean
public LocaleChangeInterceptor localeChangeInterceptor() {
LocaleChangeInterceptor localeChangeInterceptor = new LocaleChangeInterceptor();
localeChangeInterceptor.setParamName("language");
return localeChangeInterceptor;
}
还需要将LocaleChangeInterceptor添加到应用程序的注册表拦截器中。 配置类应扩展WebMvcConfigurerAdapter类,或者扩展WebMvcConfigurer ,并覆盖addInterceptors()方法。
@Override
public void addInterceptors(InterceptorRegistry registry) {
//添加locale拦截器
registry.addInterceptor(localeChangeInterceptor());
}
接下来需要确定应用程序的LocaleResolver
如果使用CookieLocaleResolver
@Bean
public LocaleResolver localeResolver(){
CookieLocaleResolver localeResolver = new CookieLocaleResolver();
localeResolver.setCookieName("localeCookie");
//设置默认区域
localeResolver.setDefaultLocale(Locale.SIMPLIFIED_CHINESE);
localeResolver.setCookieMaxAge(3600);//设置cookie有效期.
return localeResolver;
}
如果使用SessionLocaleResolver
@Bean
LocaleResolver localeResolver() {
SessionLocaleResolver localeResolver = new SessionLocaleResolver();
localeResolver.setDefaultLocale(Locale.SIMPLIFIED_CHINESE);
return localeResolver;
}