文章出處

上一節中,我們已經知道了如何用@Inject實現基本注入,這一節研究Bean實例注入后的“生命周期”,web application中有幾種基本的生命周期(不管哪種編程語言都類似)

1、Application 生命周期

即:web application啟動后,處于該生命周期級別的對象/變量,將一直存在,可以被所有web應用的用戶共同訪問,通常用來做網站計數器,實現流量訪問之類。直到web 應用停止或重新啟動,該對象才被銷毀。簡單來說:只要web application處于激活狀態,不論你換什么瀏覽器,不論你是否關閉頁面,這個對象都會一直存在。

2、Session 生命周期

每次我們在某種類型的瀏覽器(比如:IE或Firefox)里,請求web application的某個頁面時,就會生成Session,只要瀏覽器不關閉,Session就能持續有效(哪怕你把當前Tab標簽頁面給關掉,或者在當前url地址欄,輸入一個其它不相關的網址,跳到其它網站,然后再回過來訪問web app,只要Session不超時,Session仍有效)。說得更白一點:按F5刷新,該對象/變量不會被自動銷毀,除非Session過期。

注:Session是跟瀏覽器有關的,如果在FireFox里打開web Application的某個url,再到IE里打開同樣的url,這二個瀏覽器里的Session是不同的。

3、Request 生命周期

即:只有本次http請求才有效,通俗點講,如果你定義一個變量的生命周期是Request級別,刷新一次頁面后,該變量就被初始化(重新投胎)了。

為了演示上面的幾種情況,我們創建一個新的Dynamic Website,仍然用Maven來管理,項目結構如下:

model包下,創建了幾個類,先來看基類BaseItem

 1 package model;
 2 
 3 import java.io.Serializable;
 4 
 5 public class BaseItem implements Serializable {
 6 
 7     private static final long serialVersionUID = -8431052435964580554L;
 8 
 9     private long counter;
10 
11     public void addCounter() {
12         counter++;
13 
14     }
15 
16     public long getCounter() {
17         return counter;
18     }
19 
20     public long getHashCode() {
21         return hashCode();
22     }
23 
24 }
BaseItem

注:SessionScoped使用時,要求Bean實現序列化接口,否則運行會報錯,建議要注入的Bean,全都實現Serializable接口。

其它幾個類都繼承自這個類,只是在類上應用了不同的注解

ApplicationBean

 1 package model;
 2 
 3 import javax.enterprise.context.*;
 4 
 5 @ApplicationScoped
 6 public class ApplicationBean extends BaseItem {
 7 
 8     /**
 9      * 
10      */
11     private static final long serialVersionUID = -2434044044797389734L;
12 
13 
14 
15 }
ApplicationScoped

SessionBean

 1 package model;
 2 
 3 import javax.enterprise.context.*;
 4 
 5 @SessionScoped
 6 public class SessionBean extends BaseItem {
 7 
 8     /**
 9      * 
10      */
11     private static final long serialVersionUID = -4285276657265507539L;
12 
13 
14 
15 }
SessionBean

RequestBean

 1 package model;
 2 
 3 import javax.enterprise.context.*;
 4 
 5 @RequestScoped
 6 public class RequestBean extends BaseItem {
 7 
 8     /**
 9      * 
10      */
11     private static final long serialVersionUID = -2625124180601128375L;
12 
13 
14 
15 }
RequestBean

 SingletonBean

 1 package model;
 2 
 3 import javax.inject.Singleton;
 4 
 5 @Singleton
 6 public class SingletonBean extends BaseItem {
 7 
 8     /**
 9      * 
10      */
11     private static final long serialVersionUID = 283705326745708570L;
12 
13 
14 
15 }
SingletonBean

注:@Singleton算是一個“偽”Scope,顧名思義,它將以單例模式注入一個唯一的對象實例。從使用效果上看,這跟@ApplicationScoped類似.

同樣,為了跟前端JSF交互,我們再來一個“Controller”

 1 package controller;
 2 
 3 import javax.inject.*;
 4 import model.*;
 5 
 6 @Named("Index")
 7 public class IndexController {
 8 
 9     @Inject
10     private ApplicationBean applicationBean;
11 
12     @Inject
13     private SessionBean sessionBean;
14 
15     @Inject
16     private RequestBean requestBean;
17 
18     @Inject
19     private SingletonBean singletonBean;
20 
21     public ApplicationBean getApplicationBean() {
22         applicationBean.addCounter();
23         return applicationBean;
24     }
25     
26 //    public long getApplicationBeanCount(){
27 //        applicationBean.addCounter();
28 //        return applicationBean.getCounter();        
29 //    }
30 
31     public SessionBean getSessionBean() {
32         sessionBean.addCounter();
33         return sessionBean;
34     }
35 
36     public RequestBean getRequestBean() {
37         requestBean.addCounter();
38         return requestBean;
39     }
40 
41     public SingletonBean getSingletonBean() {
42         singletonBean.addCounter();
43         return singletonBean;
44     }
45 
46 }
IndexController

我們注入了上述四種生命周期的Bean,并提供了getter方法,最后準備一個簡單的xhtml頁面,作為UI展現

 1 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
 2 <html xmlns="http://www.w3.org/1999/xhtml"
 3     xmlns:h="http://java.sun.com/jsf/html"
 4     xmlns:f="http://java.sun.com/jsf/core"
 5     xmlns:ui="http://java.sun.com/jsf/facelets">
 6 
 7 <h:head>
 8     <title>cdi scope sample</title>
 9 </h:head>
10 <body>
11     Application Bean:#{Index.applicationBean.counter} ,
12     hashCode:#{Index.applicationBean.hashCode}
13     <br /> Session Bean:#{Index.sessionBean.counter} ,
14     hashCode:#{Index.sessionBean.hashCode}
15     <br /> Request Bean:#{Index.requestBean.counter} ,
16     hashCode:#{Index.requestBean.hashCode}
17     <br /> Singleton Bean:#{Index.singletonBean.counter} ,
18     hashCode:#{Index.singletonBean.hashCode}
19     <br />
20 </body>
21 </html>
index.xhtml

在Jboss里部署后,可以用http://localhost:8080/cdi-scope-sample/index.jsf 訪問,下面是運行截圖:

大家可以F5刷新下看看變化,然后多開幾個Tab頁,訪問同樣的網址,F5刷新,然后把瀏覽器關掉,再重新打開瀏覽器,訪問同樣的網址再比較一下

4、Conversation 生命周期

這個實在不知道中文如何翻譯,Conversation的字面含義是“會話、會談”,但在計算機領域里,一般已經公認將“Session”翻譯成“會話”,所以Conversion這個詞就不便再翻譯成“會話”了,還是直接Conversation這個詞吧。

我們在web開發中,經常會用到ajax,page1上的ajax向另一個頁面page2發起請求時,會建立client到server的短時連接,如果想在ajax請求期間,讓多個page之間共同訪問一些變量(或對象),請求結束時這些對象又自動銷毀(注:顯然SessionScoped、ApplicationScoped、RequestScoped都不太適合這種需求),這時可以考慮使用ConversionScoped.

要使用ConversionScoped,必須在Controller(即ManagedBean)上,顯式Inject一個Conversation類實例,而且要顯式begin/end 該Conversion,每次生成Conversation實例時,系統會分配一個id給當前Conversation,多個頁面之間根據唯一的cid來匹配使用哪個Conversation范圍內的Bean對象,如果指定的id不對(比如:根據該cid找不到對應的conversation),系統會報錯.

好了,開始碼磚:

4.1 先在model包下,新建一個ConversationBean

 1 package model;
 2 
 3 import javax.enterprise.context.*;
 4 
 5 @ConversationScoped
 6 public class ConversationBean extends BaseItem {
 7 
 8     /**
 9      * 
10      */
11     private static final long serialVersionUID = -4212732314401890050L;
12 
13 }
ConversationBean

4.2 在controller包下,新建一個ConversationController

 1 package controller;
 2 
 3 import javax.enterprise.context.Conversation;
 4 import javax.faces.context.FacesContext;
 5 import javax.inject.*;
 6 
 7 import model.ConversationBean;
 8 
 9 @Named("Conversation")
10 public class ConversationController {
11 
12     @Inject
13     private ConversationBean conversationBean;
14 
15     @Inject
16     private Conversation conversation;
17 
18     public ConversationBean getConversationBean() {
19         return conversationBean;
20     }
21 
22     public Conversation getConversation() {
23         return conversation;
24     }
25 
26     /**
27      * 開始conversation
28      */
29     public void beginConversation() {
30         //僅當前頁面未被post提交,且conversation是"瞬時"性時,才開始conversation
31         if (!FacesContext.getCurrentInstance().isPostback()
32                 && conversation.isTransient()) {
33             conversation.begin();
34         }
35     }
36     
37     
38     public String endConversation(){
39         //如果Conversation不是“瞬時”的,則結束conversion,同時所有ConversationScoped的對象也會銷毀
40         if (!conversation.isTransient()) {
41             conversation.end();
42         }
43         //然后返回第1頁
44         return "page1?faces-redirect=true";
45     }
46 
47     /**
48      * 供Ajax調用的方法
49      */
50     public void addCounter() {
51         conversationBean.addCounter();
52 
53     }
54 
55     /**
56      * 轉到第2頁
57      */
58     public String gotoPage2() {
59         // 注:faces-redirect=true會自動把conversion的id通過url parameter傳遞到page2
60         return "page2?faces-redirect=true";
61     }
62 
63 }
ConversationController

4.3 webapp里創建幾個頁面
page1.xhtml

 1 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
 2 <html xmlns="http://www.w3.org/1999/xhtml"
 3     xmlns:h="http://java.sun.com/jsf/html"
 4     xmlns:f="http://java.sun.com/jsf/core"
 5     xmlns:ui="http://java.sun.com/jsf/facelets">
 6 
 7 <h:head>
 8     <title>ConversionScoped Page1</title>
 9     <f:event listener="#{Conversation.beginConversation}"
10         type="preRenderView"></f:event>
11 </h:head>
12 <body>
13     <h1>ConversionScoped Page1</h1>
14     <h:form>
15     Conversation Bean:#{Conversation.conversationBean.counter},Conversion Id:#{Conversation.conversation.id}<br />
16         <br />
17         <h:commandLink value="AddCounter">
18             <f:ajax listener="#{Conversation.addCounter}" render="@form" />
19         </h:commandLink>
20         &nbsp;
21         <h:commandLink value="Go to Page2" action="page2"></h:commandLink>
22     </h:form>
23 </body>
24 </html>
page1.xhtml

注:

a.<f:event listener="#{Conversation.beginConversation}"  type="preRenderView"></f:event> 通過這句代碼,該頁面加載時,會先調用ConversationController中的beginConversation方法,啟動conversation

b.通過AddCounter這個按鈕發起ajax請求,調用ConversationController中的addCounter()方法,點擊之后,conversationBean實例的計數器將+1

page2.xhtml

 1 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
 2 <html xmlns="http://www.w3.org/1999/xhtml"
 3     xmlns:h="http://java.sun.com/jsf/html"
 4     xmlns:f="http://java.sun.com/jsf/core"
 5     xmlns:ui="http://java.sun.com/jsf/facelets">
 6 
 7 <h:head>
 8     <title>ConversionScoped Page2</title>
 9 </h:head>
10 <body>
11     <h1>ConversionScoped Page2</h1>
12     Conversation Bean:#{Conversation.conversationBean.counter},Conversion
13     Id:#{Conversation.conversation.id}
14     <br />
15     <br />
16     <h:link value="Back to Page1" outcome="page1">
17         <f:param name="cid" value="#{Conversation.conversation.id}" />
18     </h:link>
19     &nbsp;
20     <h:link value="Go to Page3" outcome="page3">
21         <f:param name="cid" value="#{Conversation.conversation.id}" />
22     </h:link>
23 </body>
24 </html>
page2.xhtml

在page1上將計數器加1后,點擊 Go to Page2,將跳到page2,同時會把cid自動傳過來(通過ConversationController中的return "page2?faces-redirect=true";),然后在page2上顯示已經改變的計數器值。
page3.xhtml

 1 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
 2 <html xmlns="http://www.w3.org/1999/xhtml"
 3     xmlns:h="http://java.sun.com/jsf/html"
 4     xmlns:f="http://java.sun.com/jsf/core"
 5     xmlns:ui="http://java.sun.com/jsf/facelets">
 6 <h:head>
 7     <title>ConversionScoped Page3</title>
 8 </h:head>
 9 <body>
10     <h1>ConversionScoped Page3</h1>
11     Conversation Bean:#{Conversation.conversationBean.counter},Conversion
12     Id:#{Conversation.conversation.id}
13     <br />
14     <br />
15     <h:form>
16         <a href="page2.jsf?cid=#{Conversation.conversation.id}">Back to
17             Page2</a>&nbsp;
18         <h:commandLink value="EndConversation"
19             action="#{Conversation.endConversation}"></h:commandLink>
20     </h:form>
21 </body>
22 </html>
page3.xhtml

這個頁面,仍然繼續Conversation生命周期內的conversationBean計數器值,然后通過EndConversation這個鏈接,點擊后,調用ConversationController中的endConversation方法,結束Conversation,同時所有該ConversationScoped范圍內的Bean將被銷毀,最后再返回到page1

運行截圖:

上圖是page1剛打開的情形,注意這時Conversaion Id是1(系統自動分配的),然后把AddCounter隨便點擊幾下

可以看到計數器變成了3,然后點擊 Go to Page2,跳到第2頁,如下圖:

注意地址欄里,自動帶上了?cid=1,這個很重要,沒有這個id,在page2上,就無法自動找到剛才的conversation,你可以嘗試把cid的值在地址欄里改下,然后觀察下報錯的信息

這是page3,點擊EndConversation結束本次Conversation,將跳回到page1

注意:因為剛才的conversation已經end掉了,所以再次進入page1時,系統又重新注入了一個全新的Conversation實例,此時的cid為2

另外,剛接觸jsf的朋友,可以留意下page1到page3上的Go to PageX的link處理,我刻意用了多種處理方式,比如: <h:commandLink>、<h:link>、以及最常規的<a>鏈接,以體現同一問題的處理,我們可以有多種選擇。

5、生命周期的“混用”問題

如果一個Bean在設計時,被標識為@ApplicationScoped,而注入使用它的Controller類,本身又是其它類型的生命周期,比如@RequestScoped,結果會怎樣?

比如有一個Bean,代碼如下:

 1 package model;
 2 
 3 import java.io.Serializable;
 4 
 5 import javax.enterprise.context.*;
 6 
 7 @ApplicationScoped
 8 public class MyBean implements Serializable {
 9 
10     private static final long serialVersionUID = -4190635663858456460L;
11     private long counter = 0;
12 
13     public void addCounter() {
14         counter++;
15 
16     }
17 
18     public long getCounter() {
19         return counter;
20     }
21 
22     public long getHashCode() {
23         return hashCode();
24     }
25 
26 }
MyBean

 使用它的控制器ScopeController代碼如下:

 1 package controller;
 2 
 3 import java.io.Serializable;
 4 
 5 import javax.enterprise.context.*;
 6 
 7 import javax.inject.Inject;
 8 import javax.inject.Named;
 9 
10 import model.MyBean;
11 
12 @Named("Scope")
13 @RequestScoped
14 public class ScopeController implements Serializable {
15 
16     private static final long serialVersionUID = 2717400174254702790L;
17     @Inject
18     private MyBean myBean;
19 
20     public MyBean getMyBean() {
21         myBean.addCounter();
22         return myBean;
23     }
24 
25 }
ScopeController

 再來一個頁面scope.xhtml驗證一下:

 1 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
 2 <html xmlns="http://www.w3.org/1999/xhtml"
 3     xmlns:h="http://java.sun.com/jsf/html"
 4     xmlns:f="http://java.sun.com/jsf/core"
 5     xmlns:ui="http://java.sun.com/jsf/facelets">
 6 
 7 <h:head>
 8     <title>scope test</title>
 9 </h:head>
10 <body>
11     
12     My Bean's counter:#{Scope.myBean.counter} ,
13     My Bean's hashCode:#{Scope.myBean.hashCode}
14     <br />
15 </body>
16 </html>
scope.xhtml

最后運行的結果,myBean實例仍然是一個ApplicationScoped的對象。可以這么理解:本質上講,Controller也只是一個Bean而已,上面的代碼雖然把ScopeController這個Bean標注為RequestScoped,但因為myBean是ApplicationScoped的,本次頁面訪問結束后,ScopeController實例已經被釋放了(因為它是RequestScoped生命周期,本次請求一結束,它就銷毀),但MyBean實例還將繼續存在(因為它是ApplicationScoped,整個webapp生命周期內都將繼續存在).

但有時候,這可能不符合我們的期望,在Controller上加@RequestScoped標識的本意是希望每次請求都能產生一個新的對象(包括Controller里使用的其它資源),修改MyBean.java嗎?這顯然不好,如果MyBean被很多其它類使用了,修改MyBean會影響所有調用它的代碼,一個簡單的解決辦法是使用@New注釋,比如下面這樣:

1     @Inject
2     @New
3     private MyBean myBean;
@New

這樣就告訴系統,每次需要注入得到MyBean實例時,都強制生成一個全新的實例。

繼續折騰:如果把MyBean.java上的@ApplicationScoped去掉,然后在Controller里@Inject的地方,加上@ApplicationScoped(即:把@ApplicationScoped從MyBean.java上移到ScopeController.java里),類似下面這樣:

import java.io.Serializable;

public class MyBean implements Serializable {
1 @Named("Scope")
2 @RequestScoped
3 public class ScopeController implements Serializable {
4 
5     private static final long serialVersionUID = 2717400174254702790L;
6     @Inject
7     @ApplicationScoped
8     private MyBean myBean;
View Code

也許,你會覺得應該跟剛才運行的結果相同,但是實際測試下來,myBean對象,仍然跟最外面的ScopeController一樣,是Request生命周期,所以如果你確實希望某個Bean在設計時,就決定它的生命周期,@XXXScoped建議直接使用在Bean類本身,而非@Inject的地方。

附:示例源碼下載 cdi-scope-sample.zip


文章列表




Avast logo

Avast 防毒軟體已檢查此封電子郵件的病毒。
www.avast.com


arrow
arrow
    全站熱搜
    創作者介紹
    創作者 大師兄 的頭像
    大師兄

    IT工程師數位筆記本

    大師兄 發表在 痞客邦 留言(0) 人氣()