转自:https://blog.csdn.net/flowingflying/article/details/76358970

spring中ResourceBundleMessageSource的配置使用方法

https://blog.csdn.net/qq_31230529/article/details/48436873

一般性了解

我们在JSTL fmt[1]中已经接触过国际化i18n,本地化L10n。使用JSTL fmt(Internationalization and Formatting tag library),有一些局限性:

  1. 我们需要将resource bundles(那些properties文件)放在在eclipse的src/main/resources中,也就是放在了/WEB-INF/classes中。使用classpath的要注意,这个位置是比较特别,JVM将永久缓存,直至web app终结。如果我们修改文件的内容,修改的内容不会被再次读入,除非重新加载web app。fmt无法使用其他地方的文件或者数据库。
  2. fmt通过HTTP请求的Accept-Language来进行判断使用使用哪种本地化。我们现在看到很多网页有个语言选择,选择后,后续的网页都采用这种语言,fmt在这方面不支持,因为浏览器不会根据网页的选择而修改其默认语言(进而修改Accept-Language)。

Spring的i18n解决这些问题,并提供更为便捷的方式;不仅在jsp中使用,还可以在代码中使用;可以直接错误object(如Throwable)传递到本地化API,实现直接本地化错误信息。

Resource bundle和message format

和fmt一样,Spring的i18n也使用resource bundle(java.util.ResourceBundle)和message format(java.text.MessageFormat),并在resource bundle之上提供抽象的message source,变得更容易本地化。resource bundle是消息中需要本地化的消息格式的集合。

message format

消息格式(java.text.MessageFormat)含有占位模板,在运行时被替换。例子如下

There are {0} cats on the farm.
There are {0,number,integer} cats on the farm.
The value of Pi to seven significant digits is {0,number,#.######}.
My birthdate: {0,date,short}. Today is {1,date,long} at {1,time,long}.
My birth day: {0,date,MMMMMM-d}. Today is {1,date,MMM d, YYYY} at {1,time,hh:mma).
There {0,choice,0#are no cats|1#is one cat|1<are {0,number,integer} cats}.

这些占位符号(placeholder)是有顺序的,替换是按这个顺序,而不是语句中出现的顺序(语句中的顺序本来就是要本地化)。

With a {0,number,percentage} discount, the final price is {1,number,currency}.

占位符是可以带格式的,下面#表示序号,斜体表示用户自定义的格式值

{#}
{#,number}
{#,number,integer}
{#,number,percent}
{#,number,currency}
{#,number,自定义格式,遵循java.text.DecimalFormat}
{#,date}
{#,date,short}
{#,date,medium}
{#,date,long}
{#,date,full}
{#,date,自定义格式,遵循java.text.SimpleDateFormat}
{#,time}
{#,time,short}
{#,time,medium}
{#,time,long}
{#,time,full}
{#,time,自定义格式,遵循java.text.SimpleDateFormat}
{#,choice,自定义格式,遵循java.text.ChoiceFormat}

resource bundle的命名规则

resource bundle是这些message format的集合,一般采用properties文件的方式,key就是message code,value就是message format。例如:

alerts.current.date=Current Date and Time: {0,date,full} {0,time,full}

文件的命名规则如下:

[baseName]_[language]_[script]_[region]_[variant]
[baseName]_[language]_[script]_[region]
[baseName]_[language]_[script]
[baseName]_[language]_[region]_[variant]
[baseName]_[language]_[region] 例如labels_en_US.properties
[baseName]_[language] 例如labels_en.properties

以上前面的比后面具有优先权。如果只有baseName,language和variant,最匹配的是第4个,例子为:baseName_en__JAVA

对于JSTL fmt:

  1. ResourceBundle首先根据指定的bundle名字下载和实例化一个ResourceBundlede扩展类。
  2. 如果找不到,将当中的"."提到为"/",加上".properties"的后缀,在classpath中再找,返回一个PropertyResourceBundle类。
  3. 如果还找不到,使用fallback Locale来产生的可能的bundle名字,再找
  4. 还找不到,则找寻baseName的类或文件。(即我们可以使用baseName的文件作为缺省的匹配)
  5. 还找不到,抛出异常。

如果我们需要从数据库中获取,就需要自己实现ResourceBundle,然而ResourceBundle实例在同一时刻,只能支持一个Locale,它解析message的方法不带locale参数,因此需要为不同的locale提供不同的ResourceBundle实例。这导致实现的复杂。

Spring的message source

Spring的message source在resource bundle之上提供了抽象和封装,实现了org.springframework.context.MessageSource接口,接口提供了三个方法,可以看到locale是方法的参数,无需为不同的locale创建不同的实例,此外,已经返回解析后的message,无需再根据message format进行解析。

目前,Spring提供连个MessageSource接口的实现:

  • org.springframework.context.support.ResourceBundleMessageSource,里面含有ResourceBundle的集合,使用ResourceBundle的getBundle()来定位资源,因此是和ResourceBundle完全相同的策略,即资源必须是在classpath下,因此受限不能更新。
  • org.springframework.context.support.ReloadableResourceBundleMessageSource,里面并不含有ResourceBundle,但使用相同的检测策略,只支持文件,不支持类,可以放在非calsspath,即reloadable,通常为/WEB-INF/i18n。

ReloadableResourceBundleMessageSource小例子

设置properties文件

在/WEB-INF/i18n目录下设置两个资源文件。

test_en_US.properties

foo.test = Hello, {0} {1}
foo.message = This is the default message. Args: {0}, {1}, {2}.

test_zh_CN.properties文件需要注意,在eclipse下,properties文件缺省采用ISO-8859-1编码,例子如下

foo.test = \u60A8\u597D, {1} {0}

这样看简直是疯狂,因此,我们首先要将文件编码改为UTF-8等可以看中文的方式,点击该文件,按右键选择Properties,然后进行修改。

foo.test = 您好, {1} {0}
foo.message = 这是缺省消息,参数: {0}, {1}, {2}.

配置messageSource Bean

我们在root上下文中进行设置

public class RootContextConfiguration implements AsyncConfigurer,SchedulingConfigurer{
//... 略 ... @Bean //【注意】这个bean的名字必须叫messageSource,否则无效
public MessageSource messageSource(){
ReloadableResourceBundleMessageSource messageSource = new ReloadableResourceBundleMessageSource();
//如果设置为-1,表示Cache forever。一般生产环境下采用-1,开发环境为了方便调测采用某个正整数,规范地我们可通过profile来定义
messageSource.setCacheSeconds(5);
/* 设置缺省的资源文件的编码,
* 如果个别文件采用其他编码(不适用缺省编码,但一般我们应统一进行设置),可以通过setFileEncoding()来指定
* Properties properties = new Properties();
* properties.setProperty("/WEB-INF/i18n/test_zh_CN", "GBK");
* messageSource.setFileEncodings(properties); */
messageSource.setDefaultEncoding(StandardCharsets.UTF_8.name());
//设置properties文件的basename,以便找到响应的资源文件
messageSource.setBasenames("/WEB-INF/i18n/messages", "/WEB-INF/i18n/errors","/WEB-INF/i18n/test");
return messageSource;
}
}

小例子中我们采用文件作为资源,在实际中也可以会使用到数据库,例如NoSQL仓库,我们需要构建自定义的message source,这在后面介绍。

在代码中使用

@Service
public class MyTestService implements TestService{
private static final Logger logger = LogManager.getLogger();
@Inject MessageSource messageSource; @Override
public void testSourceMessage() {
String msg = this.messageSource.getMessage("foo.message", new Object[]{"One","Two","Three"}, Locale.US);
logger.info(msg);
msg = this.messageSource.getMessage("foo.message", new Object[]{"一","二","三"}, Locale.getDefault());
logger.info(msg);
msg = this.messageSource.getMessage("foo.lable", new Object[]{"Hi"}, "{0}, my friend", Locale.ENGLISH);
logger.info(msg);
}
}

输出结果:

14:43:36.789 [INFO ] MyTestService:20 testSourceMessage() - This is the default message. Args: One, Two, Three.
14:43:36.790 [INFO ] MyTestService:22 testSourceMessage() - 这是缺省消息,参数: 一, 二, 三.
14:43:36.791 [INFO ] MyTestService:24 testSourceMessage() - Hi, my friend

Locale.US将匹配test_en_US.properties,如果我们设置Locale.ENGLISH,由于并没有test_en.propertiesst,将采用缺省的Locale值,即匹配了test_zh_CN.properties,如果仍没有找到,采用default message的参数。

在jsp中使用

测试案例的Controller相关代码如下:

    @RequestMapping(value = "/test", method = RequestMethod.GET)
public String test(Map<String, Object> model){
model.put("firstName", "San");
model.put("lastName", "Zhang");
return "home/test";
}

在jsp中使用spring:message,如下。使用方法参考文档[2]

<%@ taglib prefix="spring" uri="http://www.springframework.org/tags" %>
... ...
<spring:message code="foo.test">
<spring:argument value="${firstName}" />
<spring:argument value="${lastName}" />
</spring:message> <!-- 设置htmlEscape,确保安全;如果foo.test没有找到,采用缺省的text内容-->
<spring:message code="foo.test" htmlEscape="true" text="Hi, {0} {1}"> ... ... </spring:message> <!-- 可以通过arguments一次性将所有参数列上,分隔符缺省是逗号,也可以通过argumentSeparator设定分隔符-->
<spring:message code="foo.test" arguments="A,B" />
<spring:message code="foo.test" argumentSeparator=":" arguments="A:B" /> <!-- 直接传递MessageSourceResolvable对象,具体在后面介绍-->
<spring:message message="${exception}" />

我们可以得到页面输出您好, Zhang San。spring:message和fmt:message很相似,同样有htmlEscape属性,缺省为false。如果某个jsp中有大量需要htmlEscape的spring:message,我们可以在jsp中设置 <spring:htmlEscape defaultHtmlEscape="true" />,这将对之后的代码有效。如果我们整个app都有大量的htmlEscape,我们可以在web.xml中配置。

<context-param>
<param-name>defaultHtmlEscape</param-name>
<param-value>true</param-value>
</context-param>

我们仍可以沿用JSTL fmt tag,如下,也可得到正确的输出。

<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %>
... ...
<fmt:message key="foo.test">
<fmt:param value="${firstName}" />
<fmt:param value="${lastName}" />
</fmt:message>
... ...

我们去查看log,无论是采用spring的tag还是fmt的tag,log是同样的,同时通过ReloadableResourceBundleMessageSource bean来进行处理。

16:23:45.837 [TRACE] (Spring) JstlView - Rendering view with name 'home/test' with model {firstName=San, lastName=Zhang} and static attributes {}
16:23:45.837 [DEBUG] (Spring) JstlView - Added model object 'firstName' of type [java.lang.String] to request in view with name 'home/test'
16:23:45.837 [DEBUG] (Spring) JstlView - Added model object 'lastName' of type [java.lang.String] to request in view with name 'home/test'
16:23:45.837 [DEBUG] (Spring) JstlView - Forwarding to resource [/WEB-INF/jsp/view/home/test.jsp] in InternalResourceView 'home/test'
16:23:45.859 [DEBUG] (Spring) ReloadableResourceBundleMessageSource - Re-caching properties for filename [/WEB-INF/i18n/messages_zh_CN] - file hasn't been modified
16:23:45.859 [DEBUG] (Spring) ReloadableResourceBundleMessageSource - No properties file found for [/WEB-INF/i18n/messages_zh] - neither plain properties nor XML
16:23:45.859 [DEBUG] (Spring) ReloadableResourceBundleMessageSource - No properties file found for [/WEB-INF/i18n/messages] - neither plain properties nor XML
16:23:45.862 [DEBUG] (Spring) ReloadableResourceBundleMessageSource - Re-caching properties for filename [/WEB-INF/i18n/errors_zh_CN] - file hasn't been modified
16:23:45.863 [DEBUG] (Spring) ReloadableResourceBundleMessageSource - No properties file found for [/WEB-INF/i18n/errors_zh] - neither plain properties nor XML
16:23:45.863 [DEBUG] (Spring) ReloadableResourceBundleMessageSource - No properties file found for [/WEB-INF/i18n/errors] - neither plain properties nor XML
16:23:45.866 [DEBUG] (Spring) ReloadableResourceBundleMessageSource - Re-caching properties for filename [/WEB-INF/i18n/test_zh_CN] - file hasn't been modified
16:23:45.866 [TRACE] (Spring) DispatcherServlet - Cleared thread-bound request context: org.apache.catalina.connector.RequestFacade@e05013
16:23:45.866 [DEBUG] (Spring) DispatcherServlet - Successfully completed request

没有经过JstlView的jsp

采用<spring>和<fmt>貌似一样,但是fmt是通过spring框架的ReloadableResourceBundleMessageSource来实现,如果有某个jsp文件,并没有经过spring框架,fmt就是标准的fmt,不能读取/WEB-INF/i18n目录下的资源文件,会报异常。这种情况也是常有的,例如error.jsp就可能不通过spring框架。对于这种情况有两种解决方案:

  1. 采用spring tag,将通过spring框架使用MessageSource。
  2. 使用filter,强制所有的jsp都通过spring框架。

第一种方式很简单,推荐使用。如果有与某种原因不能使用第一种方式,需要采用filter,我们首先要将filter设置为spring的bean,这样它才可以注入message source,方式和之前学习的Listener一样。我们在Bootstrap初始化root context后代码设置filter,这样确保可以注入root context中的bean,设置相关的jsp经过它。下面是相关filter的代码:

public class JstlLocalizationContextFilter implements Filter {
private MessageSource jstlMessageSource;
@Inject MessageSource messageSource; public JstlLocalizationContextFilter() { } @Override
public void destroy() { } @Override
public void doFilter(ServletRequest request,ServletResponse response,FilterChain chain)
throws IOException,ServletException {
// Exposes JSTL-specific request attributes specifying locale and resource bundle for
// JSTL's formatting and message tags,using Spring's locale and MessageSource.
JstlUtils.exposeLocalizationContext((HttpServletRequest)request, this.jstlMessageSource );
chain.doFilter(request, response);
} @Override
public void init(FilterConfig fConfig) throws ServletException {
ServletContext servletContext = fConfig.getServletContext();
WebApplicationContext rootContext = WebApplicationContextUtils
.getRequiredWebApplicationContext(servletContext);
AutowireCapableBeanFactory factory = rootContext.getAutowireCapableBeanFactory();
factory.autowireBeanProperties(this, AutowireCapableBeanFactory.AUTOWIRE_BY_TYPE,true);
factory.initializeBean(this,"JstlLocalizationContextFilter"); /* 【1】第一个参数servletContext首先检查JSTL的"javax.servlet.jsp.jstl.fmt.localizationContext"
* 的context-param(web.xml),如果有设置,生成相应的ResourceBundleMessageSource实例。
* 【2】第二个参数为spring的MessageSource。
* 将根据这两个参数获得子message source */
this.jstlMessageSource = JstlUtils.getJstlAwareMessageSource( servletContext, this.messageSource );
}
}

相关文章相关链接: 我的Professional Java for Web Applications相关文章

spring boot 国际化MessageSource的更多相关文章

  1. 玩转spring boot——国际化

    前言 在项目开发中,可能遇到国际化的问题,而支持国际化却是一件很头疼的事.但spring boot给出了一个非常理想和方便的方案. 一.准备工作 pom.xml: <?xml version=& ...

  2. Spring boot 国际化自动加载资源文件问题

    Spring boot 国际化自动加载资源文件问题 最近在做基于Spring boot配置的项目.中间遇到一个国际化资源加载的问题,正常来说只要在application.properties文件中定义 ...

  3. 58. Spring Boot国际化(i18n)【从零开始学Spring Boot】

    国际化(internationalization)是设计和制造容易适应不同区域要求的产品的一种方式.它要求从产品中抽离所有地域语言,国家/地区和文化相关的元素.换言之,应用程序的功能和代码设计考虑在不 ...

  4. Spring Boot国际化开发实战

    本章将讲解如何在Spring Boot和Thymeleaf中做页面模板国际化的支持,根据系统语言环境或者session中的语言来自动读取不同环境中的文字. 国际化自动配置 Spring Boot中已经 ...

  5. Spring Boot国际化支持

    本章将讲解如何在Spring Boot和Thymeleaf中做页面模板国际化的支持,根据系统语言环境或者session中的语言来自动读取不同环境中的文字. 国际化自动配置 Spring Boot中已经 ...

  6. Spring Boot 国际化及点击链接跳转国家语言

    一.国际化 在SpringBoot中已经自动帮我们配置管理国际化资源的组件,所以我们只需要编写代码就可. @Bean @ConfigurationProperties(prefix = "s ...

  7. Spring boot国际化

    国际化主要是引入了MessageSource,我们简单看下如何使用,以及其原理. 1.1 设置资源文件 在 properties新建i18n目录 新建message文件: messages.prope ...

  8. spring boot 与 thymeleaf (1): 国际化

    在thymeleaf 里面有个消息表达式: #{...} , 可以借此来实现国际化. 在我使用这个功能的时候, 碰到了一个问题, 按照 JavaEE开发的颠覆者 Spring Boot实战  上面编码 ...

  9. Spring Boot Security 国际化 多语言 i18n 趟过巨坑

    网上很多的spring boot国际化的文章都是正常情况下的使用方法 如果你像我一样用了Spring Security 那么在多语言的时候可能就会遇到一个深渊 Spring Security里面的异常 ...

随机推荐

  1. Elven Postman---hdu5444(二叉树)

    题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=5444  有一个序列,由这个序列可以画出一颗二叉树(每个节点的左边(W)都比它大,右边(E)都比它小), ...

  2. 使用nginx搭建文件下载服务器

    搭建一个文件服务器的方式有很多,本文介绍笔者曾经用过的两种: 使用nginx 使用java服务,通过controller提供 一.使用nginx搭建 在nginx.conf中直接配置server即可, ...

  3. mytest3.py-api接入平台获取数据

    mytest3.py-api接入平台获取数据 import base64 import datetime import hashlib import urllib import urllib.pars ...

  4. 【云安全与同态加密_调研分析(5)】云安全标准现状与统计——By Me

  5. 汇编的WEAK关键字

    一般来说,这个关键字使用在IMPORT和EXPORT这两个声明段. ////////////////////////////////////////////////////////////////// ...

  6. 如何使用别人的代码 (特指在MFC里面 或者推广为C++里面)

    别人写了一堆代码,给了你源代码.在C++里面 应该是  头文件(.h)和源文件(.cpp).  那么我们如何使用他们呢?? 第一步:将其包含进来 如下图  ,不论是头文件还是源文件都如此 第二步:告诉 ...

  7. Postman中使用Postman Interceptor 发送带Cookie 的请求

    使用Postman 发送Cookie 的请求时,发现无法发送成功, 显示"Restricted Header (use Postman Interceptor)" 提示. 网上搜了 ...

  8. 元类 metaclass

    metaclass 类由Type创建 对象由创建 MetaClass作用 用来指定当前类由谁来创建(默认type创建). MetaClass 会被继承,如果父类指定了元类,那么子类也是由这个元类创建 ...

  9. php int 与 datetime 转换

    数据库日期类型是int类型的,该查询结果是datetime类型的 select from_unixtime( `dateline` ) from cdb_posts 如果原来类型是datetime类型 ...

  10. Linux系统——inode和block

    Linux文件属性 磁盘被分区并格式化为ext4文件系统后,会生成一定数量的inode和block Inode 索引节点 作用:存放文件的属性信息以及作为文件的索引(指向文件的实体block) Blo ...