小男孩‘自慰网亚洲一区二区,亚洲一级在线播放毛片,亚洲中文字幕av每天更新,黄aⅴ永久免费无码,91成人午夜在线精品,色网站免费在线观看,亚洲欧洲wwwww在线观看

分享

國際化之struts2實現(xiàn)研究

 宇宙之窗 2013-09-22

國際化之Struts2實現(xiàn)研究

 

一、基本原理

先不提Struts這一工具,也不用其他現(xiàn)成的工具,如何實現(xiàn)國際化?

最基本的實現(xiàn)就是,根據(jù)不同的Locale讀取不同的文本。

例如有兩個資源文件:

第一個:ApplicationResources_zh_CN.properties

第二個:ApplicationResources_en_US.properties

當(dāng)Locale=zh_CN時,就去第一個文件查找;當(dāng)Locale=en_US時,就去第二個文件查找。

 

二、自己寫方案去實現(xiàn)

明白這個原理后,我們可以自己編寫一套工具類,去實現(xiàn)國際化。通常,為了方便,我們需要自定義一個頁面標(biāo)簽,類似于<s:text>那種,可以根據(jù)Locale獲取相應(yīng)語言的字符串。

 

三、借助Struts2

其實,Struts2也是通過這個原理去實現(xiàn)國際化的。我們何必再重復(fù)造輪子?

 

Struts2是開源的,源代碼全都有,如果你的項目沒有用到Struts2,也沒有其他簡便的國際化工具,我想你照搬Struts2那一套也不難。

 

四、Struts2國際化研究

 

本人查閱了很多網(wǎng)上的資料,其實Struts2國際化,有個問題。

 

Struts2的頁面國際化,默認(rèn)要走action才行,也就是如果你直接訪問jsp文件,它是沒有國際化效果的,除非每個jsp都通過action去訪問(這也是Struts2推薦的方式)。

 

通常,大家都會寫一個通用Action,去轉(zhuǎn)發(fā)所有jsp。

比如我有個通用I18nAction,名為i18n,現(xiàn)在要從index.jsp直接跳轉(zhuǎn)到main.jsp,如果寫成

href="main.jsp"

這樣跳轉(zhuǎn)過去,main.jsp是沒有國際化效果的,因為它沒有經(jīng)過action處理,所有要寫成:

href="i18n.action?jsp=main.jsp"

我們將jsp的路徑以一個參數(shù)的形式,交給action,action再去轉(zhuǎn)發(fā)。

 

但是我們不想這么麻煩,每次都要寫i18n.action,所以,高手們想,是不是能夠編寫一個過濾器(Filter) 自動實現(xiàn)此功能?當(dāng)然可以!

我們編寫一個Filter,攔截所有的jsp訪問,然后轉(zhuǎn)交給i18n.action去處理。

 

OK,這算是一種方法,不過,網(wǎng)上能夠找到這種教程,所以我不再多講,有興趣可以baidu或google。

 

這種方法有個劣勢,就是如果你直接訪問jsp,那還是沒有國際化效果。而且攔截器可能帶來一些問題,因為它攔截了所有jsp,有時我們并不希望這樣做。

 

我要講另外一種方法,可以直接訪問jsp,而無需經(jīng)過action,當(dāng)然也就無需攔截器。

我們從基本原理入手,從問題的根源入手,Struts2國際化是怎么實現(xiàn)的呢,其入口不是action,而是<s:text>標(biāo)簽,只要我們能找出<s:text>標(biāo)簽實現(xiàn)的源碼,并稍作修改,就可以使其按照我們的模式去工作。我就是這么做的。

 

<s:text>標(biāo)簽的源碼是怎樣一個邏輯呢?請看下面的代碼段:

                             

(取自com.opensymphony.xwork2.util.LocalizedTextUtil.java)

就是根據(jù)Locale去尋找aTextName對應(yīng)的value,

直接訪問jsp時,之所以沒有國際化效果,是因為Locale設(shè)置有問題。

Struts2獲取text時默認(rèn)取Loacle的方式為:

ActionContext.getContext().getLocale()

getLocale()方法源碼如下:

 

這個getLocale()方法,是從

Map<String,Object> context里面去找

key= "com.opensymphony.xwork2.ActionContext.locale"的Object,這個Object就是Locale對象。

 

我們需要明確一點:

LocaledefultLocale=Locale.getDefault();

是獲取操作系統(tǒng)的locale,這個值我們不應(yīng)該改變(一改就會涉及到所有用戶),也不推薦使用。

 

我們要根據(jù)瀏覽器去設(shè)置LOCALE值?怎么辦呢?

打開IE的語言設(shè)置,我們可以看到,可以設(shè)置多個語言,所以說實際上瀏覽器端的Locale是一個列表。通過request可以獲得它:

   Enumeration locales=request.getLocales();

   while(locales.hasMoreElements())

   {

      LocaleclientLocale=(Locale)locales.nextElement();

      out.println("國別:"+clientLocale.getDisplayCountry()+"<br>");

      out.println("語言:"+clientLocale.getDisplayLanguage()+"<br>");

   }

另外,獲取客戶端用戶設(shè)置的第一個locale:

Localefirst=request.getLocale();

 

如此,我們有了瀏覽器端的Locale,但是每次都去request里面取,是不是有些麻煩?稍后,可以改進(jìn)一下。

 

這還不夠,我們要做到的是根據(jù)用戶的選擇,去切換語言類型。

   不同的瀏覽器、不同的訪問應(yīng)該有不同的Locale,所以應(yīng)該把Locale放在HttpSession中。所以說切換語言其實很簡單,將Locale存入Session中,然后國際化的時候從Session中去尋找Locale就行了。

 

綜上,總結(jié)出國際化的步驟:

第一點:默認(rèn)情況下(用戶沒有切換語言),則Session里面沒有Locale值,此時從用戶請求的瀏覽器端讀取,并設(shè)置到Session中。

第二點:用戶選擇了切換語言,則將切換后的語言設(shè)置到Session中。

 

第一點Struts2是做到了,每次訪問一個jsp,或Action,Struts都會new一個新的Map<String, Object> context,如下源碼所示:

 

(取自org.apache.struts2.dispatcher. Dispatcher.java)

但是第二點,Struts的處理方式就不是我想要的形式,Struts是怎樣切換語言環(huán)境的呢?

是在action后面加request_locale參數(shù),例如

changeLan.action?request_locale=en_US

執(zhí)行每個action時,它都會去檢查是否有request_locale這個參數(shù),如果有就會將Locale設(shè)置到session里面,其key為:"WW_TRANS_I18N_LOCALE"

同時執(zhí)行:

ActionContext.setLocale(locale);改變ActionContext里面的Map<String, Object>context值

我之前說了:

Struts2使用Locale時,默認(rèn)是從ActionContext中?。?/p>

ActionContext.getContext().getLocale()

那是不是以后取出的Locale都是第一次設(shè)置的locale呢?

答案是否定的。實際上每次訪問一個jsp或Action時,Struts都會new一個新的Map<String, Object> context,并且如果是訪問的Action,還會額外的經(jīng)過一個名叫I18nInterceptor的攔截器,當(dāng)session里面不存在Locale時,它會添加進(jìn)去,如果存在就不添加。最后重新設(shè)置context里面的Locale(這就說明,每次訪問Action時,ActionContext里面的Locale都是新的)。見下面的源碼:

 

 

看到?jīng)]有,這個攔截器會攔截所有Action,當(dāng)locale==null(也即requested_locale==null)時就會去session中取Locale(如果沒有,則取ActionContext.getLocale),且最后,始終會執(zhí)行saveLocale方法,這個方法調(diào)用了ActionContext.setLocale(locale),重新設(shè)置Locale。

 

所有說,按Struts的模式(action后面加request_locale參數(shù))切換了語言后,當(dāng)訪問Action時,locale實際上是從session里面取出來的,但是當(dāng)訪問jsp時,因為I18nInterceptor攔截器不會執(zhí)行,而ActionContext里面的Map<String, Object>context又是新new出來的,且在new context時,用的是request.getLocale()(見上面我摘取的Dispatcher.java源碼),所以訪問jsp時,locale不是從session中取到的,而是讀取的瀏覽器Locale。

 

好了,我們知道struts2的這個毛病之后,應(yīng)該怎樣改進(jìn)呢?

很簡單的邏輯:訪問jsp時,如果session里面的Locale不為空,就應(yīng)該以它為準(zhǔn),而不是以瀏覽器的Locale為準(zhǔn)(除非session里面的Locale為空)。

 

顯然,我們不希望每個jsp都通過action,進(jìn)而通過I18nInterceptor攔截器。

我們希望直接訪問jsp就能實現(xiàn)我們想要的那種效果。

 

用戶選擇切換語言時,session里面一定是有我們想要的那個Locale的(我們可以設(shè)置進(jìn)去)。

 

關(guān)鍵是<s:text>標(biāo)簽獲取Locale時出了問題,上面我已經(jīng)說過,它是從

ActionContext.getContext().getLocale()

里面去拿的,在直接訪問jsp的情況下,它的Locale值是瀏覽器端的語言。我們將其換成session里面的值不就行了?Yes!

下面就是我改造后的getLocale()方法:

 

或者換一個更標(biāo)準(zhǔn)的寫法:

 

 

OK,如此一來,國際化就完美了,我們做一個changeLocale.jsp,嵌入到指定頁面,只要用戶一切換語言,訪問其他jsp時就能正確的國際化了,不需要通過action,不需要攔截器。此時整個環(huán)境也都是用戶選擇的那種Locale,所以即使在java代碼中,也能正確的識別并做國際化處理。

   另外,當(dāng)用戶不切換語言時,我們能識別用戶使用的瀏覽器語言,因為我們默認(rèn)設(shè)置的是request.getLocale(),當(dāng)用戶切換語言后,session里面有Locale了,以后就用從session里面讀取的Locale。

 

另外,補充一點在國際化研究中,實踐得出的一些關(guān)于Session的理解:

服務(wù)器重啟,session仍然有效

瀏覽器重啟,session失效

可見session應(yīng)該是雙向的,服務(wù)器存一個,瀏覽器端也存一個。如果

服務(wù)器重啟,它的session沒有丟失(應(yīng)該是保存在了磁盤上),而

瀏覽器重啟,則session丟失(應(yīng)該是保存在瀏覽器端的內(nèi)存中)

 

進(jìn)一步研究得出結(jié)論:

session是服務(wù)器端和瀏覽器端雙向交互的。不過瀏覽器端存的是sessionid(Tomcat下Java程序通常是一個32位字符串,這個id是存在cookie中的,名為JSESSIONID,如下圖是我在FireFox中捕捉到的),而服務(wù)器端存的是session對象,當(dāng)瀏覽器訪問服務(wù)器時,如果是以jsp、action等非靜態(tài)訪問形式,第一次連接時,服務(wù)器會新建一個session對象,以后的訪問中,只要瀏覽器沒有重啟或者session沒有過期或銷毀,那么這個session是不會變的,也就是說后面用的session都是第一次建立的那個。

 

 

實際上,訪問jsp時,會調(diào)用如下方法:

HttpServletRequestgetSession(true);

該方法,如果session為空,則會new一個session,否則,返回已有的session。官方解釋為:

publicHttpSession getSession(boolean arg)

   Returns the current HttpSession associatedwith this request or, if if there is no current session and arg is true,returns a new session.

   If arg is false and the request has no validHttpSession, this method returns null.

 

Tomcat默認(rèn)是啟用了session持久化技術(shù)的(session persistence),也就是說服務(wù)器關(guān)閉后,session會存在磁盤上(文件名為session.ser),重啟服務(wù)器時,只要session沒過期,仍然可以用。

(提醒一點,存入session的類建議實現(xiàn)序列化接口,比如User什么的)

 

在tomcat的配置文件context.xml中有一個<Manager ... />標(biāo)簽,可以配置session的持久化。

 

 

在JSP頁面,我們可以設(shè)置

<%@ page session="false"%>

這樣設(shè)置呢,不是不讓頁面創(chuàng)建Session,而是在此JSP頁面無法使用session,可以減少網(wǎng)絡(luò)數(shù)據(jù)傳輸。

 

 

另外補充一個“URL重寫技術(shù)”:

   通常session id是保存在瀏覽器的cookie中的,由于cookie可以被人為的禁止,必須有其他機制以便在cookie被禁止時仍然能夠把session id傳遞回服務(wù)器。URL重寫,就是把session id直接附加在URL路徑的后面,附加方式也有兩種,一種是作為URL路徑的附加信息,表現(xiàn)形式為

   http://...../xxx;jsessionid=ByOK3vjFD75aPnrF788764

另一種是作為查詢字符串附加在URL后面,表現(xiàn)形式為

   http://...../xxx?jsessionid=ByOK3vjFD75aPnrF88764

   這兩種方式對于用戶來說是沒有區(qū)別的,只是服務(wù)器在解析的時候處理的方式不同,采用第一種方式也有利于把session id的信息和正常程序參數(shù)區(qū)分開來。

   為了在整個交互過程中始終保持狀態(tài),就必須在每個客戶端可能請求的路徑后面都包含這個sessionid。

 

還有一個問題,是不是關(guān)閉瀏覽器后session就消失了呢?

回答:session是在服務(wù)器端的,你關(guān)不關(guān)瀏覽器對它沒有影響,因為你關(guān)閉瀏覽器時,只是瀏覽器端的session id丟了,但是瀏覽器并會主動通知服務(wù)器說“我已經(jīng)關(guān)閉了,你將session注銷吧”。

 

最后一個問題,session如何過期的呢?

1)主動注銷

服務(wù)器會check session object 是不是valid的,如果是無效的。如果invalid,則先throw IllegalStateException,然后開始后續(xù)處理(從map中移除,通知listener等)

代碼片段如下:

 

  1. /**   
  2.  * Perform the internal processing required to invalidate this session,   
  3.  * without triggering an exception if the session has already expired.   
  4.  *   
  5.  * @param notify Should we notify listeners about the demise of   
  6.  *  this session?   
  7.  */    
  8. public void expire(boolean notify) {     
  9.     
  10.     // Check to see if expire is in progress or has previously been called      
  11.     if (expiring || !isValid)     
  12.         return;     
  13.     
  14.     synchronized (this) {     
  15.         // Check again, now we are inside the sync so this code only runs once      
  16.         // Double check locking - expiring and isValid need to be volatile      
  17.         if (expiring || !isValid)     
  18.             return;     
  19.     
  20.         expiring = true;     
  21.         setValid(false);     
  22.         manager.remove(thistrue);//在管理對象中講這個session object刪除(內(nèi)部也是map實現(xiàn)的)      
  23.     
  24.         // 此處nofity標(biāo)示是否在注銷session的時候發(fā)送Evnet給listener,典型的觀察者pattern      
  25.         if (notify) {     
  26.             fireSessionEvent(Session.SESSION_DESTROYED_EVENT, null);     
  27.         }     
  28.     
  29.         expiring = false;     
  30.     
  31.         // Unbind any objects associated with this session      
  32.         String keys[] = keys();     
  33.         for (int i = 0; i < keys.length; i++)     
  34.             removeAttributeInternal(keys[i], notify);     
  35.     
  36.     }     
  37.     
  38. }   


2)超時注銷

如果瀏覽器端一直有操作(即一直有請求),那么session就不會過期,是什么原理呢?

 

其實,有一個守護(hù)線程去檢查session到期時間,每兩次訪問的時間間隔,如果超過timeout時間,則執(zhí)行銷毀工作。所以,想讓session永不過期,可以在timeout時間內(nèi),一直保持有request。

 

不過session可能會意外丟失,這個就不是我們能控制的了。

 

 

好了,國際化涉及到的難題基本已經(jīng)講解完了,不懂的多看幾遍,理解理解。我也是花了很多時間實踐和分析才得出的,若有不正確之處,還望賜教。

 

附:Struts2國際化的DEMO項目

下載地址:http://dl.vmall.com/c04g39g2q7

(struts的jar包需自己添加,2.3以上的版本均可,需要把xwork-core-2.3.4.1.jar里面的com/opensymphony/xwork2/ActionContext.class刪掉,因為我重寫了這個類)

 

 

    本站是提供個人知識管理的網(wǎng)絡(luò)存儲空間,所有內(nèi)容均由用戶發(fā)布,不代表本站觀點。請注意甄別內(nèi)容中的聯(lián)系方式、誘導(dǎo)購買等信息,謹(jǐn)防詐騙。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請點擊一鍵舉報。
    轉(zhuǎn)藏 分享 獻(xiàn)花(0

    0條評論

    發(fā)表

    請遵守用戶 評論公約

    類似文章 更多