这篇文章主要介绍了Java Spring项目国际化的示例分析,具有一定借鉴价值,感兴趣的朋友可以参考下,希望大家阅读完这篇文章之后大有收获,下面让小编带着大家一起了解一下。
国际化信息”也称为“本地化信息”,一般需要两个条件才可以确定一个特定类型的本地化信息,它们分别是“语言类型”和“国家/地区的类型”。如中文本地化信息既有中国大陆地区的中文,又有中国台湾、中国香港地区的中文,还有新加坡地区的中文。Java通过java.util.Locale类表示一个本地化对象,它允许通过语言参数和国家/地区参数创建一个确定的本地化对象。
语言参数使用ISO标准语言代码表示,这些代码是由ISO-639标准定义的,每一种语言由两个小写字母表示。在许多网站上都可以找到这些代码的完整列表,下面的网址是提供了标准语言代码的信息:http://www.loc.gov/standards/iso639-2/php/English_list.php。
国家/地区参数也由标准的ISO国家/地区代码表示,这些代码是由ISO-3166标准定义的,每个国家/地区由两个大写字母表示。用户可以从以下网址查看ISO-3166的标准代码:http://www.iso.ch/iso/en/prods-services/iso3166ma/02iso-3166-code-lists/list-en1.html,部分语言和国家/地区的标准代码如下所示:
语言 | 简称 |
---|---|
简体中文(中国) | zh_CN |
繁体中文(中国台湾) | zh_TW |
繁体中文(中国香港) | zh_HK |
英语(中国香港) | en_HK |
英语(美国) | en_US |
英语(英国) | en_GB |
英语(全球) | en_WW |
英语(加拿大) | en_CA |
英语(澳大利亚) | en_AU |
英语(爱尔兰) | en_IE |
英语(芬兰) | en_FI |
芬兰语(芬兰) | fi_FI |
英语(丹麦) | en_DK |
丹麦语(丹麦) | da_DK |
英语(以色列) | en_IL |
希伯来语(以色列) | he_IL |
英语(南非) | en_ZA |
英语(印度) | en_IN |
英语(挪威) | en_NO |
英语(新加坡) | en_SG |
英语(新西兰) | en_NZ |
英语(印度尼西亚) | en_ID |
英语(菲律宾) | en_PH |
英语(泰国) | en_TH |
英语(马来西亚) | en_MY |
英语(阿拉伯) | en_XA |
韩文(韩国) | ko_KR |
日语(日本) | ja_JP |
荷兰语(荷兰) | nl_NL |
荷兰语(比利时) | nl_BE |
葡萄牙语(葡萄牙) | pt_PT |
葡萄牙语(巴西) | pt_BR |
法语(法国) | fr_FR |
法语(卢森堡) | fr_LU |
法语(瑞士) | fr_CH |
法语(比利时) | fr_BE |
法语(加拿大) | fr_CA |
西班牙语(拉丁美洲) | es_LA |
西班牙语(西班牙) | es_ES |
西班牙语(阿根廷) | es_AR |
西班牙语(美国) | es_US |
西班牙语(墨西哥) | es_MX |
西班牙语(哥伦比亚) | es_CO |
西班牙语(波多黎各) | es_PR |
德语(德国) | de_DE |
德语(奥地利) | de_AT |
德语(瑞士) | de_CH |
俄语(俄罗斯) | ru_RU |
意大利语(意大利) | it_IT |
希腊语(希腊) | el_GR |
挪威语(挪威) | no_NO |
匈牙利语(匈牙利) | hu_HU |
土耳其语(土耳其) | tr_TR |
捷克语(捷克共和国) | cs_CZ |
斯洛文尼亚语 | sl_SL |
波兰语(波兰) | pl_PL |
瑞典语(瑞典) | sv_SE |
西班牙语(智利) | es_CL |
根据Request Headers中的Accept-language来判断。
要求客户端第一次(或者每次)传递的自定义参数值来判断,如规定传locale,值为zh-cn、en-us等内容,如果只在第一次传入则local以及timeZone先关信息要存入session或者cookie中,后面的请求语言方式则直接从两者中取,其有效时间与session和cookie设置的生命周期关联。
当获取语言类型时没有找到对应类型时,会使用默认的语言类型。
<!-- 定义本地化变更拦截器 -->
<bean id="localeChangeInterceptor" class="org.springframework.web.servlet.i18n.LocaleChangeInterceptor" />
<!-- 定义注解URL映射处理器,所有的请求映射关联本地化拦截器,或者也可自定义该拦截器路径映射-->
<bean id="urlMapping" class="org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping">
<property name="interceptors" ref=" localeChangeInterceptor " />
<property name="order" value="1"></property>
</bean>
该种方式需要每次都在请求的url上带上local参数,指定该次需要的语言类型,并且该方式的local解析器需要配置,如下:
<a href="xxx.do?locale=zh_CN" rel="external nofollow" >中文</a>或<a href="xxx.do?locale=en" rel="external nofollow" >英文</a>
<bean id="localeResolver" class="org.springframework.web.servlet.i18n.AcceptHeaderLocaleResolver"/>
但在该配置下使用会抛Cannot change HTTP accept header - use a different locale resolution strategy异常,这是因为spring source做了限制,无法对本地的local赋值修改,解决办法如下,新建一个类MyLocaleResolver继承AcceptHeaderLocaleResolver,重写resolveLocale和setLocale方法,并将上面的localeResolver的class指向如下MyLocaleResolver类:
public class MyLocaleResolver extends AcceptHeaderLocaleResolver {
private Locale myLocal;
public Locale resolveLocale(HttpServletRequest request) {
return myLocal == null ? request.getLocale() : myLocal;
}
public void setLocale(HttpServletRequest request, HttpServletResponse response, Locale locale) {
myLocal = locale;
}
}
基于session的状态保存方式只需要在第一次请求的时候指定语言类型,localResolver会将该属性保存到session中,后面的请求直接从session中获取该语言类型,该种方式的localResolver对应的类为SessionLocaleResolver,如下配置:
<bean id="localeResolver" class="org.springframework.web.servlet.i18n.SessionLocaleResolver"/>
与session的机制类似,差异在于两者的存储和周期,鉴于安全、大小以及体验等因素的影响,实际使用中使用者更倾向于前者,该种cookie保存方式的localResolver为
<bean id="localeResolver" class="org.springframework.web.servlet.i18n.CookieLocaleResolver" />
对于语言类型的资源文件,需要开发者对文案进行搜集整理,并翻译成相应的语言确定关键字key,目前大多数情况是将这些信息置于.properties文件中,在使用的时候直接访问获取,当然也可置于数据库中,但频繁的文案获取会影响服务器性能及产品体验,可结合数据字典以及缓存工具使用。
<!-- 默认的注解映射的支持 -->
<mvc:annotation-driven validator="validator" conversion-service="conversionService" />
<!-- 资源文件 -->
<bean id="propertiesMessageSource" class="org.springframework.context.support.ResourceBundleMessageSource">
<property name="basenames">
<list>
<value>resource</value>
<value>validation</value>
</list>
</property>
</bean>
<bean id="databaseMessageSource" class="com.obs2.util.MessageResource">
<property name="parentMessageSource" ref="propertiesMessageSource"/>
</bean>
<bean id="messageInterpolator" class="com.obs2.util.MessageResourceInterpolator">
<property name="messageResource" ref="databaseMessageSource"/>
</bean>
<!-- 验证器 -->
<bean id="validator" class="org.springframework.validation.beanvalidation.LocalValidatorFactoryBean">
<property name="messageInterpolator" ref="messageInterpolator"/>
</bean>
这里定义了一个propertiesMessageSource,一个databaseMessageSourcer,和一个messageInterpolator。propertiesMessageSource用于读取properties文件databaseMessageSourcer用于读取数据库的数据配置,其中,有一个属性设置它的父MessageSource为propertiesMessageSource。意思是如果数据库找不到对应的数据,到properties文件当中查找。messageInterpolator是个拦截器。
@Entity
@SuppressWarnings("serial")
@Table(name="resource")
public class Resource implements Serializable {
@Id
@GeneratedValue(strategy=GenerationType.IDENTITY)
@Column(name="resource_id")
private long resourceId;
@Column(name="name", length=50, nullable=false)
private String name;
@Column(name="text", length=1000, nullable=false)
private String text;
@Column(name="language", length=5, nullable=false)
private String language;
public long getResourceId() {
return resourceId;
}
public void setResourceId(long resourceId) {
this.resourceId = resourceId;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getText() {
return text;
}
public void setText(String text) {
this.text = text;
}
public String getLanguage() {
return language;
}
public void setLanguage(String language) {
this.language = language;
}
}
定义了一张表[resource],字段有:[resource_id]、[name]、[text]、[language]。
/**
* 取得资源数据
* @author Robin
*/
public class MessageResource extends AbstractMessageSource implements ResourceLoaderAware {
@SuppressWarnings("unused")
private ResourceLoader resourceLoader;
@Resource
private ResourceService resourceService;
/**
* Map切分字符
*/
protected final String MAP_SPLIT_CODE = "|";
protected final String DB_SPLIT_CODE = "_";
private final Map<String, String> properties = new HashMap<String, String>();
public MessageResource() {
reload();
}
public void reload() {
properties.clear();
properties.putAll(loadTexts());
}
protected Map<String, String> loadTexts() {
Map<String, String> mapResource = new HashMap<String, String>();
List<com.obs2.service.bean.Resource> resources = resourceService.findAll();
for (com.obs2.service.bean.Resource item : resources) {
String code = item.getName() + MAP_SPLIT_CODE + item.getLanguage();
mapResource.put(code, item.getText());
}
return mapResource;
}
private String getText(String code, Locale locale) {
String localeCode = locale.getLanguage() + DB_SPLIT_CODE + locale.getCountry();
String key = code + MAP_SPLIT_CODE + localeCode;
String localeText = properties.get(key);
String resourceText = code;
if(localeText != null) {
resourceText = localeText;
}else {
localeCode = Locale.ENGLISH.getLanguage();
key = code + MAP_SPLIT_CODE + localeCode;
localeText = properties.get(key);
if(localeText != null) {
resourceText = localeText;
}else {
try {
if(getParentMessageSource() != null) {
resourceText = getParentMessageSource().getMessage(code, null, locale);
}
} catch (Exception e) {
logger.error("Cannot find message with code: " + code);
}
}
}
return resourceText;
}
@Override
public void setResourceLoader(ResourceLoader resourceLoader) {
this.resourceLoader = (resourceLoader != null ? resourceLoader : new DefaultResourceLoader());
}
@Override
protected MessageFormat resolveCode(String code, Locale locale) {
String msg = getText(code, locale);
MessageFormat result = createMessageFormat(msg, locale);
return result;
}
@Override
protected String resolveCodeWithoutArguments(String code, Locale locale) {
String result = getText(code, locale);
return result;
}
}
主要是重载AbstractMessageSource和ResourceLoaderAware,以实现Spring MVC的MessageSource国际化调用。类中的reload()方法,我把它写到了一个ServletListener当中,让项目启动时,自动加载数据到static的map中。
/**
* 系统启动监听
* @author Robin
*/
public class SystemListener implements ServletContextListener {
/**
* context初始化时激发
*/
@Override
public void contextInitialized(ServletContextEvent e) {
// 取得ServletContext
ServletContext context = e.getServletContext();
WebApplicationContext applicationContext = WebApplicationContextUtils .getWebApplicationContext(context);
// 设置国际化多语言
MessageResource messageSource = applicationContext.getBean(MessageResource.class);
messageSource.reload();
}
/**
* context删除时激发
*/
@Override
public void contextDestroyed(ServletContextEvent e) {
}
/**
* 创建一个 session时激发
* @param e
*/
public void sessionCreated(HttpSessionEvent e) {
}
/**
* 当一个 session失效时激发
* @param e
*
public void sessionDestroyed(HttpSessionEvent e) {
}
/**
* 设置 context的属性,它将激发attributeReplaced或attributeAdded方法
* @param e
*/
public void setContext(HttpSessionEvent e) {
}
/**
* 增加一个新的属性时激发
* @param e
*/
public void attributeAdded(ServletContextAttributeEvent e) {
}
/**
*删除一个新的属性时激发
* @param e
*/
public void attributeRemoved(ServletContextAttributeEvent e) {
}
/*
* 属性被替代时激发
* @param e
*/
public void attributeReplaced(ServletContextAttributeEvent e) {
}
}
该Listener需要加入到web.xml当中:
<!-- 系统启动监听 -->
<listener>
<listener-class>com.obs2.util.SystemListener</listener-class>
</listener>
/**
* 拦截Annotation验证信息
* @author Robin
*
*/
public class MessageResourceInterpolator implements MessageInterpolator {
@Resource
private MessageResource messageResource;
public void setMessageResource(MessageResource messageResource) {
this.messageResource = messageResource;
}
@Override
public String interpolate(String messageTemplate, Context context) {
String messageTemp = null;
if(messageTemplate.startsWith("{") && messageTemplate.endsWith("}")) {
messageTemp = messageTemplate.substring(1, messageTemplate.length() - 1);
}else {
return messageTemplate;
}
String[] params = (String[]) context.getConstraintDescriptor().getAttributes().get("params");
MessageBuilder builder = new MessageBuilder().code(messageTemp);
if (params != null) {
for (String param : params) {
builder = builder.arg(param);
}
}
String result = builder.build().resolveMessage(messageResource, Locale.ENGLISH).getText();
return result;
}
@Override
public String interpolate(String messageTemplate, Context context, Locale locale) {
String messageTemp = null;
if(messageTemplate.startsWith("{") && messageTemplate.endsWith("}")) {
messageTemp = messageTemplate.substring(1, messageTemplate.length() - 1);
}else {
return messageTemplate;
}
String[] params = (String[]) context.getConstraintDescriptor().getAttributes().get("params");
MessageBuilder builder = new MessageBuilder().code(messageTemp);
if (params != null) {
builder = builder.args(params);
}
String result = builder.build().resolveMessage(messageResource, locale).getText();
return result
}
}
<!-- 资源文件绑定器,文件名称:messages.properties(没有找到时的默认文件), messages_en.properties(英文),messages_zh_CN.properties(中文),等等-->
<bean id="messageSource" class="org.springframework.context.support.ResourceBundleMessageSource">
<property name="basename" value="config.messages.messages" />
<property name="defaultEncoding" value="UTF-8"/>
<property name="basename" value="i18n.messages"/>
<property name="useCodeAsDefaultMessage" value="true" />
</bean>
Spring定义了访问国际化信息的MessageSource接口,并提供了几个易用的实现类。首先来了解一下该接口的几个重要方法:
1)String getMessage(String code, Object[] args, String defaultMessage, Locale locale) code
表示国际化资源中的属性名;args用于传递格式化串占位符所用的运行期参数;当在资源找不到对应属性名时,返回defaultMessage参数所指定的默认信息;locale表示本地化对象;
2)String getMessage(String code, Object[] args, Locale locale) throws NoSuchMessageException
与上面的方法类似,只不过在找不到资源中对应的属性名时,直接抛出NoSuchMessageException异常;
3)String getMessage(MessageSourceResolvable resolvable, Locale locale) throws NoSuchMessageException
MessageSourceResolvable 将属性名、参数数组以及默认信息封装起来,它的功能和第一个接口方法相同。
MessageSource分别被HierarchicalMessageSource和ApplicationContext接口扩展,这里我们主要看一下HierarchicalMessageSource接口的几个实现类
HierarchicalMessageSource接口添加了两个方法,建立父子层级的MessageSource结构,类似于前面我们所介绍的HierarchicalBeanFactory。该接口的setParentMessageSource (MessageSource parent)方法用于设置父MessageSource,而getParentMessageSource()方法用于返回父MessageSource。
HierarchicalMessageSource接口最重要的两个实现类是ResourceBundleMessageSource和ReloadableResourceBundleMessageSource。它们基于Java的ResourceBundle基础类实现,允许仅通过资源名加载国际化资源。ReloadableResourceBundleMessageSource提供了定时刷新功能,允许在不重启系统的情况下,更新资源的信息。StaticMessageSource主要用于程序测试,它允许通过编程的方式提供国际化信息。而DelegatingMessageSource是为方便操作父MessageSource而提供的代理类。
<bean id=" messageSource "
class="org.springframework.context.support.ResourceBundleMessageSource">
<!--①通过基名指定资源,相对于类根路径-->
<property name="basenames">
<list>
<value>com/baobaotao/i18n/fmt_resource</value>
</list>
</property>
</bean>
<bean id="messageSource "
class="org.springframework.context.support.ReloadableResourceBundleMessageSource">
<property name="basenames">
<list>
<value>com/baobaotao/i18n/fmt_resource</value>
</list>
</property>
<!--①刷新资源文件的周期,以秒为单位-->
<property name="cacheSeconds" value="5"/>
</bean>
两者都是利用资源名通过getMessage()接口就可以加载整套的国际化资源文件,唯一区别在于ReloadableResourceBundleMessageSource可以定时刷新资源文件,以便在应用程序不重启的情况下感知资源文件的变化。很多生产系统都需要长时间持续运行,系统重启会给运行带来很大的负面影响,这时通过该实现类就可以解决国际化信息更新的问题。上面的配置中cacheSeconds属性让ReloadableResourceBundleMessageSource每5秒钟刷新一次资源文件(在真实的应用中,刷新周期不能太短,否则频繁的刷新将带来性能上的负面影响,一般不建议小于30分钟)。cacheSeconds默认值为-1表示永不刷新,此时,该实现类的功能就蜕化为ResourceBundleMessageSource的功能。
引入标签库:
<%@ taglib prefix="spring" uri="http://www.springframework.org/tags" %>
获取文案:
<s:message code="test.app"/>
<spring:message code="main.title" />
引入标签库:
<%@ taglib prefix="c" uri="http://java.sun.com/jstl/core_rt"%>
获取文案:
<fmt:message key="test.app"/>
@Autowired
private MessageSource messageSource;
String s = messageSource.getMessage("SystemError", new Object[]{}, Locale.US);
a. 获取容器
容器已经初始化:
WebApplicationContext wac = ContextLoader.getCurrentWebApplicationContext();
容器没有初始化:
String[] configs = {"com/baobaotao/i18n/beans.xml"};
ApplicationContext ctx = new ClassPathXmlApplicationContext(configs);
b. 获取bean跟文案
MessageSource ms = (MessageSource) wac.getBean("myResource");
Object[] params = {"John", new GregorianCalendar().getTime()};
String str1 = ms.getMessage("greeting.common",params,Locale.US);
在前面的MessageSource类图结构中我们发现ApplicationContext实现了MessageSource的接口,也就是说ApplicationContext的实现类本身也是一个MessageSource对象。
将ApplicationContext和MessageSource整合起来, Spring此处的设计人为:在一般情况下,国际化信息资源应该是容器级。我们一般不会将MessageSource作为一个Bean注入到其他的Bean中,相反MessageSource作为容器的基础设施向容器中所有的Bean开放。只要我们考察一下国际化信息的实际消费场所就更能理解Spring这一设计的用意了。国际化信息一般在系统输出信息时使用,如Spring MVC的页面标签,控制器Controller等,不同的模块都可能通过这些组件访问国际化信息,因此Spring就将国际化消息作为容器的公共基础设施对所有组件开放。
既然一般情况下我们不会直接通过引用MessageSource Bean使用国际信息,那如何声明容器级的国际化信息呢? Spring容器启动过程时,在初始化容器的时候通过initMessageSource()方法所执行的工作就是初始化容器中的国际化信息资源,它根据反射机制从BeanDefinitionRegistry中找出名称为“messageSource”且类型为org.springframework.context.MessageSource的Bean,将这个Bean定义的信息资源加载为容器级的国际化信息资源。请看下面的配置:
<!--注册资源Bean,其Bean名称只能为messageSource -->
<bean id="messageSource"
class="org.springframework.context.support.ResourceBundleMessageSource">
<property name="basenames">
<list>
<value>com/baobaotao/i18n/fmt_resource</value>
</list>
</property>
</bean>
a. 获取容器
容器已经初始化:
WebApplicationContext wac = ContextLoader.getCurrentWebApplicationContext();
容器没有初始化:
String[] configs = {"com/baobaotao/i18n/beans.xml"};
ApplicationContext ctx = new ClassPathXmlApplicationContext(configs);
b. 获取bean跟文案
Object[] params = {"John", new GregorianCalendar().getTime()};
String str1 = ctx.getMessage("greeting.common",params,Locale.US);
a. 改变properties文件编码为UTF-8/GBK,然而ResourceBundleMessageSource的默认编码defaultEncoding是ISO-8859-1,需要在xml中增加一个相应属性将其改变为你需要的UTF-8/GBK之类。
b. 如果资源文件想统一使用ISO-8859-1格式,可以将原本用UTF-8写好的中文资源文件使用jdk自带的工具native2ascii将UTF-8文件和内容转为ISO-8859-1文件,其中的中文内容会使用16进制unicode编码为\u****格式:
cmd命令:
JAVA_HOME\bin\native2ascii -encoding UTF-8 messages_zh_CN.properties messages_zh_C1N.properties
感谢你能够认真阅读完这篇文章,希望小编分享的“Java Spring项目国际化的示例分析”这篇文章对大家有帮助,同时也希望大家多多支持亿速云,关注亿速云行业资讯频道,更多相关知识等着你来学习!
亿速云「云服务器」,即开即用、新一代英特尔至强铂金CPU、三副本存储NVMe SSD云盘,价格低至29元/月。点击查看>>
免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。