spring boot 2.0.5 + thymeleaf3 国际化,无法自动加载MessageSource,找不资源文件,页面访问出现问号

首先,需要明确一点,国际化资源文件必须包含默认文件和国家语言文件,且必须:

(1)直接放在类路径下

(2)或放在resource目录下

1、spring boot 添加 thymele3依赖

        
        
            org.springframework.boot
            spring-boot-starter-thymeleaf
        

2、添加国际化资源文件(我是放在了 resources目录)

spring boot 2.0.5 + thymeleaf3 国际化,无法自动加载MessageSource,找不资源文件,页面访问出现问号_第1张图片

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 文件。

你可能感兴趣的:(SpringBoot)