文章目录
前言
国际化(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 以组件的形式添加到容器中;
- MessageSourceProperties 的属性与配置文件中以“spring.messages”开头的配置进行了绑定;
- 从容器中获取 MessageSourceProperties 组件,并从中读取国际化资源文件的 basename(文件基本名)、 encoding(编码)等信息,将它们封装到 ResourceBundleMessageSource 中;
- 将 ResourceBundleMessageSource 以组件的形式添加到容器中,进而实现对国际化资源文件的管理。
查看 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 点:
- MessageSourceProperties 为 basename、encoding 等属性提供了默认值;
- basename 表示国际化资源文件的基本名,其默认取值为“messages”,即 Spring Boot 默认会获取类路径下的 message.properties 以及 message_XXX.properties 作为国际化资源文件;
- 在 application.porperties/yml 等配置文件中,使用配置参数“spring.messages.basename”即可重新指定国际化资源文件的基本名。
通过以上源码分析,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;
}
从以上源码可知:
- 该方法默认向容器中添加了一个区域信息解析器(LocaleResolver)组件,它会根据请求头中携带的“Accept-Language”参数,获取相应区域信息(Locale)对象。
- 该方法上使用了 @ConditionalOnMissingBean 注解,其参数 name 的取值为 localeResolver(与该方法注入到容器中的组件名称一致),该注解的含义为:当容器中不存在名称为 localResolver 组件时,该方法才会生效。换句话说,当我们手动向容器中添加一个名为“localeResolver”的组件时,Spring Boot 自动配置的区域信息解析器会失效,而我们定义的区域信息解析器则会生效。
看到这里我们就看明白了,没有指定的LocaleResolver组件时,会采用这个默认的LocaleResolver,他取的是http请求头中“Accept-Language”参数的值确定语言。
二、国际化使用方法
Springboot国际化存在三种使用方式:
- AcceptHeaderLocaleResolver 默认解析器,通过请求头的 Accept-Language 字段来判断当前请求所属的环境的
- SessionLocaleResolver Locale 保存在 HttpSession 对象中
- CookieLocaleResolver LOCAL保存在Cookie中
1.默认解析器AcceptHeaderLocaleResolver
- 修改Springboot application.yml配置
spring:
messages:
basename: i18n/messages #配置国际化资源文件路径
- 创建国际化资源文件
messages.properties不带后缀为默认语言资源 - 编写国际化文本
简体中文 messages_zh_CN.properties
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));
- 国际化测试
2.自定义解析器SessionLocaleResolver 和CookieLocaleResolver
所谓自定义包含两层意思,第一是指改变获取语言参数的方式,例如从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;
}