在Spring程序中,国际化主要是通过ResourceBundleMessageSource
这个类来实现的,那么下面我们分析一下Spring Boot是如何实现国际化支持的。
Spring Boot通过MessageSourceAutoConfiguration
是为我们自动配置好了管理国际化资源文件的组件的:
org.springframework.boot.autoconfigure.context.MessageSourceAutoConfiguration
看一下其源码:
@Configuration @ConditionalOnMissingBean(value = MessageSource.class, search = SearchStrategy.CURRENT) @AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE) @Conditional(ResourceBundleCondition.class) @EnableConfigurationProperties public class MessageSourceAutoConfiguration { private static final Resource[] NO_RESOURCES = {}; @Bean @ConfigurationProperties(prefix = "spring.messages") public MessageSourceProperties messageSourceProperties() { return new MessageSourceProperties(); } @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; }
主要了解messageSource()这个方法。
MessageSourceProperties properties = messageSourceProperties();
方法中首先声明了MessageSourceProperties这个对象,看一下这个对象包含了什么:
public class MessageSourceProperties { /** * Comma-separated list of basenames (essentially a fully-qualified classpath * location), each following the ResourceBundle convention with relaxed support for * slash based locations. If it doesn't contain a package qualifier (such as * "org.mypackage"), it will be resolved from the classpath root. */ private String basename = "messages"; /** * Message bundles encoding. */ private Charset encoding = StandardCharsets.UTF_8;
类中首先声明了一个属性basename
,默认值为messages
。看其介绍,这是一个以逗号分隔的基本名称列表,如果它不包含包限定符(例如“org.mypackage”),它将从类的根路径解析。它的意思是如果你不在配置文件中指定以逗号分隔开的国际化资源文件名称的话,它默认会去类路径下找messages.properties作为国际化资源文件的基本文件。若是你的国际化资源文件是在类路径某个包(如:i18n)下的话,你就需要在配置文件中指定基本名称了。
spring.messages.basename=i18n/login
其中i18n是存放资源的文件夹名,login是资源文件的基本名称。
之后:
ResourceBundleMessageSource messageSource = new ResourceBundleMessageSource();
上面说了国际化支持主要是通过ResourceBundleMessageSource实现的,这里对它做出实例化操作。
if (StringUtils.hasText(properties.getBasename())) { messageSource.setBasenames(StringUtils.commaDelimitedListToStringArray( StringUtils.trimAllWhitespace(properties.getBasename()))); }
这里可以看到基本名称的设置了,你若是没有在配置文件中设置的话,它默认就是messages
了。
先做些准备工作,新建一个登录html页面,之后写一个controler层方法 指向登录页面。
login.html:
<html lang="en" xmlns:th="http://www.thymeleaf.org"> <head> <meta charset="UTF-8"/> <title>Titletitle> head> <body> <form action="" method="post"> <label >Usernamelabel> <input type="text" name="username" placeholder="Username" > <label >Passwordlabel> <input type="password" name="password" placeholder="Password" > <br> <br> <div> <label> <input type="checkbox" value="remember-me"/> Remember Me label> div> <br> <button type="submit">Sign inbutton> <br> <br> <a>中文a> <a>Englisha> form> body> html>
login.java:
@Controller public class Login { @RequestMapping("/login") public String login(){ return "login"; } }
准备工作就是这些,首先尝试一下默认的国际化资源文件messages.properties
:
其中的内容分别是:
messages.properties:
login.password=密码1
login.remmber=记住我1
login.sign=登录1
login.username=用户名1
messages_en_US.properties:
login.password=Password
login.remmber=Remember Me
login.sign=Sign In
login.username=Username
messages_zh_CN.properties:
login.password=密码~
login.remmber=记住我~
login.sign=登录~
login.username=用户名~
要使国际化在页面生效的话,还需修改一下页面代码:
<html lang="en" xmlns:th="http://www.thymeleaf.org"> <head> <meta charset="UTF-8"/> <title>Titletitle> head> <body> <form action="" method="post"> <label th:text="#{login.username}">Usernamelabel> <input type="text" name="username" placeholder="Username" th:placeholder="#{login.username}"> <label th:text="#{login.password}">Passwordlabel> <input type="password" name="password" placeholder="Password" th:placeholder="#{login.password}"> <br> <br> <div> <label> <input type="checkbox" value="remember-me"/> [[#{login.remmber}]] label> div> <br> <button type="submit" th:text="#{login.sign}">Sign inbutton> <br> <br> <a>中文a> <a>Englisha> form> body> html>
现在启动应用,访问登录页面,若你使用的是Chrome浏览器的话,可以通过修改浏览器语言来看看效果。打开
设置 – 高级 – 语言,选择你需要展示的语言,如列表中没有英语的话,你需要添加语言,添加英语(美国),这才是对应en_US的语言(在这里浪费了许多时间),添错了就看不到国际化效果。
若你不想使用默认的资源文件名称,你也可以自己自定义。如我在类路径下新建了一个i18n.login与i18n.index文件夹,在其中新建login.properties与index.properties作为基本名称文件。
你不使用默认的messages.properties作为资源文件,那么就得在配置文件中指定国际化基本资源文件的名称,有多个的话就以逗号隔开。我们这里有两个基本名称文件,那么在配置文件中这样来写:
spring.messages.basename=i18n/login/login,i18n/index/index
其他的就不用做任何配置了,在页面中通过切换浏览器语言查看效果。
国际化语言的切换主要是因为有一个区域信息解析器在其作用。我们可以看一下
org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration
这个类,这个类是Spring Boot对SpringMVC功能作自动配置的类。在其中我们找到:
@Bean @ConditionalOnMissingBean @ConditionalOnProperty(prefix = "spring.mvc", name = "locale") public LocaleResolver localeResolver() { if (this.mvcProperties .getLocaleResolver() == WebMvcProperties.LocaleResolver.FIXED) { return new FixedLocaleResolver(this.mvcProperties.getLocale()); } AcceptHeaderLocaleResolver localeResolver = new AcceptHeaderLocaleResolver(); localeResolver.setDefaultLocale(this.mvcProperties.getLocale()); return localeResolver; }
可以看到,当容器中没有LocaleResolver这个组件的时候,上述方法生成的localeResolver对象就会注册到容器中去。具体看一下AcceptHeaderLocaleResolver这个类,在其中找到:
@Override public Locale resolveLocale(HttpServletRequest request) { Locale defaultLocale = getDefaultLocale(); if (defaultLocale != null && request.getHeader("Accept-Language") == null) { return defaultLocale; } Locale requestLocale = request.getLocale(); List<Locale> supportedLocales = getSupportedLocales(); if (supportedLocales.isEmpty() || supportedLocales.contains(requestLocale)) { return requestLocale; } Locale supportedLocale = findSupportedLocale(request, supportedLocales); if (supportedLocale != null) { return supportedLocale; } return (defaultLocale != null ? defaultLocale : requestLocale); }
可以看出,它是根据HttpServletRequest中的locale属性来判定启用哪个语言文件的。
我们的需求是通过点击链接来切换语言,那么我们可以自定义一个区域信息解析器来替代这个默认的解析器。
首先,建立了WebMVCConfig.java这个配置类来扩展Spring Boot对SpringMVC的支持,因为在Spring Boot2.0.4中WebMvcConfigurerAdapter
这个适配器类已经不推荐使用了,所以我们采用直接实现WebMvcConfigurer的方式来进行扩展。
package com.baiding.springboot.config; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.util.StringUtils; import org.springframework.web.servlet.LocaleResolver; import org.springframework.web.servlet.config.annotation.ViewControllerRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.util.Locale; /** * @Author: BaiDing * @Date: 2018/9/19 15:40 * @Email: [email protected] */ @Configuration public class WebMVCConfig implements WebMvcConfigurer { @Override public void addViewControllers(ViewControllerRegistry registry) { registry.addViewController("/").setViewName("login"); registry.addViewController("/login.html").setViewName("login"); } @Bean public LocaleResolver localeResolver(){ return new NativeLocaleResolver(); } protected static class NativeLocaleResolver implements LocaleResolver{ @Override public Locale resolveLocale(HttpServletRequest request) { String language = request.getParameter("language"); Locale locale = Locale.getDefault(); if(!StringUtils.isEmpty(language)){ String[] split = language.split("_"); locale = new Locale(split[0],split[1]); } return locale; } @Override public void setLocale(HttpServletRequest request, HttpServletResponse response, Locale locale) { } } }
从上面的代码可以看出,定义了NativeLocaleResolver这个内部类实现LocaleResolver这个接口,其内部实现原理就是通过读取request中language属性,声明一个具体的Locale对象。之后将NativeLocaleResolver注册到了容器中去。
之后修改一下html页面下面两个超链接的href值:
<a th:href="@{/login.html(language='zh_CN')}">中文a> <a th:href="@{/login.html(language='en_US')}">Englisha>
这就可以很成功的通过点击超链接来切换语言了。
具体代码可以移驾GitHub