上一篇文章,我大概地介紹一下基於Faceslet的JSF頁面端的構成.接下來,是時候說一下後台邏輯的實現啦.JSF的頁面邏輯是由Managed Bean(姑且譯為託管BEAN)實現.所謂的「Managed Bean」就是指一些由JSF運行時(Runtime)創建與管理的普通Java對象(潮流一點的叫法——POJO).
標準JSF中Managed Bean
標準的JSF中Managed Bean是在faces-config.xml中通過XML定義的.例如:
<managed-bean> <managed-bean-name>helloBean</managed-bean-name> <managed-bean-class> net.blogjava.max.seam.HelloBean </managed-bean-class> <managed-bean-scope>request</managed-bean-scope> </managed-bean> |
這些XML相信不用我怎麼講解大家也知道其作用啦.定義一個BEAN,無論是在Spring、EJB還其它的BEAN容器(Container)中,無非都是這幾個元素:BEAN的名稱(或者標識,ID)、JAVA類型和作用域.JSF的Managed Bean有四個作用域,如下表所示:
作用域 | 描述 |
none | 作用域是none的Managed Bean通常是定義一些公用的BEAN,它們的創建與存儲依賴於引用它的BEAN |
request | 在單一的HTTP請求(Request)中被創建和保持有效 |
session | 在HTTP的會話(Session)中被創建和保持有效,可以跨請 |
application | 存儲WEB應用的Application上下文中,對於所有的請求和會話可見 |
可能大家對「none」作用域比較陌生,舉個例子可能會好理解一點:
<managed-bean> <managed-bean-name>helloBean</managed-bean-name> <managed-bean-class> com.pccw.jsftraining.managedbean.HelloBean </managed-bean-class> <managed-bean-scope>request</managed-bean-scope> <managed-property> <property-name>messageFromOtherBean</property-name> <property-class>java.lang.String</property-class> <value>#{messageBean}</value> </managed-property> </managed-bean> <managed-bean> <managed-bean-name>messageBean</managed-bean-name> <managed-bean-class>java.lang.String</managed-bean-class> <managed-bean-scope>none</managed-bean-scope> <value>Hello World from Another Bean!</value> </managed-bean> |
這個例子定義了一個名為messageBean、作用域是none的Managed Bean,然後它被注入名為messageBean、作用域是request的Managed Bean中.因此,這個messageBean的作用域會跟隨helloBean,同為request.另外值得一提的是,上例同樣展示了如何在一個Managed Bean初始其屬性(Property)的值,如何引用其它的Managed Bean.
Seam中的Web Bean(相當於Managed Bean)
標準JSF的Managed Bean存在不少缺點:
必須通過XML進行配置,過於麻煩;
貧乏的作用域,上文提及標準的JSF的Managed Bean只有四種作用域:none、request、session和application.由於JSF對狀態的依賴比較強,經常需要在請求之間保存應用的狀態,很多時候我們時候都不得不使用Session作用域的Managed Bean.但是眾所周知,過多地使用Session會帶來很多問題,如容易造成內存耗盡,難於集群(Cluster)等.
有監於此,Seam對JSF進行了擴展,並進而起草了Web Bean標準(Web Bean還在BETA階段,坦白的說我也不是很了解).下面我們就來學習一下Seam的Managed Bean(官方文檔中叫Component).在這方面Seam與標準JSF有如下不同:
Seam的Component既可以通過XML配置,又可以通過Annotation的方式配置.我個人比較偏愛Annotation的方式,方便快捷,能夠提高工作效率.XML方式有一個好處就是可以集中管理,但是Managed Bean配置相對比較穩定,不會經常修改,XML優勢並不會太明顯;
更豐富的作用域(Seam中稱為上下文Context),Seam有6種上下文可選:無狀態(Stateless Context)、事件(Event Context,或者Request Context)、頁面(Page Context)、對話(Conversation Context)、會話(Session Context)、業務流(Business Process Context)和應用程序(Application Context);
Seam引入一種雙向注入(Binjection)的方式.所謂的雙向注入就是可以將上下文中的Bean注入到另一個Bean中,又或者將Bean中的屬性(Property)直接發布在上下文中;
Seam的Compoenet可以直接使用EJB 3.0的Bean.
下面我們看一個簡單的Component的定義的例子,
1 package net.blogjava.max.hello.session; 2 3 import org.jboss.seam.ScopeType; 4 import org.jboss.seam.annotations.Name; 5 import org.jboss.seam.annotations.Scope; 6 7 @Name("helloWB") 8 @Scope(ScopeType.PAGE) 9 public class HelloWB { 10 private String name; 11 private String message; 12 13 public String getName() { 14 return name; 15 } 16 17 public void setName(String name) { 18 this.name = name; 19 } 20 21 public String getMessage() { 22 return message; 23 } 24 25 public void setMessage(String message) { 26 this.message = message; 27 } 28 29 public void sayHello() { 30 message = "Hello, " name "!"; 31 } 32 33 public void anotherEvent() { 34 System.out.println("Another request is coming"); 35 } 36 } |
通過上述代碼,大家可以看到有兩句Annotation定義——Name和Scope.除此之外,並沒有什麼特別的地方,正是這兩個Annotation使HelloWB成為一個可以被SEAM的運行時識別的Component.Name用於定義Component的名稱,是必須的;Scope則用於定義Component的作用域,是可選的,默認值為短對話(Short Conversation).在本例中,HelloWB的作用域是Page.Page與Conversation都Seam的傑作,在標準JSF是沒有的.,這兩個作用域是比較常用,我個人比較熱衷於Page作用域,在這裡先談一下Page.
正如我前面所說「JSF對狀態的依賴比較強……」,造成我們對Session的依賴,引起了很多問題.Page很大程度上解決了這個問題,它可以跨請求存活,只要該請求不是「新的」.什麼請求是「新的」請求呢?要回答這個問題,先要搞清楚什麼是「POST-BACK」.學過ASP.NET的朋友可能對POST-BACK概念比較熟悉,沒學過的話不要緊.POST-BACK並不是什麼深奧的東西,所謂的POST-BACK,就是指用戶按下頁面上的某個按鈕或表單控制項,將表單數據發送回到頁面自身的URL.相反,如果用戶是通過在地址欄中輸入URL,或通過點擊頁面的鏈接訪問頁面,則這個請求就是一個NON-POST-BACK的請求,也即是一個新的請求.
另外,一些JSF專家都推薦一種叫Backing Bean的風格.所謂的Backing Bean就是指一個JSF頁面對應一個Managed Bean處理頁面邏輯.Page作用域非常適用這種情況,它是與頁面一起序列化(Serialize)到瀏覽器或保存在Session中.如果大家還是不太明白的話,請看以下的XHTML代碼.
1 <?xml version="1.0" encoding="utf-8"?> 2 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> 3 4 <html xmlns="http://www.w3.org/1999/xhtml" xmlns:h="http://java.sun.com/jsf/html"> 5 <head> 6 <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/> 7 <title> 8 Hello World 9 </title> 10 </head> 11 <body> 12 <h:form> 13 <h:outputLabel value="Name: " for="itName" /> 14 <h:inputText id="itName" value="#{helloWB.name}" /> 15 <h:commandButton action="#{helloWB.sayHello}" value="Say Hello" /> 16 <h:commandButton action="#{helloWB.anotherEvent}" value="Another Reqeust" /> 17 <hr /> 18 <h2> 19 <h:outputText value="#{helloWB.message}" /> 20 </h2> 21 </h:form> 22 </body> 23 </html> |
發布運行上述代碼,大家可以看如下頁面.
在輸入框中鍵入「max」,點擊「Say Hello」按鈕,將請求POST-BACK到Seam的組件中,由於按鈕註冊了組件的監聽方法,HelloWB的sayHello方法會被調用.它將通過值綁定所得的name與「Hello」和「!」串起來,賦給message屬性.因此,響應頁面會如下圖所示:
這時候,大家可以再點擊「Another Request」按鈕,出現的結果與上圖一樣.這就說明雖然發生了第二次請求,name與message的同樣保持上一次的值.然後,大家可以再試下複製頁面地址,粘貼到新窗口或新選項卡(TAB)的地址欄中,按下ENTER.你會發現頁面的被重置回到最初的狀態.這個例子很好地演示了POST-BACK請求與NON-POST-BACK請求的區別.
小結
本文粗略地介紹了一下JSF的頁面邏輯處理組件——Managed Bean,還有很多關於Seam的組件知識如Bijection和Conversation等還沒有介紹,只有留待以後文章了.
[火星人 ] Seam的頁面邏輯實現已經有825次圍觀