明明说好的国际化,可你却还是返回了中文

问题现象

一个 Spring Boot 项目,在微服务的 resources 路径下有 messages.propertiesmessages_zh.properties 两个文件,同时还有一个 Controller 对外提供了一个接口,这个接口会根据 key 去获取对应的国际化信息然后返回。

现在遇到了一个很坑的问题是明明传入的 LocaleLocale.US 时,但是获取到的国际化信息居然是中文的

复现问题的 Demo 项目文件结构如下 : 整体就一个 HelloController 提供接口,然后有messages.propertiesmessages_zh.properties 两个国际化文件。

messages.properties 文件中的内容是:

json 复制代码
hello=hello

messages_zh_CH.properties 文件中的内容是:

json 复制代码
hello=你好

HelloController 对外提供了一个 /hello 接口,根据 hello 这个 key 去国际化文件获取数据并返回。这里为了复现问题,这里手动传入 Locale.US

java 复制代码
package com.example.demo.controller;  
  
import org.springframework.beans.factory.annotation.Autowired;  
import org.springframework.context.MessageSource;  
import org.springframework.web.bind.annotation.GetMapping;  
import org.springframework.web.bind.annotation.RestController;  
  
import java.util.Locale;  
  
@RestController  
public class HelloController {  
    @Autowired  
    private MessageSource messageSource;  
  
    @GetMapping("/hello")  
    public String hello() {  
        return messageSource.getMessage("hello", null, Locale.US);  
    }  
}

调用接口之后返回的结果如下: 同时查看操作系统的语言明明是英文的,但是通过 DEBUG 程序发现,Locale.getDefault() 返回居然是 zh_CN。但是手动重启一下服务之后,它获取的 locale 又和操作系统语言是匹配得上的了。好吧,明明只有一个问题的,现在定位了一下,有两个问题了

问题原理

Spring Boot 查找国际化文件的过程分析

首先最开始的时候项目路径下实际上是有 messages.properties,messages_en_US.properties 以及 messages_zh_CN.properties 三个文件的,但是后来认为找不到对应 locale 的相匹配文件时,默认就会退化到 messages.properties 文件中查找,而 messages.properties 文件中的实际上配置的就是英文的国际化,因此就把 messages_en_US.properties 给删了(这就是个血淋淋的教训啊,程序能跑就不要动,真的手贱啊),而问题的关键就出在对默认退化查找的文件这个理解有误

Spring Boot 实际上提供了一个名为 spring.messages.fallback-to-system-locale 的配置项,它的作用就是决定在找不到和传入的 locale 相匹配的国际化文件的时候,默认查找的文件。

在 spring-boot-autoconfigure 这个 jar 包中的 spring-configuration-metadata.json 中有对这个配置项的详细说明:

json 复制代码
{  
  "name": "spring.messages.fallback-to-system-locale",  
  "type": "java.lang.Boolean",  
  "description": "Whether to fall back to the system Locale if no files for a specific Locale have been found. if this is turned off, the only fallback will be the default file (e.g. "messages.properties" for basename "messages").",  
  "sourceType": "org.springframework.boot.autoconfigure.context.MessageSourceProperties",  
  "defaultValue": true  
}

从上面的说明可以看出,当该配置项关闭时才会默认去 messages.properties 文件中查找,否则就会去和当前系统 locale 相同的文件中去查找,并且该配置项的默认值是 true

基于以上信息来解释一下为什么上面传入 Locale.US 拿到的结果还是中文? spring.messages.fallback-to-system-locale 配置项的默认值是 true 且项目文件结构下没有 messages.en_US.properties 文件。

因为传入的 localeLocale.US,所以首先会去 messages.en_US.properties 文件中查找,但是并没有这个文件存在。接着就应该去和系统 locale 相匹配的文件中去查找,通过 DEBUG 发现 Locale.getDefault() 返回的值是 zh_CN,按照这个结果就应该去 messages.zh_CN.properties 文件中去查找,这个文件是存在的,从这个文件中获取到值就返回了,从而拿到的就是中文。

spring.messages.fallback-to-system-locale 这个配置项的默认值又是在什么时候配置的呢?这个默认值实际上是在 MessageSourceProperties 这个类的构造函数中初始化为 true 的,代码如下:

java 复制代码
@ConfigurationProperties("spring.messages")  
public class MessageSourceProperties {  
    private List<String> basename = new ArrayList(List.of("messages"));  
    private List<Resource> commonMessages;  
    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;  
        //这里设置默认值为true
        this.fallbackToSystemLocale = true;  
        this.alwaysUseMessageFormat = false;  
        this.useCodeAsDefaultMessage = false;  
    }
}

然后在 MessageSourceAutoConfiguration 类中会配置将 MessageSourceProperties 加载为一个 Spring 的 Bean,然后还会配置一个 MessageSource 类型的 Bean,然后这个 Bean 依赖 MessageSourceProperties 的默认值值来对自己进行初始化,从而将自己的 fallbackToSystemLocale 属性也设置为了 true,代码如下:

java 复制代码
@AutoConfiguration
@ConditionalOnMissingBean(
    name = {"messageSource"},
    search = SearchStrategy.CURRENT
)
@AutoConfigureOrder(Integer.MIN_VALUE)
@Conditional({ResourceBundleCondition.class})

//配置加载MessageSourceProperties为Spring的Bean
@EnableConfigurationProperties({MessageSourceProperties.class})
@ImportRuntimeHints({MessageSourceRuntimeHints.class})
public class MessageSourceAutoConfiguration {
    private static final Resource[] NO_RESOURCES = new Resource[0];

	//这里依赖注入MessageSourceProperties这个Bean
    @Bean
    public MessageSource messageSource(MessageSourceProperties properties) {
        ResourceBundleMessageSource messageSource = new ResourceBundleMessageSource();
        if (!CollectionUtils.isEmpty(properties.getBasename())) {
            messageSource.setBasenames((String[])properties.getBasename().toArray(new String[0]));
        }

        if (properties.getEncoding() != null) {
            messageSource.setDefaultEncoding(properties.getEncoding().name());
        }
		//这里设置MessageSource的fallbackToSystemLocale的属性值为true
        messageSource.setFallbackToSystemLocale(properties.isFallbackToSystemLocale());
        Duration cacheDuration = properties.getCacheDuration();
        if (cacheDuration != null) {
            messageSource.setCacheMillis(cacheDuration.toMillis());
        }

        messageSource.setAlwaysUseMessageFormat(properties.isAlwaysUseMessageFormat());
        messageSource.setUseCodeAsDefaultMessage(properties.isUseCodeAsDefaultMessage());
        messageSource.setCommonMessages(this.loadCommonMessages(properties.getCommonMessages()));
        return messageSource;
    }
}    

操作系统语言环境和获取的默认Locale不一致分析

这个问题是因为部署的时候使用的 Jenkins 打包,然后上传到服务器上,然后通过 SSH 连接到服务器上执行命令启动的 Java 进程。 在建立 SSH 连接的时候,客户端会把自己的语言环境变量发送给服务端设置,这个是在 /etc/ssh/sshd_config 配置文件中有一个 SendEnv 配置项来决定的:

而 Jenkins 服务器的语言是设置的是中文,因此在当前打开的 SSH 连接里面执行 echo $LANG 输出的结果就是 zh_CN。Java 进程启动的时候获取到的系统语言就是 zh_CN ,从而通过 Locale.getDefault() 获取到的也是 zh_CN。重启之后好了的原因就是,重启之后又重新读取到了操作系统的语言环境为 en_US,所以 Locale.getDefault() 返回的结果也正确了。

相关推荐
猪猪拆迁队14 分钟前
虚拟工厂仿真引擎的架构设计:让一条产线可编程、可观测、可干预
后端·ai编程
字节跳动数据库39 分钟前
文章分享——相似函数处理方法
人工智能·后端·程序员
云技纵横39 分钟前
@Transactional 失效的 7 种场景:第 5 种最难排查
后端
用户6757049885021 小时前
你知道 Go 结构体和结构体指针调用的区别吗?一文带你彻底搞懂!
后端·go
程序员cxuan1 小时前
读懂 Claude Code 架构分析系列,第一篇,开始!
人工智能·后端·架构
用户6757049885021 小时前
面试官问“装饰器模式”,这样回答薪资多要 3000!
后端
tntxia1 小时前
Geo Scene域名修改引起的一些问题
后端
用户298698530141 小时前
Java 实现 Word 文档加密与权限解除
java·后端
vanuan2 小时前
给你的A2A-Agent加把锁-认证鉴权实战指南
后端
Yeats_Liao2 小时前
14:Servlet中的页面跳转-Java Web
java·后端·架构