在java工程中,经常使用ResourceBundle来处理国际化问题。
在spring中有一个ResourceBundleMessageSource 类,用来读取ResourceBundle中的内容。从源码可以看到,其实这里最主要的一个操作,就是获取Locale这个对象,里面会包括语言,国家的相关信息。而ResourceBundle根据Locale获取到对应的语言文件。
spring boot在启动的时候会创建ResourceBundleMessageSource的bean。我们可以通过设置
spring.messages.basename 的值来配置启动的时候会加载哪些对象到缓存里。
MessageSourceAutoConfiguration
@Bean
public MessageSource messageSource(MessageSourceProperties properties) {
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;
}
以入参校验为例
LocalValidatorFactoryBean
public void setValidationMessageSource(MessageSource messageSource) {
this.messageInterpolator = HibernateValidatorDelegate.buildMessageInterpolator(messageSource);
}
private static class HibernateValidatorDelegate {
public static MessageInterpolator buildMessageInterpolator(MessageSource messageSource) {
return new ResourceBundleMessageInterpolator(new MessageSourceResourceBundleLocator(messageSource));
}
}
这里注入的对象就是一个ResourceBundleMessageSource
那么,在请求过程中,如何去判断当前的语言是什么呢?在接受到请求之后,就会有默认的过滤器去解析http requet header中的Accept-Language,然后选择当前的语言,并且赋值给LocaleContextHolder
RequestContextFilter
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
ServletRequestAttributes attributes = new ServletRequestAttributes(request, response);
this.initContextHolders(request, attributes);
try {
filterChain.doFilter(request, response);
} finally {
this.resetContextHolders();
if (this.logger.isTraceEnabled()) {
this.logger.trace("Cleared thread-bound request context: " + request);
}
attributes.requestCompleted();
}
}
private void initContextHolders(HttpServletRequest request, ServletRequestAttributes requestAttributes) {
LocaleContextHolder.setLocale(request.getLocale(), this.threadContextInheritable);
RequestContextHolder.setRequestAttributes(requestAttributes, this.threadContextInheritable);
if (this.logger.isTraceEnabled()) {
this.logger.trace("Bound request context to thread: " + request);
}
}
HttpServletRequestImpl
@Override
public Locale getLocale() {
return getLocales().nextElement();
}
@Override
public Enumeration<Locale> getLocales() {
final List<String> acceptLanguage = exchange.getRequestHeaders().get(Headers.ACCEPT_LANGUAGE);
List<Locale> ret = LocaleUtils.getLocalesFromHeader(acceptLanguage);
if(ret.isEmpty()) {
return new IteratorEnumeration<>(Collections.singletonList(Locale.getDefault()).iterator());
}
return new IteratorEnumeration<>(ret.iterator());
}
此时spring mvc已经根据hearder中的信息,为我们在LocaleContextHolder中填充了locale的信息。
在从ResourceBundleMessageSource获取对象的时候,我们就可以知道应该去拿对应的语言文件了。