摘要:本文主要介紹了如何利用Frails框架進(jìn)行快速開(kāi)發(fā)。在本文中我們的目標(biāo)是架構(gòu)起一個(gè)Spring+JSF+Hibernate的項(xiàng)目,并且實(shí)現(xiàn)后臺(tái)代碼。您會(huì)發(fā)現(xiàn),原本僅僅實(shí)現(xiàn)一個(gè)Spring框架就要話很長(zhǎng)時(shí)間的“痛苦”過(guò)程,在Frails框架和模板的幫助下,幾乎喝杯咖啡的功夫就可以搞定了。您甚至?xí)l(fā)現(xiàn),不需要寫(xiě)任何SQL或者數(shù)據(jù)庫(kù)相關(guān)代碼就能夠?qū)崿F(xiàn)幾乎可以覆蓋全部的數(shù)據(jù)庫(kù)操作需求。通過(guò)本文,您一定會(huì)喜歡上在Frails框架幫助下快速開(kāi)發(fā)的感覺(jué)。
Frails簡(jiǎn)介
Frails是SourceForge.net上的一個(gè)開(kāi)源項(xiàng)目,其項(xiàng)目管理開(kāi)發(fā)是由華中科技大學(xué)IBM俱樂(lè)部JAVA組與軟件學(xué)院學(xué)生合作完成。Frails框架誕生的目的就是幫助開(kāi)發(fā)者快速開(kāi)發(fā)J2EE項(xiàng)目。更多基本信息您可以查看http://www.sourceforge.net/projects/frails(英文),http://frails.hexiao.cn/(中文)。
我們現(xiàn)在假設(shè)您對(duì)現(xiàn)在主流的J2EE框架Spring,JSF,Hibernate有所了解。那么您會(huì)發(fā)現(xiàn)這些框架的配置與實(shí)現(xiàn)是相當(dāng)復(fù)雜的,然而相當(dāng)多的情況下我們不需要用到其中高靈活性的配置。就拿JSF來(lái)說(shuō),JSF的頁(yè)面導(dǎo)航配置實(shí)際上是一種沒(méi)有必要的靈活,實(shí)際上我們更喜歡的將A頁(yè)面導(dǎo)航到B頁(yè)面的規(guī)則簡(jiǎn)單的約定,而不是每個(gè)都要手動(dòng)配置;驗(yàn)證過(guò)程也是很麻煩的,相對(duì)于將驗(yàn)證代碼零星的嵌入到頁(yè)面邏輯中,我們更希望看到的是有統(tǒng)一的驗(yàn)證中心,這樣更符合軟件工程的原則;對(duì)于數(shù)據(jù)庫(kù)操作來(lái)說(shuō),通常我們的操作都是增刪查改四種操作,每個(gè)操作都會(huì)有很多重復(fù)的“垃圾”代碼,這也阻礙了快速開(kāi)發(fā)的目標(biāo);Spring更方便的注射,也能夠提高開(kāi)發(fā)速度。上述問(wèn)題在Frails中有良好的解決,并且保持了與原始框架一樣的高靈活可配置性,甚至提高了靈活性,或者是提供了一種更優(yōu)秀的解決方案。這里要強(qiáng)調(diào)的是,F(xiàn)rails不是其他框架的一個(gè)簡(jiǎn)化版本,它只是通過(guò)開(kāi)發(fā)者和框架之間的一種約定或者是“默契”,代辦了其他框架累贅、重復(fù)的工作。如果你想要高靈活的配置,F(xiàn)rails同樣適合。
動(dòng)手實(shí)踐
下面我們將展示一個(gè)建立項(xiàng)目的過(guò)程。這里的項(xiàng)目需求是實(shí)現(xiàn)一個(gè)簡(jiǎn)單的留言簿,目的就是通過(guò)最簡(jiǎn)單的例子最大化的覆蓋到Frails的特性。
搭建項(xiàng)目
首先要有個(gè)IDE比較好,我使用的是Eclipse與WTP插件,這樣開(kāi)發(fā)起來(lái)會(huì)很方便,當(dāng)然你要是不想用IDE或者用別的也一樣可以,F(xiàn)rails是和IDE無(wú)關(guān)的。
最方便的是Frails為一般的項(xiàng)目提供了一個(gè)模板FrailsTemplate(這個(gè)模板在2.0包里的Samples下可以找到)。這個(gè)模板已經(jīng)包含了Spring+Hibernate+JSF的全部配置。我們僅僅需要對(duì)這個(gè)模板進(jìn)行做些適合自己的修改就可以快速的搭建起一個(gè)項(xiàng)目來(lái)了。
我們新建一個(gè)WEB項(xiàng)目,將FrailsTemplate的WEB-INF拷貝到自己的項(xiàng)目中來(lái),并且在scr中建立四個(gè)包,分別為actions,domain,dao,domain.entities。這樣基本上一個(gè)項(xiàng)目的結(jié)構(gòu)就我們接下來(lái)介紹這些行為的意義:
WEB-INF/lib目錄下是整個(gè)項(xiàng)目需要的所有JAR包。其中Frails需要的JAR就是frails4jsf1.1.jar。如果你沒(méi)有別的什么特殊需求,例如要使用Icefaces的JSF組件,那么這個(gè)lib的JAR文件足夠你開(kāi)發(fā)了。你看看里面還包括了tomahawk-1.1.3.jar呢。但是里面沒(méi)有包含facelet,如果你想用facelet的話直接添加進(jìn)來(lái)就可以了。如果你僅僅想急著些個(gè)JSF頁(yè)面完成老板的任務(wù),那么你這些東西都不用管了,直接考過(guò)來(lái)就是了。
WEB-INF下的幾個(gè)配置文件可能需要改動(dòng):
Jdbc.properties
Jdbc.properties里面包含的是你的數(shù)據(jù)庫(kù)配置。實(shí)際上applicationContext.xml里數(shù)據(jù)庫(kù)有關(guān)的配置都關(guān)聯(lián)到此。
jdbc.driverClassName=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost/test?useUnicode=true&characterEncoding=GBK
jdbc.username=root
jdbc.password=password
jdbc.driverClassName對(duì)應(yīng)的是數(shù)據(jù)庫(kù)驅(qū)動(dòng)。正對(duì)不同的數(shù)據(jù)庫(kù)填寫(xiě)不同的驅(qū)動(dòng)就可以了。這里的是MySQL。
Jdbc.url是數(shù)據(jù)庫(kù)鏈接地址,一般不需要更改。但是你要用別的數(shù)據(jù)庫(kù)還是得改成相應(yīng)的數(shù)據(jù)庫(kù)URL。最后的characterEncoding是數(shù)據(jù)庫(kù)編碼,這里選用的是GBK,你也可以根據(jù)你的需要設(shè)置為UTF-8或者別的。
Jdbc.username是數(shù)據(jù)庫(kù)用戶名,需要根據(jù)你自己的設(shè)置更改。
Jdbc.password是數(shù)據(jù)庫(kù)密碼,需要根據(jù)你的設(shè)置更改。
applicationContext.xml
基本的應(yīng)用上下文配置,一般不需要修改。
faces-config.xml
由于Frails框架的原因,原本復(fù)雜的faces-config.xml你現(xiàn)在可以一個(gè)字不用寫(xiě)。
frails-config.xml
Frails框架配置。簡(jiǎn)單的來(lái)說(shuō)你可以不做任何修改。Frails將根據(jù)這里的配置(如果沒(méi)有的話就是默認(rèn)配置),識(shí)別項(xiàng)目中的ManagedBeans,Validators,Converters,頁(yè)面,資源目錄等。具體說(shuō)明可以在Frails項(xiàng)目包中的Frails User Guide中的Configrations找到相應(yīng)的說(shuō)明。這里簡(jiǎn)單的認(rèn)為什么都不寫(xiě)就好了,我們等下按照一個(gè)默認(rèn)配置的規(guī)范來(lái)繼續(xù)我們的項(xiàng)目開(kāi)發(fā)就OK了。
hibernate.cfg.xml
Hibernate配置。這個(gè)配置是必須修改的。至少我們?cè)诿繉?xiě)一個(gè)Hibernate實(shí)體的時(shí)候,就應(yīng)該在SessionFactory里添加一條<mapping class="domain.entities.ClassName"/> class里面對(duì)應(yīng)了一個(gè)實(shí)體的類(lèi)名。我們等下也會(huì)提到。
如果你并不是使用MySQL數(shù)據(jù)庫(kù)的話,需要把數(shù)據(jù)庫(kù)的別名修改成你數(shù)據(jù)庫(kù)的相應(yīng)設(shè)置:<propertyname="dialect">org.hibernate.dialect.MySQLDialect</property>。
<property name="hibernate.hbm2ddl.auto">update</property>是說(shuō)每次啟動(dòng)Hibernate都會(huì)自動(dòng)檢查,如果原來(lái)數(shù)據(jù)庫(kù)中沒(méi)有表則建立新的表,如果有了則更新原有表。
spring-beans.xml
Spring框架配置。這個(gè)配置是必須修改的。這里主要是配置所有的dao,Spring按照這個(gè)配置進(jìn)行依賴注入。不要擔(dān)心,我們的配置也之是寫(xiě)點(diǎn)點(diǎn)東西,等下詳細(xì)說(shuō)明。
現(xiàn)在項(xiàng)目的基本配置就完成了,雖然還有些要根據(jù)實(shí)際的設(shè)計(jì)實(shí)現(xiàn)增加些配置,但是大體的工作已經(jīng)完成了。您看,根本就沒(méi)有花很多時(shí)間。下面讓我們快速進(jìn)入到實(shí)際的開(kāi)發(fā)實(shí)現(xiàn)過(guò)程中來(lái)。
實(shí)現(xiàn)一個(gè)項(xiàng)目
現(xiàn)在該輪到解釋剛才建立的四個(gè)包了(當(dāng)然了,你也完全可以不這么做,包名只是對(duì)類(lèi)的一種功能上的分類(lèi),完全可以根據(jù)自己的需要更改為其他的名字):actions包里面包含的是所有的ManagedBeans;dao里面包含所有的數(shù)據(jù)庫(kù)相關(guān)業(yè)務(wù)接口;domain包里是數(shù)據(jù)庫(kù)業(yè)務(wù)實(shí)現(xiàn);domain.entities包里是所有的實(shí)體類(lèi)。下面結(jié)合我們的例子來(lái)介紹。
例子:我們要實(shí)現(xiàn)一個(gè)留言簿,notebook.jsp頁(yè)面顯示出留言列表,并在下方顯示一個(gè)用戶名和內(nèi)容輸入框,點(diǎn)擊提交后將留言儲(chǔ)存到數(shù)據(jù)庫(kù)里。
后臺(tái)(業(yè)務(wù)邏輯)代碼設(shè)計(jì)與實(shí)現(xiàn)
1 建立實(shí)體
我們?cè)O(shè)計(jì)是每條留言記錄對(duì)應(yīng)一個(gè)實(shí)體:在包domain.entitis下建立Note.java,并且按照Hibernate的Annotation規(guī)范編寫(xiě),使得Note.java這個(gè)類(lèi)會(huì)按照Hibernate的方式映射到數(shù)據(jù)庫(kù)中。(關(guān)于Hibernate相關(guān)內(nèi)容請(qǐng)查看Hibernate相關(guān)文檔,這里不再贅述),代碼如下:
package
?domain.entities;

import
?javax.persistence.Entity;
import
?javax.persistence.GeneratedValue;
import
?javax.persistence.GenerationType;
import
?javax.persistence.Id;

@Entity

public
?
class
?Note?
{
????

????
/**?*/
/**
?????*?要數(shù)據(jù)庫(kù)為每個(gè)留言建立一個(gè)ID
?????
*/
????@Id
????@GeneratedValue(strategy
=
GenerationType.AUTO)
????
long
?id;
????

????
/**?*/
/**
?????*?留言者姓名
?????
*/
????String?poster;
????

????
/**?*/
/**
?????*?留言內(nèi)容
?????
*/
????String?content;


????
public
?String?getContent()?
{
????????
return
?content;
????}
????
public
?
void
?setContent(String?content)?
{
????????
this
.content?
=
?content;
????}
????
public
?
long
?getId()?
{
????????
return
?id;
????}
????
public
?
void
?setId(
long
?id)?
{
????????
this
.id?
=
?id;
????}
????
public
?String?getPoster()?
{
????????
return
?poster;
????}
????
public
?
void
?setPoster(String?poster)?
{
????????
this
.poster?
=
?poster;
????}
}
同時(shí),不要忘了,你應(yīng)該在hibernate.cfg.xml的<session-factory>里加上對(duì)本實(shí)體映射聲明<mapping class="domain.entities.Note"/>。
2實(shí)體的數(shù)據(jù)庫(kù)操作接口在dao包內(nèi)設(shè)計(jì)出所有實(shí)體對(duì)飲的數(shù)據(jù)庫(kù)業(yè)務(wù)接口,最佳實(shí)踐是,每個(gè)實(shí)體對(duì)應(yīng)一個(gè)接口,并且使用ClassnameDao的形式命名。所以我們這里的為NoteDao。所則上我們應(yīng)該在本接口中聲明所有的和Note類(lèi)相關(guān)的數(shù)據(jù)庫(kù)方法,但是就像前面提到過(guò)了的,不同實(shí)體的數(shù)據(jù)庫(kù)業(yè)務(wù)基本上都是增、刪、查、該,無(wú)非是在操作的對(duì)象上有不同而已。所以在實(shí)現(xiàn)的階段,我們要實(shí)現(xiàn)每個(gè)接口,將是非常累贅的事情——反復(fù)的寫(xiě)例如開(kāi)關(guān)數(shù)據(jù)庫(kù)的“垃圾”代碼。好在我們有了Java 5的泛型,以及Frails的GenericDao<T>,我們可以省了好多事情。GenericDao基本上已經(jīng)幫我們聲明了所有可能要用到的數(shù)據(jù)庫(kù)方法,一般情況下我們僅僅之需要讓實(shí)體的Dao繼承自此就可以了,不用寫(xiě)任何代碼。記得繼承的時(shí)候要聲明泛型的類(lèi)型。所以NoteDao接口代碼如下:
package?dao;

import?net.sf.frails.hibernate.GenericDao;
import?domain.entities.Note;


public?interface?NoteDao?extends?GenericDao<Note>?
{

} 3 實(shí)現(xiàn)實(shí)體數(shù)據(jù)庫(kù)操作接口下面是實(shí)現(xiàn)每個(gè)dao了。你可能會(huì)說(shuō),盡管本例子中只有一個(gè)dao,但實(shí)際的項(xiàng)目中實(shí)體的數(shù)目將會(huì)很多,并且每個(gè)實(shí)體內(nèi)都有不少的方法要實(shí)現(xiàn),這樣一個(gè)個(gè)的寫(xiě)代碼將是多大的負(fù)擔(dān)啊!幸運(yùn)的是,再一次Frails解決了這個(gè)問(wèn)題,同樣,我們讓所有的實(shí)現(xiàn)類(lèi)繼承自一個(gè)支持泛型的GenericDaoSupport<T>。
GenericDaoSupport實(shí)現(xiàn)了GenericDao<T>接口,已經(jīng)實(shí)現(xiàn)了幾乎所有可能要用到的數(shù)據(jù)庫(kù)方法。NoteDaoImp代碼如下,我們還是什么都沒(méi)有寫(xiě):
package?dao;

import?java.util.List;
import?net.sf.frails.hibernate.GenericDaoSupport;
import?domain.entities.Note;


public?class?NoteDaoImp?extends?GenericDaoSupport<Note>?implements?NoteDao
{
}這個(gè)時(shí)候我們就要修改spring-beans.xml里的配置了。在<Beans>里為每個(gè)daoImp加入一個(gè)聲明<bean id="noteDao" parent="dao" class="dao.NoteDaoImpl"></bean>
這個(gè)地方parent屬性指的是在applicationContext.xml里幫助Dao對(duì)象得到hibernate的Session的抽象Bean 的名字。
并且在<bean id="servicesImp" class="domain.ServicesImp">內(nèi)為每個(gè)dao加入屬性:<property name="noteDao" ref="noteDao"></property>。這里ServicesImp就是下面要提到的業(yè)務(wù)邏輯實(shí)現(xiàn)。所以整個(gè)的<Beans>的結(jié)構(gòu)如下:
<beans>????
????<bean?id="dao"?abstract="true">????
????????<property?name="sessionFactory"?ref="sessionFactory"/>
????</bean>
????<bean?id="noteDao"?parent="dao"?class="dao.NoteDaoImp">
????</bean>
????<bean?id="services"?parent="transactionProxy">
????????<property?name="target"?ref="servicesImp"/>
????????<property?name="transactionAttributes">
????????????<props>????????????????
????????????????<prop?key="*">PROPAGATION_REQUIRED</prop>
????????????</props>
????????</property>
????</bean>
????<bean?id="servicesImp"?class="domain.ServicesImp">
????????<property?name="noteDao"?ref="noteDao"></property>
????</bean>
</beans> 4 提供統(tǒng)一的業(yè)務(wù)邏輯接口接下來(lái)我們要將所有的業(yè)務(wù)邏輯包裝起來(lái)。在一個(gè)接口中聲明所有對(duì)外提供的業(yè)務(wù)邏輯方法,這里我們起名叫做Services。這樣做的目的是統(tǒng)一對(duì)外提供業(yè)務(wù)邏輯,當(dāng)我們?cè)O(shè)計(jì)頁(yè)面邏輯的時(shí)候就可以完全不用關(guān)心業(yè)務(wù)邏輯實(shí)現(xiàn)了,所以頁(yè)面設(shè)計(jì)人員和后臺(tái)設(shè)計(jì)人員可以分離,各自僅僅關(guān)心自己的領(lǐng)域顯然有助于高效的開(kāi)發(fā)。再者,如果我們需要更改業(yè)務(wù)邏輯方法的話,僅僅修改這個(gè)接口和其實(shí)現(xiàn)就可以了,使得代碼有很高的維護(hù)性。代碼如下:
package?domain;
import?java.util.List;
import?domain.entities.Note;


public?interface?Services?
{
????
????public?void?saveNote(Note?note);
????
????public?List<Note>?getAllNotes();

}5 實(shí)現(xiàn)統(tǒng)一數(shù)業(yè)務(wù)邏輯接口
當(dāng)然了,我們現(xiàn)在就要實(shí)現(xiàn)的Services里聲明的所有數(shù)據(jù)庫(kù)方法:在domain包的ServiceImp,這里才是要真正寫(xiě)代碼的地方(別急,每個(gè)方法不超過(guò)兩行就搞定了)。這個(gè)類(lèi)有所有dao的引用,Spring會(huì)依照上面在spring-beans.xml的配置注入所有引用的實(shí)例,實(shí)際上我們調(diào)用的是GenericSupport的方法,而這些方法基本上覆蓋了所有的業(yè)務(wù)邏輯需求。通過(guò)繼承GenericDaoSupport<T>的方法如下,足夠你用了,萬(wàn)一不行使用里面的HQL的查詢支持的方法吧。
我們這個(gè)例子的ServicesImp代碼如下,這樣看來(lái)我們?nèi)匀粵](méi)有寫(xiě)什么代碼?。?br />
package?domain;
import?java.util.List;
import?dao.NoteDao;
import?domain.entities.Note;


public?class?ServicesImp?implements?Services
{
????NoteDao?noteDao;


????public?List<Note>?getAllNotes()?
{
????????return?noteDao.listAll();
????}


????public?void?saveNote(Note?note)?
{
????????noteDao.save(note);
????}

}
這樣,我們的后臺(tái)代碼,也就是業(yè)務(wù)邏輯代碼已經(jīng)編寫(xiě)完畢了。下面我們開(kāi)始寫(xiě)前臺(tái)。
前臺(tái)(頁(yè)面)邏輯代碼實(shí)現(xiàn)
1 頁(yè)面與ManagedBean
我們?cè)赼ctions包里添加一個(gè)叫做NotebookAction的類(lèi),這個(gè)類(lèi)自動(dòng)的將成為notebook.jsp(等下建立)頁(yè)面的ManagedBean,負(fù)責(zé)處理本頁(yè)面的邏輯。根據(jù)Frails的默認(rèn)配置,在actions包下的以頁(yè)面名字開(kāi)頭(首字母大寫(xiě))加上Action結(jié)尾的類(lèi)將自動(dòng)成為該頁(yè)面的ManagedBean。如果你不喜歡這個(gè)后綴(Action),你也可以根據(jù)自己的需要該成別的,方法是在frails-config.xml的<frails-config>里加上:
<mbean-package>actions</mbean-package>這里是對(duì)應(yīng)的ManagedBean所在的包名稱。
<mbean-suffix>Action</mbean-suffix>這里是所有ManagedBean的后綴。
按照Frails 2.0的規(guī)范,我們還應(yīng)該在相應(yīng)的Bean上顯示的標(biāo)注些annotation。具體代碼如下:
package?action;

import?net.sf.frails.bean.annotations.DefMbean;
import?net.sf.frails.bean.annotations.ScopeType;

@DefMbean(scope=ScopeType.REQUEST)

public?class?NotebookAction?
{

}您所要做的就是在類(lèi)聲明的前面加上@DefMbean(scope=ScopeType.REQUEST)。其中scope是Bean的范圍,默認(rèn)的是REQUEST。每個(gè)頁(yè)面的ManagedBean都應(yīng)該盡量使用REQUEST范圍,這樣做是有很大好處的,我們將在最后一個(gè)部分具體介紹。在給這個(gè)Bean里添加頁(yè)面所需要的屬性和方法。我們讓所有的ManagedBean繼承自一個(gè)ServicesAction,在ServicesAction里包含了對(duì)Services的聲明。按照Frials框架,在Services聲明前注明@SpringBean后,Spring就可以自動(dòng)注入了。
這樣繼承的好處是,我們?cè)谧鲰?yè)面測(cè)試的時(shí)候,只需要把ServicesAction的引用換成一個(gè)假的Services實(shí)現(xiàn),只是模擬下數(shù)據(jù)庫(kù)的結(jié)果就省去大量的反復(fù)啟動(dòng)數(shù)據(jù)庫(kù)的時(shí)間了。
package?action;

import?net.sf.frails.bean.annotations.SpringBean;
import?domain.Services;
import?domain.ServicesImp;


public?class?ServicesAction?
{


????/**?*//**
?????*?若果是測(cè)試頁(yè)面的話,去掉annotation換成模擬的實(shí)現(xiàn)就好了:
?????*??Services?services?=?new?MockServices();
?????*??這里MockServices是對(duì)Services接口的實(shí)現(xiàn),
?????*??但是邏輯代碼并不是真的操作數(shù)據(jù)庫(kù),而是直接返回一些東西,方便頁(yè)面測(cè)試。
?????*/
????@SpringBean
????Services?sevices;
}?
package?action;

import?java.util.ArrayList;
import?java.util.List;

import?net.sf.frails.bean.annotations.DefMbean;
import?net.sf.frails.bean.annotations.Prop;
import?net.sf.frails.bean.annotations.ScopeType;
import?domain.entities.Note;

@DefMbean(scope=ScopeType.REQUEST)

public?class?NotebookAction?extends?ServicesAction
{
????

????/**?*//**
?????*?存放已有的Note列表
?????*/
????@Prop
????List<Note>?noteList?=?new?ArrayList<Note>();


????/**?*//**
?????*?存放一個(gè)新的Note
?????*/
????@Prop
????Note?note?=?new?Note();
????

????/**?*//**
?????*?增加一個(gè)Note
?????*?@param?newNote?要增加的Note
?????*/
????public?void?addNote(Note?newNote)

????
{
????????
????}
}這個(gè)Bean里沒(méi)有setter和getter方法,這個(gè)并不是沒(méi)有給出完整的代碼,而是根本不用寫(xiě)!Frials知道為每個(gè)屬性增加setter和getter是多么乏味的事情,雖然elipse能夠自動(dòng)處理,但是Frials是和IDE無(wú)關(guān)的,并不假設(shè)快速開(kāi)發(fā)一定要建立在功能強(qiáng)大的IDE上(但是實(shí)際上JSF是暗含有這樣的假設(shè)的)。只需要在屬性前面加上@Prop,這個(gè)屬性就相當(dāng)于自動(dòng)加上了setter和getter方法。
更重要的是,我們?cè)趯?xiě)對(duì)應(yīng)的頁(yè)面的時(shí)候,EL表達(dá)式可以更加簡(jiǎn)練:在引用對(duì)應(yīng)本頁(yè)面的ManagedBean的時(shí)候,我們不需要再寫(xiě)這個(gè)Bean的名字,取而代之的是一個(gè)美元符號(hào)。所以,我們?cè)趯?xiě)datatable標(biāo)簽的時(shí)候,value的值寫(xiě)成 #{$.noteList}這樣就好了。現(xiàn)在來(lái)寫(xiě)JSF頁(yè)面吧,除了EL表達(dá)式簡(jiǎn)化了外,和原來(lái)的JSF寫(xiě)法沒(méi)有什么區(qū)別,記住名字的對(duì)應(yīng)就好了。
這里要說(shuō)明的一點(diǎn)是Frails的EL表達(dá)式可以傳遞參數(shù),形式為#{$.method['arg1','arg2'... ]}。并且Frails提供了注入支持,我們甚至可以直接在頁(yè)面上通過(guò)EL表達(dá)式調(diào)用Services方法,比如我們這里要實(shí)現(xiàn)刪除一個(gè)Note,那么可以這樣寫(xiě)dataTable:
<h:dataTable?value="#{$.noteList}"?var="note">
????????????<h:column>
????????????????
.
????????????</h:column>
????????????<h:column>
????????????????<h:commandButton?action=#{$.Services.delete['note']}>
<!--?BUTTON?NAME?-->
</h:commandButton>
????????????</h:column>
</h:dataTable>另外頁(yè)面的導(dǎo)航也簡(jiǎn)化成直接寫(xiě)要導(dǎo)航到頁(yè)面的名字了。如action="a",則直接導(dǎo)航到a.jsp。
2 輸入驗(yàn)證假設(shè)我們需要驗(yàn)證驗(yàn)證是否輸入了用戶名,并且用戶名的長(zhǎng)度在4-12之間。輸入內(nèi)容應(yīng)該大于12。當(dāng)然,我們完全可以按照J(rèn)SF原有的方式在頁(yè)面里嵌入驗(yàn)證的組件。但是,驗(yàn)證和頁(yè)面邏輯混雜在一起不是件好事情,隨著需要驗(yàn)證的內(nèi)容加大,代碼變得難以維護(hù),并且驗(yàn)證需求的變更——這個(gè)是可能的可會(huì)導(dǎo)致很大的損失。在Frails里,對(duì)屬性的驗(yàn)證都在ManagedBean里屬性聲明時(shí)候處理。也就是說(shuō),我們聲明一個(gè)屬性的時(shí)候同時(shí)說(shuō)明需要滿足什么條件的時(shí)候這個(gè)屬性才會(huì)接受值,否則我們就提示錯(cuò)誤信息,或者轉(zhuǎn)到某個(gè)專(zhuān)門(mén)用來(lái)報(bào)錯(cuò)的頁(yè)面。如果我們要在NotebookAction里驗(yàn)證輸入的note,具體的做法有兩種:
一是使用annotation在當(dāng)前Bean里寫(xiě)驗(yàn)證方法,一般用于對(duì)一般類(lèi)型的驗(yàn)證。要做的就是在屬性前面加上@ValidateXXX(message="error message"),message里存放錯(cuò)誤信息。
例如我們要驗(yàn)證一個(gè)Email:
@Prop
????@ValidateEmail(message="error.message")
????String?email;或者要驗(yàn)證字符串,要求不為空,最小長(zhǎng)度為3:
@Prop
????@ValidateString(required?=?true,?minLen?=?3)
????String?name;還有@ValidateNumber,@ValidateDate。如果這些基本的還不能滿足你驗(yàn)證需求,可以使用一個(gè)方法來(lái)抓們驗(yàn)證屬性,例如我們要用一個(gè)叫validateMethod的方法驗(yàn)證一個(gè)叫username的字符串,直接在Prop加上屬性(validator="Method"):
@Prop(validator="validate")
String?username;

public?void?validate(String?username)?
{

if(username?==?null?||?username.length()?==?0)?
{
//DO?SOMETHING?TO?REPROT?ERROR
}
}二是使用驗(yàn)證Bean,用于對(duì)類(lèi)的屬性域的驗(yàn)證。回到我們notebook的例子中來(lái),我們要驗(yàn)證一個(gè)note,可以在note屬性前加上@ValidateBean以及參數(shù):
@Prop

????@ValidateBean(fields=
{
????@Field(name="poster",required=true,
????????????validateNumber=@ValidateNumber(max=12,min=8,message="Poster?Name?????too?long?or?to?short"),
????????????message="You?must?input?your?name"),
????@Field(name="content",required=true,
????????????validateString=@ValidateString(minLen=8,message="You?have?????to?put?more?words?here"),
????????????message="You?must?input?the?content")
????})
????Note?note?=?new?Note();其中每一個(gè)@Feild對(duì)應(yīng)了一個(gè)類(lèi)中要驗(yàn)證的屬性。Name對(duì)應(yīng)的是屬性的名稱,必須與你在類(lèi)中屬性聲明一致;required代表是否不為空。后面還可以跟上若干validateXXX嵌套,每個(gè)層面上都可以有message來(lái)顯示錯(cuò)誤。當(dāng)然了,其中的以部分這里也可以被標(biāo)注為使用方法來(lái)驗(yàn)證。
也可以將驗(yàn)證Bean單獨(dú)寫(xiě)到一個(gè)專(zhuān)門(mén)的驗(yàn)證類(lèi)上,只需要在被驗(yàn)證的屬性前加上@ValidateBean,并且在里面指明用來(lái)驗(yàn)證類(lèi)的別名就可以了:
@Prop
????@ValidateBean(name="noteValidator")
????Note?note?=?new?Note();然后我們新建一個(gè)類(lèi)來(lái)負(fù)責(zé)驗(yàn)證note。在類(lèi)前面加上@BeanValidator標(biāo)注,其fiels與恰面提到的一樣,只是注意在BeanValidator的name要和前面被驗(yàn)證屬性上的匹配。代碼如下:
import?net.sf.frails.bean.validator.BeanValidator;


@BeanValidator(name?=?"infoValidator",?fields?=?
{
????????//THE?SAME?WITH?VALIDATORBEAN
})

public?class?Validator?
{

}以上我們實(shí)現(xiàn)了統(tǒng)一的驗(yàn)證過(guò)程。經(jīng)過(guò)實(shí)踐后您會(huì)發(fā)現(xiàn)將驗(yàn)證代碼從頁(yè)面分離開(kāi)后會(huì)帶來(lái)多么大的好處。這些請(qǐng)您親自去體驗(yàn)吧。
3 ManagedBean范圍及設(shè)計(jì)模式
在實(shí)際的項(xiàng)目中,頁(yè)面肯定不會(huì)只有一個(gè)。頁(yè)面和頁(yè)面之間的需要傳遞的數(shù)據(jù),以前往往是放在Session里面,因?yàn)镴SF內(nèi)建的導(dǎo)航機(jī)制都是POST請(qǐng)求。這就使得我們不得不將些ManagedBean設(shè)置為Session范圍,但是這樣做并不是那么必要,會(huì)造成一些麻煩。最好的設(shè)計(jì)是,每個(gè)頁(yè)面的ManagedBean都是Request范圍,他們通過(guò)簡(jiǎn)單的GET請(qǐng)求互相傳遞一些數(shù)據(jù)的主鍵,而每個(gè)ManagedBean自己負(fù)責(zé)根據(jù)這些主鍵將相應(yīng)的信息提取出來(lái)。這樣頁(yè)面與頁(yè)面之間就通過(guò)類(lèi)似接口一樣關(guān)聯(lián)起來(lái),一旦他們之間傳遞的主鍵約定好了,頁(yè)面就可以相對(duì)分離開(kāi);同時(shí),這些頁(yè)面也是支持瀏覽器收藏夾收藏的。這樣我們只需要少量的,基本上是一個(gè)Session范圍的Bean保存當(dāng)前用戶的信息,一個(gè)Application范圍的Bean保存應(yīng)用的某些屬性,其余的都是Request范圍的Bean來(lái)實(shí)現(xiàn)整個(gè)頁(yè)面架構(gòu)了。
有了Frails中@PreRender將一個(gè)方法標(biāo)注為在JSF組件樹(shù)渲染周期前調(diào)用,@Param將一個(gè)屬性標(biāo)注為GET請(qǐng)求參數(shù),通過(guò)這兩個(gè)annotation 的結(jié)合使用,我們能很輕松的實(shí)現(xiàn)GET請(qǐng)求。
假設(shè)我們要新增加一個(gè)頁(yè)面叫note.Jsp,用來(lái)顯示一條留言的詳細(xì)內(nèi)容。他的ManagedBean(當(dāng)然就是NoteAction了)接受一個(gè)long id作為GET請(qǐng)求參數(shù),并且在渲染出組件樹(shù)前事先從數(shù)據(jù)庫(kù)里根據(jù)id提取出note的內(nèi)容。
package?action;

import?net.sf.frails.bean.annotations.DefMbean;
import?net.sf.frails.bean.annotations.Param;
import?net.sf.frails.bean.annotations.PreRender;

@DefMbean

public?class?NoteAction?
{

????@Param(name?=?"noteID")
????private?long?id;
????
????@PreRender
????private?void?preRender()

????
{
????????if(?id?!=?0?)

????????
{
????????????//GET?INFO

????????}else
{
????????????//HANDLE?WITH?NO?GET?PARAM
????????}
????}
????
????//OTHERS
}上面Param里的name屬性就是GET參數(shù)的名字,并且可以自動(dòng)的將String參數(shù)類(lèi)型轉(zhuǎn)換成其他的基本類(lèi)型。GET參數(shù)的定義可以簡(jiǎn)單的在頁(yè)面上使用<h:OutputLink>,這個(gè)標(biāo)簽對(duì)應(yīng)的是一個(gè)GET請(qǐng)求,在內(nèi)嵌入<f:param>就是請(qǐng)求的參數(shù)了。
<h:outputLink?value="note.jsp"?>
????????????????????<h:outputText?value="查看詳細(xì)信息"></h:outputText>
????????????????????<f:param?name="noteID"/>
????????</h:outputLink>請(qǐng)注意outputLink的屬性value就是要導(dǎo)航的頁(yè)面,這里Frials沒(méi)有對(duì)其進(jìn)行任何的簡(jiǎn)化,必須填寫(xiě)要導(dǎo)航到的頁(yè)面的全名。Param里的name屬性就是GET參數(shù)的名字,必須和noteAction里的@Param屬性一致。
總結(jié):
我們通過(guò)一個(gè)簡(jiǎn)單的例子,基本上了解了Frails框架模板快速搭建一個(gè)Spring+Hibernate+JSF的項(xiàng)目過(guò)程。首先,我們按照Frails的默認(rèn)配置快速的完成了各種設(shè)置;接下來(lái)我們使用Frails對(duì)Dao的支持類(lèi)在沒(méi)有寫(xiě)任何Hibernate或者SQL代碼的情況下完成了數(shù)據(jù)庫(kù)業(yè)務(wù)的方法;然后我們了解了頁(yè)面上Frails簡(jiǎn)化的EL表達(dá)式;還有相當(dāng)重要的驗(yàn)證方法;最后我們了解了下通過(guò)實(shí)現(xiàn)GET請(qǐng)求來(lái)使得頁(yè)面之間相對(duì)松耦合的設(shè)計(jì)。希望這樣的介紹能帶您進(jìn)入到Frails框架的奇妙世界中來(lái),我相信當(dāng)您親自體驗(yàn)Frails的魅力后,一定愛(ài)不釋手!