spring boot 國際化MessageSource


轉自: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相關文章


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM