首先,需要明确一点,国际化资源文件必须包含默认文件和国家语言文件,且必须:
(1)直接放在类路径下
(2)或放在resource目录下
1、spring boot 添加 thymele3依赖
org.springframework.boot
spring-boot-starter-thymeleaf
2、添加国际化资源文件(我是放在了 resources目录)
login_zh_CN.properties:
login.btn=登录
login.password=密码
login.remember=记住我
login.tip=请登录
login.username=用户名
3、在 application.properties 或 application.yml 文件中添加 basename 引用路径(很重要)
#国际化文件
spring.messages.basename=i18n/login
4、通过 #{} 引用资源文件中的值
5、开始测试,发现界面上找不到资源文件中的值,一直出现??login_zh_CN??这种问题。遇到这种问题简直要疯了,于是各种baidu、google,找了好多文章,各种方案,都没有解决问题。静下心来,自己一步一步去调试。
(1)自动加载配置类 MessageSourceAutoConfiguration,自动注入MessageResource,调试中发现,自动注入的不是ResourceBundleMessageSource 对象,而是DelegatingMessageSource对象,且其中没有值。在注入 MessageResource 的代码中打断点,发现确实没有运行,即没有自动注入ResourceBundleMessageSource。
@Bean
public MessageSource messageSource() {
MessageSourceProperties properties = messageSourceProperties();
ResourceBundleMessageSource messageSource = new ResourceBundleMessageSource();
if (StringUtils.hasText(properties.getBasename())) {
messageSource.setBasenames(StringUtils.commaDelimitedListToStringArray(
StringUtils.trimAllWhitespace(properties.getBasename())));
}
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;
}
(2)在Spring boot 启动过程找到了 DelegatingMessageSource 对象来源, 在启动过程中如果Spring没有找到messageSource定义,就会自动创建一个 DelegatingMessageSource 对象提供给SpringContext。也就是说 MessageSourceAutoConfiguration 根本没有被加载。
protected void initMessageSource() {
ConfigurableListableBeanFactory beanFactory = this.getBeanFactory();
if (beanFactory.containsLocalBean("messageSource")) {
this.messageSource = (MessageSource)beanFactory.getBean("messageSource", MessageSource.class);
if (this.parent != null && this.messageSource instanceof HierarchicalMessageSource) {
HierarchicalMessageSource hms = (HierarchicalMessageSource)this.messageSource;
if (hms.getParentMessageSource() == null) {
hms.setParentMessageSource(this.getInternalParentMessageSource());
}
}
if (this.logger.isDebugEnabled()) {
this.logger.debug("Using MessageSource [" + this.messageSource + "]");
}
} else {
DelegatingMessageSource dms = new DelegatingMessageSource();
dms.setParentMessageSource(this.getInternalParentMessageSource());
this.messageSource = dms;
beanFactory.registerSingleton("messageSource", this.messageSource);
if (this.logger.isDebugEnabled()) {
this.logger.debug("Unable to locate MessageSource with name 'messageSource': using default [" + this.messageSource + "]");
}
}
}
(3)到这里,就可以知道,为什么页面上没有找到资源了,因为 ResourceBundleMessageSource 对象没有注入容器中,找不到MessageResource 对象,返回了一个空的 DelegatingMessageSource 对象注入到了容器中。现在分析为什么配置类MessageSourceAutoConfiguration 中的 MessageResource 对象没有自动注入呢,查看 MessageSourceAutoConfiguration 类上的注解,可以看到 有如下一个注解:
@Conditional(ResourceBundleCondition.class)
@conditional 是spring 的条件判定注解,大致意思为:满足一定条件后,ResourceBundleCondition 类会注册生效。继续分析ResourceBundleCondition实现,这个类是一个内部类,经过分析,发现有如下判定是否生效的方法:
@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());
}
这个就是根据basename是否存在,来判定是否满足注册条件。basename就是我们前面配置的”i18n/login“(没有配置,默认为 类路径下的messages),根据basename去项目的类路径下寻找 i18n/login.properties ,若存在,会正常返回一个Resouce对象,满足注册条件,会成功加载 MessageResource 对象;若不存在,不满足条件,无法继续注册。
这里说下我的问题,我是因为多建了一个resources文件夹导致,无法找到资源文件。到这里,终于把这个奇葩的问题解决了,记录一下。
总结:Spring boot 国际化配置时,一定要建立默认的 basename.properties 文件。