SpringBoot實(shí)現(xiàn)國際化的教程
前言
SpringBoot提供了國際化功能,其原理是將配置的各個(gè)語言資源文件信息,以Map的形式進(jìn)行緩存。
當(dāng)前端請求給定某個(gè)語言標(biāo)識時(shí)(一般是放到請求頭中),拿去指定的語言標(biāo)識去獲取響應(yīng)的響應(yīng)信息。
在Springboot項(xiàng)目啟動時(shí),由MessageSourceAutoConfiguration類進(jìn)行消息資源自動配置。

該類存在 @Conditional 條件注解,也就是說必須滿足某個(gè)條件是才會進(jìn)行自動裝載配置。
- ResourceBundleCondition類用于判斷是否滿足自動注入條件。
- getMatchOutcome用于返回一個(gè) ConditionOutcome 對象,用于后續(xù)判斷是否滿足自動注入條件。該方法會自動讀取spring.messages.basename配置的資源文件地址信息,通過getResources方法獲取默認(rèn)的文件資源。如果該資源不存在,則不滿足自動注入條件。
- getResources明確標(biāo)注了只能從
classpath*下拿去資源文件,文件類型為properties。
/**
* 判斷是否滿足自動注入條件
* @param context the condition context
* @param metadata the annotation metadata
* @return
*/
@Override
public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) {
String basename = context.getEnvironment().getProperty("spring.messages.basename", "messages");
ConditionOutcome outcome = cache.get(basename);
if (outcome == null) {
outcome = getMatchOutcomeForBasename(context, basename);
cache.put(basename, outcome);
}
return outcome;
}
private ConditionOutcome getMatchOutcomeForBasename(ConditionContext context, String basename) {
ConditionMessage.Builder message = ConditionMessage.forCondition("ResourceBundle");
for (String name : StringUtils.commaDelimitedListToStringArray(StringUtils.trimAllWhitespace(basename))) {
for (Resource resource : getResources(context.getClassLoader(),name)) {
if (resource.exists()) {
return ConditionOutcome.match(message.found("bundle").items(resource));
}
}
}
return ConditionOutcome.noMatch(message.didNotFind("bundle with basename " + basename).atAll());
}
//讀取消息資源文件,從classpath下尋找格式為properties類型的資源文件
private Resource[] getResources(ClassLoader classLoader, String name) {
String target = name.replace('.', '/');
try {
return new PathMatchingResourcePatternResolver(classLoader)
.getResources("classpath*:" + target + ".properties");
}
catch (Exception ex) {
return NO_RESOURCES;
}
}MessageSourceProperties類則用于配置消息源的配置屬性。在ResourceBundleCondition返回條件成立的情況下,會通過注解Bean進(jìn)行注入。讀取前綴帶有spring.messages的配置信息。
@Bean
@ConfigurationProperties(prefix = "spring.messages")
public MessageSourceProperties messageSourceProperties() {
return new MessageSourceProperties();
}MessageSourceProperties類可配置的屬性并不多,具體屬性含義用途如下:
#配置國際化資源文件路徑,基礎(chǔ)包名名稱地址 spring.messages.basename=il8n/messages #編碼格式,默認(rèn)使用UTF-8 spring.messages.encoding=UTF-8 #是否總是應(yīng)用MessageFormat規(guī)則解析信息,即使是解析不帶參數(shù)的消息 spring.messages.always-use-message-format=false # 是否使用消息代碼作為默認(rèn)消息,而不是拋出NoSuchMessageException.建議在開發(fā)測試時(shí)開啟,避免不必要的拋錯而影響正常業(yè)務(wù)流程 spring.messages.use-code-as-default-message=true
在MessageSourceProperties完成讀取配置之后,將會自動注入MessageSource,而默認(rèn)注入的MessageSource的實(shí)現(xiàn)類ResourceBundleMessageSource。ResourceBundleMessageSource是SpringBoot實(shí)現(xiàn)國際化的核心。其采用的是及時(shí)加載文件的形式。
即:只有當(dāng)某種特定的語言要求被返回時(shí),才會去讀取資源文件,將消息內(nèi)容緩存起來并通過響應(yīng)碼進(jìn)行返回具體消息。ResourceBundleMessageSource的源碼并不復(fù)雜,這里就不展開講解。
@Bean
public MessageSource messageSource(MessageSourceProperties properties) {
//消息資源綁定類,用于緩存資源消息
ResourceBundleMessageSource messageSource = new ResourceBundleMessageSource();
if (StringUtils.hasText(properties.getBasename())) {
//設(shè)置資源消息默認(rèn)包名
messageSource.setBasenames(StringUtils.commaDelimitedListToStringArray(StringUtils.trimAllWhitespace(properties.getBasename())));
}
if (properties.getEncoding() != null) {
//設(shè)置編碼格式
messageSource.setDefaultEncoding(properties.getEncoding().name());
}
messageSource.setFallbackToSystemLocale(properties.isFallbackToSystemLocale());
Duration cacheDuration = properties.getCacheDuration();
if (cacheDuration != null) {
//設(shè)置消息資源過期時(shí)間
messageSource.setCacheMillis(cacheDuration.toMillis());
}
//是否總是應(yīng)用MessageFormat規(guī)則解析信息,即使是解析不帶參數(shù)的消息
messageSource.setAlwaysUseMessageFormat(properties.isAlwaysUseMessageFormat());
//是否使用消息代碼作為默認(rèn)消息,而不是拋出NoSuchMessageException.建議在開發(fā)測試時(shí)開啟,避免不必要的拋錯而影響正常業(yè)務(wù)流程
messageSource.setUseCodeAsDefaultMessage(properties.isUseCodeAsDefaultMessage());
return messageSource;
}一、配置消息資源文件
消息資源文件用于存儲不同國家語言響應(yīng)的消息,我們通過源碼知道該文件必須是properties類型(在你不去更改源碼的時(shí)候),因此我們需要在resources資源文件下存放消息文件。
這里我存放三個(gè)文件,message基礎(chǔ)資源文件(內(nèi)容可以為空),messages_en_US.properties英文文件,messages_zh_CN.properties中文文件。

messages_zh_CN.properties和messages_en_US.properties內(nèi)容如下:

二、配置消息源
這里采用的是以application.properties形式進(jìn)行配置,您也可以采用yaml文件形式進(jìn)行配置:
#配置國際化資源文件路徑 spring.messages.basename=il8n/messages #編碼格式,默認(rèn)使用UTF-8 spring.messages.encoding=UTF-8 #是否總是應(yīng)用MessageFormat規(guī)則解析信息,即使是解析不帶參數(shù)的消息 spring.messages.always-use-message-format=false # 是否使用消息代碼作為默認(rèn)消息,而不是拋出NoSuchMessageException.建議在開發(fā)測試時(shí)開啟,避免不必要的拋錯而影響正常業(yè)務(wù)流程 spring.messages.use-code-as-default-message=true
三、配置消息攔截器
攔截器用于從請求頭中獲取語言標(biāo)識,以便于后續(xù)根據(jù)語言標(biāo)識響應(yīng)不同的響應(yīng)信息。
package com.il8n.config;
import lombok.Getter;
import lombok.Setter;
import org.jetbrains.annotations.NotNull;
import org.springframework.util.ObjectUtils;
import org.springframework.web.servlet.LocaleResolver;
import org.springframework.web.servlet.i18n.LocaleChangeInterceptor;
import org.springframework.web.servlet.support.RequestContextUtils;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* @Author: Greyfus
* @Create: 2024-01-12 13:06
* @Version: 1.0.0
* @Description:國際化攔截器
*/
@Setter
@Getter
public class IL8nLangInterceptor extends LocaleChangeInterceptor {
private String langHeader;
@Override
public boolean preHandle(HttpServletRequest request, @NotNull HttpServletResponse response, @NotNull Object handler) throws ServletException {
String locale = request.getHeader(getLangHeader());
if (locale != null) {
if (iL8nCheckHttpMethod(request.getMethod())) {
LocaleResolver localeResolver = RequestContextUtils.getLocaleResolver(request);
if (localeResolver == null) {
throw new IllegalStateException("No LocaleResolver found: not in a DispatcherServlet request?");
}
try {
localeResolver.setLocale(request, response, parseLocaleValue(locale));
} catch (IllegalArgumentException ex) {
if (isIgnoreInvalidLocale()) {
if (logger.isDebugEnabled()) {
logger.debug("Ignoring invalid locale value [" + locale + "]: " + ex.getMessage());
}
} else {
throw ex;
}
}
}
}
// Proceed in any case.
return true;
}
public boolean iL8nCheckHttpMethod(String currentMethod) {
String[] configuredMethods = getHttpMethods();
if (ObjectUtils.isEmpty(configuredMethods)) {
return true;
}
for (String configuredMethod : configuredMethods) {
if (configuredMethod.equalsIgnoreCase(currentMethod)) {
return true;
}
}
return false;
}
}
注入消息攔截器,并設(shè)置默認(rèn)語言為中文:
package com.il8n.config;
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;
/**
* @Author: Greyfus
* @Create: 2024-01-12 00:22
* @Version: 1.0.0
* @Description:語言國際化配置
*/
@Configuration
public class IL8nLangConfig implements WebMvcConfigurer {
private static final String LANG_HEADER = "lang";
@Bean
public LocaleResolver localeResolver() {
SessionLocaleResolver sessionLocaleResolver = new SessionLocaleResolver();
sessionLocaleResolver.setDefaultLocale(Locale.SIMPLIFIED_CHINESE);
return sessionLocaleResolver;
}
@Bean
public LocaleChangeInterceptor localeChangeInterceptor() {
IL8nLangInterceptor lci = new IL8nLangInterceptor();
//設(shè)置請求的語言變量
lci.setLangHeader(LANG_HEADER);
return lci;
}
/**
* 注冊攔截器
* @param registry
*/
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(localeChangeInterceptor());
}
}
四、編寫消息工具類
消息工具類用于通過指定的消息碼獲取對應(yīng)的響應(yīng)消息
package com.il8n.config;
import lombok.Getter;
import org.jetbrains.annotations.NotNull;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;
import java.util.Map;
/**
* @Author: Greyfus
* @Create: 2024-01-12 00:36
* @Version: 1.0.0
* @Description: 工具
*/
@Component
public class SpringUtils implements ApplicationContextAware {
@Getter
private static ApplicationContext applicationContext;
public SpringUtils() {
}
public void setApplicationContext(@NotNull ApplicationContext applicationContext) {
SpringUtils.applicationContext = applicationContext;
}
public static <T> T getBean(String name) {
return (T) applicationContext.getBean(name);
}
public static <T> T getBean(Class<T> clazz) {
return applicationContext.getBean(clazz);
}
public static <T> T getBean(String name, Class<T> clazz) {
return applicationContext.getBean(name, clazz);
}
public static <T> Map<String, T> getBeansOfType(Class<T> type) {
return applicationContext.getBeansOfType(type);
}
public static String[] getBeanNamesForType(Class<?> type) {
return applicationContext.getBeanNamesForType(type);
}
public static String getProperty(String key) {
return applicationContext.getEnvironment().getProperty(key);
}
public static String[] getActiveProfiles() {
return applicationContext.getEnvironment().getActiveProfiles();
}
}package com.il8n.config;
import org.springframework.context.MessageSource;
import org.springframework.context.i18n.LocaleContextHolder;
/**
* @Author: Greyfus
* @Create: 2024-01-12 00:29
* @Version: 1.0.0
* @Description: IL8N語言轉(zhuǎn)換工具
*/
public class IL8nMessageUtils {
private static final MessageSource messageSource;
static {
messageSource = SpringUtils.getBean(MessageSource.class);
}
/**
* 獲取國際化語言值
*
* @param messageCode
* @param args
* @return
*/
public static String message(String messageCode, Object... args) {
return messageSource.getMessage(messageCode, args, LocaleContextHolder.getLocale());
}
}五、測試國際化
編碼一個(gè)Controller用于模擬測試效果,代碼如下:
package com.il8n.controller;
import com.il8n.config.IL8nMessageUtils;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
/**
* @Author: DI.YIN
* @Date: 2025/1/6 9:37
* @Version:
* @Description:
**/
@RestController
@RequestMapping("/mock")
public class IL8nTestController {
@RequestMapping(value = "/login")
public ResponseEntity<String> login(@RequestParam(value = "userName") String userName, @RequestParam(value = "password") String password) {
if (!"admin".equals(userName) || !"admin".equals(password)) {
return new ResponseEntity<>(IL8nMessageUtils.message("LoginFailure", (Object) null), HttpStatus.OK);
}
return new ResponseEntity<>(IL8nMessageUtils.message("loginSuccess", userName), HttpStatus.OK);
}
}
消息攔截器會嘗試從請求頭中獲取屬性為lang的值,將其作為語言標(biāo)識,因此我們在使用PostMan模擬時(shí),需要在請求頭中增加lang屬性。

模擬結(jié)果如下:
中文語言標(biāo)識:

英文語言標(biāo)識:

總結(jié)
以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
相關(guān)文章
SpringBoot注冊FilterRegistrationBean相關(guān)情況講解
這篇文章主要介紹了SpringBoot注冊FilterRegistrationBean相關(guān)情況,借助FilterRegistrationBean來注冊filter,可以避免在web.xml種配置filter這種原始的寫法2023-02-02
Spring Boot 配置 Quartz 定時(shí)任務(wù)的方法
這篇文章主要介紹了Spring Boot 配置 Quartz 定時(shí)任務(wù)的方法,本文通過實(shí)例代碼給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2021-09-09
Java實(shí)現(xiàn)輕松處理日期和時(shí)間的API小結(jié)
這篇文章主要為大家詳細(xì)介紹了Java中的日期和時(shí)間API,可以輕松處理日期和時(shí)間,文中的示例代碼講解詳細(xì),感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下2024-03-03
Java Date與String的相互轉(zhuǎn)換詳解
這篇文章主要介紹了Java Date與String的相互轉(zhuǎn)換詳解的相關(guān)資料,需要的朋友可以參考下2017-02-02
MyBatis中如何接收String類型的參數(shù)實(shí)現(xiàn)
這篇文章主要介紹了MyBatis中如何接收String類型的參數(shù)實(shí)現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2021-02-02
Java隨機(jī)生成驗(yàn)證碼的實(shí)現(xiàn)示例
這篇文章主要介紹Java隨機(jī)生成驗(yàn)證碼的實(shí)現(xiàn)方法,文中有相關(guān)的實(shí)現(xiàn)代碼供大家參考,具有一定的參考價(jià)值,需要的朋友可以參考下2023-08-08
javax.management.InvalidApplicationException的問題解決
javax.management.InvalidApplicationException是與Java Management Extensions (JMX) API相關(guān)的一個(gè)常見異常,本文主要介紹了javax.management.InvalidApplicationException的問題解決,感興趣的可以了解一下2024-08-08
SpringBoot中打印SQL語句的幾種方法實(shí)現(xiàn)
本文主要介紹了SpringBoot中打印SQL語句的幾種方法實(shí)現(xiàn),,通過打印SQL語句可以幫助開發(fā)人員快速了解數(shù)據(jù)庫的操作情況,進(jìn)而進(jìn)行性能分析和調(diào)試,感興趣的可以了解一下2023-11-11

