spring boot 国际化MessageSource

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

spring中ResourceBundleMessageSource的配置使用方法

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

通常性了解

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

  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,实现直接本地化错误信息。html

Resource bundle和message format

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

message format

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

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)是有顺序的,替换是按这个顺序,而不是语句中出现的顺序(语句中的顺序原本就是要本地化)。git

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

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

{#} {#,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。例如:spring

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__JAVAapache

对于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相关文章

相关文章
相关标签/搜索