原創(chuàng)|其它|編輯:郝浩|2009-03-10 15:49:56.000|閱讀 635 次
概述:Java平臺(tái)已經(jīng)逐漸發(fā)展為一個(gè)成熟可靠的企業(yè)應(yīng)用平臺(tái),成熟的應(yīng)用平臺(tái)的一個(gè)標(biāo)志則是它能夠帶動(dòng)大量的衍生技術(shù)以及可以與其他技術(shù)集成的選項(xiàng)。本文將詳細(xì)講述怎樣用Grails這項(xiàng)傳統(tǒng)JEE應(yīng)用開發(fā)的衍生技術(shù),結(jié)合另一項(xiàng)完全不同但卻可以在Java中使用的Flex技術(shù)來開發(fā)JEE。
# 界面/圖表報(bào)表/文檔/IDE等千款熱門軟控件火熱銷售中 >>
Java平臺(tái)已經(jīng)逐漸發(fā)展為一個(gè)成熟可靠的企業(yè)應(yīng)用平臺(tái),成熟的應(yīng)用平臺(tái)的一個(gè)標(biāo)志則是它能夠帶動(dòng)大量的衍生技術(shù)以及可以與其他技術(shù)集成的選 項(xiàng)。本文將詳細(xì)講述怎樣用Grails這項(xiàng)傳統(tǒng)JEE應(yīng)用開發(fā)的衍生技術(shù),結(jié)合另一項(xiàng)完全不同但卻可以在Java中使用的Flex技術(shù)來開發(fā)JEE。這兩 個(gè)平臺(tái)都能大幅度提高開發(fā)效率。兩者相結(jié)合則在為J2EE應(yīng)用創(chuàng)建富客戶端的同時(shí)不影響整體的開發(fā)效率。
Grails的前身是一個(gè)在 JVM中運(yùn)行的web應(yīng)用,它使用Groovy以及其它幾個(gè)著名的框架,比如Spring和Hibernate。為了實(shí)現(xiàn)快速應(yīng)用開發(fā),它極為依賴 “Convention over Configuration”原則。Groovy擁有很多動(dòng)態(tài)的特性,在定義組件間共同行為方面,功能非常強(qiáng)大。Grails采用plug-in構(gòu)架,因 此很容易把它與其他框架集成,而且也很容易在應(yīng)用間復(fù)用各自的功能。
Flex是個(gè)RIA開發(fā)套件,由它創(chuàng)建的SWF應(yīng)用只能在 FlashPlayer下應(yīng)用。這是Adobe(前身為MacroMedia)的一個(gè)新型 Flash開發(fā)套件。除了擁有豐富的widget和把各種widget粘合在一起的強(qiáng)大的語言之外,它還能提供一些高端通信解決方案,分布式應(yīng)用程序的開 發(fā)因此變得相當(dāng)容易。它使用兩種語法:MXML和ActionScript。MXML創(chuàng)建在XML語法之上,專門用來定義通用組件的用戶接口;而 ActionScript則用來定義組件之間的動(dòng)態(tài)交互。
Grails和Flex的集成——難題所在
要把Grails和Flex這兩個(gè)建立在完全不同基礎(chǔ)上的框架結(jié)合起來,首先會(huì)遇到諸多通信方面的問題:
一個(gè)框架中的組件如何才能在另一個(gè)框架中找到正確的通信對(duì)象?
用戶在Flex UI發(fā)起的通信必須通過Grails組件來調(diào)用業(yè)務(wù)邏輯。那么,F(xiàn)lex UI組件該如何找到正確的Grails組件呢?
框架間如何解析彼此的數(shù)據(jù)?
Flex采用ActionScript來描述數(shù)據(jù),而Grails則采用Java和Groovy對(duì)象。Flex UI向服務(wù)器發(fā)送的ActionScript對(duì)象應(yīng)該被轉(zhuǎn)述為應(yīng)用程序能夠理解的數(shù)據(jù)結(jié)構(gòu)。這該如何實(shí)現(xiàn)?
某個(gè)用戶的修改該如何與該應(yīng)用程序的其他用戶交互?
這是多用戶應(yīng)用程序普遍存在的問題,但同時(shí)運(yùn)用兩個(gè)不同的框架使得問題更加復(fù)雜。難點(diǎn)在于Grails應(yīng)用程序,用戶通過Flex UI來啟動(dòng)這個(gè)應(yīng)用,但如何通過Flex UI與其他用戶通信,讓他們知道該用戶的這一動(dòng)作呢?
在接下來的三個(gè)部分中,我們詳細(xì)討論上文提到的三個(gè)問題,尋找采用Grails和Flex的解決方案。
集成——尋找消息接收對(duì)象
一個(gè)框架中的組件如何才能在另一個(gè)框架中找到正確的通信對(duì)象呢?
具體到Grails和Flex的話,這個(gè)問題其實(shí)就是在問Flex組件怎樣才能找到正確的Grails組件,進(jìn)而發(fā)送請求數(shù)據(jù),或者以用戶的名義執(zhí)行一些操作。為了更好的理解解決這個(gè)難點(diǎn)的方法,我們首先來了解一下Flex的通信子系統(tǒng)。
Flex中的客戶——服務(wù)器通信
Flex的通信子系統(tǒng)可以分為客戶和服務(wù)器兩個(gè)部分。客戶部分包含了那些允許應(yīng)用程序發(fā)送或者接受消息的組件,比如RemoteObject和 Consumer組件。這些組件與服務(wù)器部分特定的“服務(wù)”對(duì)象相關(guān)聯(lián),比如RemotingService和MessagingService。客戶組 件及其相關(guān)聯(lián)的服務(wù)器組件的結(jié)合能夠支持典型的通信模式。比方說結(jié)合Consumer、Producers和MessagingService,應(yīng)用軟件 就能夠使用Publish-Subscribe機(jī)制來通信。
客戶和服務(wù)器件的通信通過信道(Channel)來完成。信道的實(shí)現(xiàn)方式并不唯一,所有信道中最重要的是AMFChannel和 RTMPChannel。 AMFChannel建立在HTTP基礎(chǔ)上,也就是說建立在請求-回復(fù)的構(gòu)架上。它可以和MessagingService同時(shí)使用,從而支持 Publish-Subscribe構(gòu)架。這種結(jié)合下,信道定期從發(fā)布中讀取新的消息,生成請求。RTMPChannel在這樣的配置下效率更高,它能夠 在TCP/IP的基礎(chǔ)上支持客戶與服務(wù)器間的連接。這樣一來,客戶與服務(wù)器之間能夠立即發(fā)送或接受消息。遺憾的是,Adobe免費(fèi)開源的Flex實(shí)現(xiàn)—— BlazeDS不包含這樣的RTMPChannel實(shí)現(xiàn)。
Flex中最重要的通信基礎(chǔ)設(shè)施是Destinations。 Destination是通信信道的服務(wù)器端終點(diǎn)。一個(gè)服務(wù)提供一個(gè) Destination,而客戶組件則通過這個(gè)Destination與這個(gè)服務(wù)相關(guān)聯(lián)。關(guān)聯(lián)的客戶組件可以向Destination發(fā)送和讀取消息。 Destinations可以由Factories創(chuàng)建。
Grails暴露的遠(yuǎn)程接口:服務(wù)
如何把Flex復(fù)雜的 通信設(shè)施和Grails結(jié)合起來呢?Grails能夠識(shí)別幾類對(duì)象:域?qū)ο蟆⒖刂破鳌⒁晥D和服務(wù)。Grails中的每個(gè)服務(wù)都是通過外部通信信道——比如 HTTP——展示某些功能或者服務(wù)的一個(gè)對(duì)象。而在Flex中,每個(gè)服務(wù)則與一個(gè)Destination相對(duì)應(yīng)。
這恰恰就是針對(duì) Grails的flex-plugin所提供的解決方案。Grails中所有注明向Flex展示的服務(wù)都將在Flex框架中以Destination的形 式注冊。Grails通過一個(gè)特定的 Factory把注明的服務(wù)添加到Flex中特別配置的RemotingService。這個(gè)特定的Factory會(huì)在Grails使用的Spring上 下文中定位到對(duì)應(yīng)的服務(wù)。所有這些配置都可以在services-config.xml中找到,flex-plugin會(huì)為Grails將這個(gè)文件復(fù)制到 正確的地方。
class UserService {
static expose = ['flex-remoting']
def List all() {
User.createCriteria().listDistinct {}
}
def Object get(id) {
User.get(id);
}
def List update(User entity) throws BindException {
entity.merge();
if (entity.errors.hasErrors()) {
throw new BindException(entity.errors);
}
all();
}
def List remove(User entity) {
entity.delete();
all();
}
}
這段配置將UserService展示給flex客戶。下面這段MXML代碼則是對(duì)前面這段配置的應(yīng)用。RemoteObject的 destination是userService,這個(gè)userService正是Grails中目標(biāo)對(duì)象提供的服務(wù)名。服務(wù)對(duì)象的所有方法這下都可以作 為遠(yuǎn)程操作調(diào)用。ActionScript可以將這些操作像一般的方法那樣調(diào)用,而方法調(diào)用的結(jié)果或錯(cuò)誤也可以當(dāng)作一般的ActionScript事件來 處理。
...
結(jié)論
<mx:RemoteObject id="service" destination="userService">
<mx:operation name="all" result="setList(event.message.body)"/>
<mx:operation name="get" result="setSelected(event.message.body)"/>
<mx:operation name="update"/>
<mx:operation name="remove"/>
</mx:RemoteObject>
...
flex-plugin為Grails提供的針對(duì)集成的解決方案非常漂亮,易于使用而且?guī)缀跏亲詣?dòng)化的。在Convention-over-Configuration概念下,Destinations動(dòng)態(tài)添加到Flex配置的時(shí)候使用命名規(guī)范。
數(shù)據(jù)轉(zhuǎn)換
框架間如何互相轉(zhuǎn)換對(duì)方的數(shù)據(jù)(本文中就是Java和ActionScript對(duì)象轉(zhuǎn)換的問題)?
這個(gè)問題的關(guān)鍵之處在于兩框架相交接的地方。Flex包含Java(web服務(wù)器)和ActionScript(客戶端)兩個(gè)組件。因此,Grails和Flex之間的邊界就在web服務(wù)器,而這個(gè)服務(wù)器在兩個(gè)框架內(nèi)實(shí)際上都是Java應(yīng)用。
Flex的Java組件只關(guān)注于與Flex客戶間的通信。基于ActionScript對(duì)象的AMF協(xié)議就用于這樣的數(shù)據(jù)通信。服務(wù)器內(nèi)部的 Java代碼將數(shù)據(jù)轉(zhuǎn)換成ActionScript對(duì)象,這些對(duì)象在信道上實(shí)現(xiàn)系列化。Flex支持Java的基本類型,也支持其標(biāo)準(zhǔn)復(fù)雜類型(比如 Date或者 Collection類型)。由于ActionScript是門動(dòng)態(tài)語言,因此它也支持隨機(jī)對(duì)象結(jié)構(gòu)。Java對(duì)象域會(huì)轉(zhuǎn)換成ActionScript對(duì) 象的動(dòng)態(tài)屬性。但把這些非類型ActionScript對(duì)象轉(zhuǎn)換成Groovy域?qū)ο蟮倪^程則沒那么直接,它會(huì)默認(rèn)生成一個(gè)Map,將屬性以key- Value對(duì)的形式存儲(chǔ)到這個(gè)Map中。
Flex創(chuàng)建與Groovy域?qū)ο髶碛型瑯訉傩缘腁ctionScript類,通過注解將兩者互相關(guān)聯(lián)起來,這樣一來,數(shù)據(jù)轉(zhuǎn)換更加方便。下面的這個(gè)例子就是這樣一對(duì)關(guān)聯(lián)的Groovy-ActionScript。
Groovy | ActionScript |
class User implements Serializable { String username String password String displayName} | [RemoteClass(alias="User")] |
注 解“RemoteClass”將ActionScript類鏈接到由alias屬性指明的Java(或Groovy)類。alias這個(gè)屬性應(yīng)該包含完整 的類名。Grails中的領(lǐng)域類通常都添加到默認(rèn)的類包。Grails類中的所有屬性都會(huì)復(fù)制到ActionScript類。這些屬性的名字都應(yīng)當(dāng)完全一 樣。Grails會(huì)為所有需要“id”和“version”的領(lǐng)域?qū)ο髣?dòng)態(tài)添加這兩個(gè)屬性,領(lǐng)域?qū)ο笠虼丝梢栽谂c客戶端交互的時(shí)候保留這兩個(gè)信息。
結(jié)論
Flex 提供的與Java(或Groovy)間數(shù)據(jù)轉(zhuǎn)換的解決方案會(huì)導(dǎo)致很多重復(fù)的代碼。每個(gè)領(lǐng)域類都會(huì)被定義兩次,一次用Groovy(或Java)定義,另一 次用ActionScript。但是這樣一來,可以添加一些客戶端特定代碼,比如說那些單單用ActionScript編寫的控制對(duì)象顯示的代碼。這也推 動(dòng)編輯器同時(shí)提供兩種代碼的自動(dòng)完成功能。至于用于配置的注解則非常簡便。
多用戶
應(yīng)用程序如何將某個(gè)用戶所作的修改通知到其他用戶?
對(duì)于一個(gè)能同時(shí)支持多用戶的應(yīng)用程序來說,將某個(gè)用戶對(duì)共享數(shù)據(jù)所做的修改通知到其他用戶著實(shí)是個(gè)挑戰(zhàn)。對(duì)于其他用戶來說,這個(gè)過程可以看作是有服務(wù)器發(fā)起的通信。
單個(gè)中央結(jié)點(diǎn)(通常指服務(wù)器)向很多接收點(diǎn)(通常指客戶)發(fā)起通信的時(shí)候,發(fā)布-注冊(publish-subscribe)就非常實(shí)用。客戶在服務(wù)器上注冊之后,服務(wù)器上任何相關(guān)消息的發(fā)布,他們都會(huì)收到通知。
由于Grails可以使用Java,自然就可以用到JMS。JMS是應(yīng)用程序間通信的Java標(biāo)準(zhǔn),它支持publish-subscribe技術(shù),而且應(yīng)用程序也可以通過適配器來集成JMS。
Grails中的JMS配置
在眾多標(biāo)準(zhǔn)中,有一個(gè)特別針對(duì)Grails的jms-plugin,它添加了很多有用的方法可以用來向JMS目的地對(duì)象、向所有的控制器和服務(wù)類發(fā)送消息。在上一章中提到的UserService就可以運(yùn)用這些方法在數(shù)據(jù)發(fā)生變化時(shí)通過JMS向所有的用戶發(fā)送更新消息。
class UserService {
...
def List update(User entity) throws BindException {
entity.merge(flush:true );
if (entity.errors.hasErrors()) {
throw new BindException(entity.errors)
}
sendUpdate();
all();
}
def List remove(User entity) {
entity.delete(flush:true );
sendUpdate();
all();
}
private def void sendUpdate() {
try {
sendPubSubJMSMessage("tpc",all(),[type:"User"]);
} catch (Exception e) {
log.error("Sending updates failed.", e);
}
}
}
服務(wù)可以決定什么時(shí)候發(fā)送什么樣的消息。無論用戶什么時(shí)候更新或刪除數(shù)據(jù),都會(huì)發(fā)送一條包含了完整的數(shù)據(jù)列表的消息。這條消息會(huì)發(fā)送到特定話題,也就是 這里的“tpc”。任何注冊了這個(gè)話題的用戶都將接收到新數(shù)據(jù)。列表中的對(duì)象類型(本例中也就是“User”)作為元數(shù)據(jù)添加到消息中,接收對(duì)象因此在服 務(wù)器上注冊的時(shí)候特別指明他們所關(guān)注的數(shù)據(jù)類型。
為了讓Grails應(yīng)用也能夠采用JMS,每個(gè)JMS都需要實(shí)現(xiàn)provider。 Apache有個(gè)免費(fèi)的開源實(shí)現(xiàn),只需簡單配置就能在Grails應(yīng)用程序中使用。你所需要做的是把ApacheMQ類庫添加到Grails應(yīng)用的lib 文件夾下,再將下列代碼段復(fù)制到connection factory所使用的conf/spring文件夾下的resources.xml中。
...
在Flex中接收J(rèn)MS消息
<bean id="connectionFactory"
class="org.apache.activemq.pool.PooledConnectionFactory"
destroy-method="stop">
<property name="connectionFactory">
<bean class="org.apache.activemq.ActiveMQConnectionFactory">
<property name="brokerURL" value="vm://localhost"/>
</bean>
</property>
</bean>
...
目前的Flex配置僅僅包含一個(gè)RemotingService,依靠它來支持request-response類型的用戶與UserService間 交互。這個(gè)服務(wù)由flex-plugin向Grails中添加。除此之外,我們還需要一個(gè)MessagingService來支持publish- subscribe類型的交互。
...
<service id="message-service" class="flex.messaging.services.MessageService" messageTypes="flex.messaging.messages.AsyncMessage">
<adapters>
<adapter-definition id="jms" class="flex.messaging.services.messaging.adapters.JMSAdapter" default="true"/> </adapters> <destination id="tpc">
<properties>
<jms>
<message-type>javax.jms.ObjectMessage</message-type>
<connection-factory>ConnectionFactory</connection-factory>
<destination-jndi-name>tpc</destination-jndi-name>
<delivery-mode>NON_PERSISTENT</delivery-mode>
<message-priority>DEFAULT_PRIORITY</message-priority>
<acknowledge-mode>AUTO_ACKNOWLEDGE</acknowledge-mode>
<transacted-sessions>false</transacted-sessions>
<initial-context-environment>
<property>
<name>Context.PROVIDER_URL</name>
<value>vm://localhost</value>
</property>
<property>
<name>Context.INITIAL_CONTEXT_FACTORY</name>
<value>org.apache.activemq.jndi.ActiveMQInitialContextFactory</value>
</property>
<property>
<name>topic.tpc</name>
<value>tpc</value>
</property>
</initial-context-environment>
</jms>
</properties>
</destination>
</service>
...
在services-config.xml文件中,我們需要添加下列這段包含了一個(gè)新的MessagingService和JMSAdapter的代碼 段。添加的這個(gè)適配器將服務(wù)中的destination鏈接到JMS資源。這個(gè)服務(wù)中還包含一個(gè)destination的配置,flex代碼中的用戶可以 通過注冊獲得這個(gè)destination的數(shù)據(jù)更新。Destination中含有很多JMS特定的配置。大部分都是常用的JMS屬性。initial- context-environment中的“topic.tpc”屬性是個(gè)可定制的ActiveMQ屬性,這個(gè)屬性將在上下文中注冊一個(gè)JNDI名為 “tpc”的話題。
...
<mx:Consumer destination="tpc" selector="type = 'User'"
message="setList(event.message.body)"/>
...
Flex客戶端代碼非常簡單。消息根據(jù)選擇器(selector)被發(fā)送到特定的destination,而Consumer組件因此接受到所對(duì)應(yīng)的 destination中的消息。在這個(gè)例子中,我們通過篩選器指定Consumer所關(guān)注的消息是元數(shù)據(jù)“type”屬性值為“User”的內(nèi)容。無論 消息是何時(shí)收到的,消息的內(nèi)容,也就是User-objects列表會(huì)被置為可顯示的內(nèi)部列表。消息內(nèi)容的處理和RemoteObject上“all”處 理的返回值完全一樣。
結(jié)論
Grails和Flex中將數(shù)據(jù)變化傳遞給多用戶的解決方案完全可以通過標(biāo)準(zhǔn)組件來實(shí)現(xiàn)。涉及到的組件數(shù)量很多,配置和實(shí)現(xiàn)因此相當(dāng)復(fù)雜。如果配置正確的話,這個(gè)解決方案使用起來就非常方便。
合并解決方案
回顧前三章提出的解決方案,你會(huì)發(fā)現(xiàn)還可以把他們合并起來得到一個(gè)更為通用的解決方案來實(shí)現(xiàn)Flex/Grails應(yīng)用程序客戶與服務(wù)器間的關(guān)于領(lǐng)域狀態(tài)信息的通信。本章節(jié)中,我們要討論的就是這樣一個(gè)更為通用的解決方案。
泛化服務(wù)器端代碼
問題1和3的解決方案所需要的服務(wù)器端的代碼可以合并到同一個(gè)Groovy服務(wù)中。我們將把它指明為針對(duì)User領(lǐng)域類的服務(wù)。通過Groovy這樣一門動(dòng)態(tài)語言,要把這樣一個(gè)服務(wù)泛化到面向所有領(lǐng)域類的操作非常容易。
import org.codehaus.groovy.grails.commons.ApplicationHolder
class CrudService {
static expose = ['flex-remoting']
def List all(String domainType) {
clazz(domainType).createCriteria().listDistinct {}
}
def Object get(String domainType, id) {
clazz(domainType).get(id)
}
def List update(String domainType, Object entity)
throws BindException {
entity.merge(deepValidate:false, flush:true)
if (entity.errors.hasErrors()) {
throw new BindException(entity.errors)
}
sendUpdate(domainType);
all(domainType);
}
def List remove(String domainType, Object entity) {
entity.delete(flush:true);
sendUpdate(domainType);
all(domainType);
}
private def Class clazz(className) {
return ApplicationHolder.application.getClassForName(className);
}
private def void sendUpdate(String domainType) {
try {
sendPubSubJMSMessage("tpc", all(domainType), [type:domainType]);
} catch (Exception e) {
log.error("Sending updates failed.", e);
}
}
}
要實(shí)現(xiàn)這個(gè)目的的關(guān)鍵在于讓客戶來決定返回的領(lǐng)域類型。出于這個(gè)目的,我們需要為所有服務(wù)引入一個(gè)參數(shù),通過這個(gè)參數(shù)為服務(wù)器鑒定各個(gè)領(lǐng)域類型。很明 顯,對(duì)于這個(gè)參數(shù)來說,領(lǐng)域類型的類名是無非是最好的選擇。為所有領(lǐng)域?qū)ο筇峁?C(reate)R(etrieve)U(pdate)D(elete)操 作的服務(wù)被稱為CrudService。
一旦有任何數(shù)據(jù)更改,CrudService都會(huì)向JMS話題發(fā)送更新消息。這個(gè)更新消息包含了應(yīng)用程序所知道的完整的領(lǐng)域?qū)ο罅斜怼榱俗層?戶來決定這是否是自己心儀的更新內(nèi)容,領(lǐng)域類型的類名將以元數(shù)據(jù)方式添加到消息中。
客戶端代碼
public class DomainInstancesManager
{
private var domainType : String;
public function EntityManager(domainType : String, destination : String) {
this.domainType = domainType;
initializeRemoteObject();
initializeConsumer(destination);
}
private var _list : ArrayCollection = new ArrayCollection();
public function get list () : ArrayCollection {
return _list;
}
private function setList(list : *) : void {
_list.removeAll();
for each (var o : * in list) {
_list.addItem(o);
}
}
internal static function defaultFault(error : FaultEvent) : void {
Alert.show("Error while communicating with server: " + error.fault.faultString);
}
...
}
實(shí)現(xiàn)客戶端的ActionScript基本上包含兩個(gè)組件:簡化request-response對(duì)話的RemoteObject和用于 producer-subscriber對(duì)話的Consumer組件。上一章節(jié)中,這些對(duì)象通過MXML代碼實(shí)現(xiàn)初始化,但他們也可以通過 ActionScript來創(chuàng)建。上面這段代碼段顯示的是這兩個(gè)組件共同使用的結(jié)構(gòu):包含了實(shí)例和錯(cuò)誤處理的列表。包含實(shí)例的列表根據(jù)任何一個(gè)通信組件發(fā) 送的消息而更新。
...
private var consumer : Consumer;
private function initializeConsumer(destination : String) : void {
this.consumer = new Consumer();
this.consumer.destination = destination;
this.consumer.selector = "type ='" + domainType + "'";
this.consumer.addEventListener(MessageEvent.MESSAGE, setListFromMessage);
this.consumer.subscribe();
}
private function setListFromMessage(e : MessageEvent) : void {
setList(e.message.body);
}
...
這里這段代碼顯示的是Consumer如何通過ActionScript來構(gòu)建,這段代碼用來接收服務(wù)器端發(fā)送的消息。Consumer的 selector屬性僅僅用來接收那些包括了元數(shù)據(jù)中所指明的領(lǐng)域類型的消息。無論什么時(shí)候接收到這樣的消息,event handler都會(huì)被調(diào)用,并且列表也會(huì)得到更新。
接下來這段代碼段將RemoteObject設(shè)置為request-response型通信的一個(gè)結(jié)點(diǎn)。所有必要的操作都作為操作屬性而添加到RemoteObject上,客戶因而很容易調(diào)用這些操作。
...
private var service : RemoteObject;
private var getOperation : Operation = new Operation();
public function initializeRemoteObject() {
this.service = new RemoteObject("crudService");
var operations:Object = new Object();
operations["all"] = new Operation();
operations["all"].addEventListener(ResultEvent.RESULT, setListFromInvocation);
operations["get"] = getOperation
operations["remove"] = new Operation()
operations["remove"].addEventListener(ResultEvent.RESULT, setListFromInvocation);
operations["update"] = new Operation()
operations["update"].addEventListener(ResultEvent.RESULT, setListFromInvocation);
this .service.operations = operations;
this .service.addEventListener(FaultEvent.FAULT, defaultFault);
// Get the instances from the server.
this.service.all(domainType);
}
public function get(id : *, callback : Function) : void {
var future: AsyncToken = getOperation.send(domainType, id);
future.addResponder(new CallbackResponder(callback));
}
public function update(entity : Object) : void {
service.update(domainType, entity);
}
public function remove(entity : Object) : void {
service.remove(domainType, entity);
}
private function setListFromInvocation(e : ResultEvent) : void {
setList(e.message.body);
}
...
大部分方法都將任務(wù)委派到服務(wù)的其中一個(gè)操作。所有這些操作都不會(huì)阻塞其它操作,同時(shí)它們都是異步操作。服務(wù)的返回值無論什么時(shí)候都會(huì)由注冊的事件處理 器(eventhandler,本例中為setListFromInvocation)來處理,這個(gè)處理器同時(shí)還會(huì)更新列表。由于返回值在很多地方都會(huì)用 到,“getOperation”就顯得有點(diǎn)特別。CallbackResponder只有注冊了調(diào)用才能得到該調(diào)用的返回值。答復(fù)方也將調(diào)用一個(gè) Function來處理剛接收到的消息的內(nèi)容。
import mx.rpc.IResponder;
使用通用的類包
import mx.rpc.events.ResultEvent;
public class CallbackResponder implements IResponder {
private var callback : Function;
function CallbackResponder(callback : Function) {
this .callback = callback;
}
public function result(data : Object) : void {
callback(ResultEvent(data).message.body);
}
public function fault(info : Object) : void {
DomainInstancesManager.defaultFault(info);
}
}
怎樣使用這個(gè)通用的類包呢?我們來看一個(gè)例子,這個(gè)例子中我們要實(shí)現(xiàn)的是在第二個(gè)解決方案中提到的管理User對(duì)象的實(shí)例。下面這段MXML代碼定義了 一個(gè)PopUpDialog,這個(gè)PopUpDialog可以用來編輯系統(tǒng)中Users的詳細(xì)信息。這個(gè)對(duì)話框的外觀就如左圖所示。實(shí)例變量 “manager”為User領(lǐng)域類型初始為一個(gè)DomainInstanceManager實(shí)例。界面中包含了所有捆綁到這個(gè)manager的list 屬性的用戶的列表。它顯示了用戶的displayName值。
<mx:TitleWindow xmlns:mx="//www.adobe.com/2006/mxml" xmlns:users="users.*" title="User Manager">
<mx:Script>
<![CDATA[
import crud.DomainInstancesManager;
import mx.managers.PopUpManager;
[Bindable]
private var manager : DomainInstancesManager = new DomainInstancesManager("User", "tpc");
private function resetForm() : void {
selectedUser = new User();
secondPasswordInput.text = "";
}
private function setSelected(o : Object) : void
{
selectedUser = User(o);
secondPasswordInput.text = selectedUser.password;
}
]]>
</mx:Script>
<users:User id="selectedUser"
displayName="{displayNameInput.text}"
username="{usernameInput.text}"
password="{passwordInput.text}"/>
<mx:List height="100%" width="200" dataProvider="{manager.list}" labelField="displayName"
itemClick="manager.get(User(event.currentTarget.selectedItem).id, setSelected)"/>
<mx:VBox height="100%" horizontalAlign="right">
<mx:Form>
<mx:FormItem label="Display Name">
<mx:TextInput id="displayNameInput" text="{selectedUser.displayName}"/>
</mx:FormItem>
<mx:FormItem label="User Name">
<mx:TextInput id="usernameInput" text="{selectedUser.username}"/>
</mx:FormItem>
<mx:FormItem label="Password">
<mx:TextInput id="passwordInput" text="{selectedUser.password}" displayAsPassword="true"/>
</mx:FormItem>
<mx:FormItem label="Password">
<mx:TextInput id="secondPasswordInput" text="" displayAsPassword="true"/>
</mx:FormItem>
</mx:Form>
<mx:HBox width="100%">
<mx:Button label="New User" click="{resetForm()}"/>
<mx:Button label="Update User" click="{manager.update(selectedUser);resetForm()}"/>
<mx:Button label="Remove User" click="{manager.remove(selectedUser);resetForm()}"/>
</mx:HBox>
<mx:Button label="Close" click="PopUpManager.removePopUp(this)"/>
</mx:VBox>
</mx:TitleWindow>
一旦點(diǎn)擊列表中的數(shù)據(jù)項(xiàng),你就可以從服務(wù)端讀取對(duì)應(yīng)的user對(duì)象的數(shù)據(jù),這些數(shù)據(jù)存儲(chǔ)在界面的“ selectedUser”中。這個(gè)屬性在MXML中定義,因此很容易用來與表單中的域綁定。 “selectedUser”屬性的屬性和表單中的input域是雙向綁定,所以“selectedUser”屬性值的改變(由服務(wù)器端的事件引發(fā)的修 改)會(huì)影響到input域,而input域的值的改變(由用戶輸入值所引發(fā)的修改)也會(huì)影響到“selectedUser”屬性值。界面上的按鈕是鏈接到 manager的方法,這個(gè)方法的參數(shù)就是“selectedUser”屬性值。方法調(diào)用的結(jié)果會(huì)影響到manager維護(hù)的表單,也會(huì)影響到界面上顯示 的列表內(nèi)容,因?yàn)檫@兩者也是互相綁定的。
注意事項(xiàng)
需要注意的是,在使用這個(gè)通用類庫的時(shí)候,你需要在客戶端維護(hù)一個(gè)包含了系統(tǒng)所識(shí)別的某個(gè)特定類型的所有對(duì)象的列 表。有些你所期望使用的引用數(shù)據(jù)和數(shù)據(jù)本身可能會(huì)在實(shí)例的數(shù)量上有一定的限制,這沒什么問題。另外還有一些數(shù)據(jù)你可能不是必須的,甚至不可能維護(hù)一個(gè)完整 的列表,這時(shí)候你可以在這個(gè)完整的表單的子集上采用同樣的原理。
有趣的是,無論客戶何時(shí)修改數(shù)據(jù)(無論是保存、更新或是刪除領(lǐng)域?qū)?象),他都會(huì)得到一個(gè)包含了新的列表的回復(fù)。他還會(huì)接收到一個(gè)消息表明其他用戶也都收到了更新的列表。因此,用戶會(huì)因?yàn)樽约旱拿總€(gè)修改而收到兩條更新消 息。第一條(針對(duì)他請求的回復(fù))可以被丟棄,但這條消息會(huì)添加到系統(tǒng)中,因?yàn)橹苯踊貜?fù)常常比通過JMS發(fā)送消息更費(fèi)時(shí)間。
另外一個(gè)值得 提及的是,由于消息中包含的更新(本例中就是完整的列表)來自于不同的信道,這個(gè)模型中可能存在并發(fā)問題。消息有可能被延遲,用戶可能在收到一個(gè)更新消息 之后再收到收到上一個(gè)更新的消息。這也意味著用戶看到的是失去實(shí)效的數(shù)據(jù)。解決這個(gè)問題的一個(gè)方法是在消息中添加一個(gè)序列號(hào),然后在每次接收消息的時(shí)候通 過檢驗(yàn)這個(gè)序列號(hào)來查看這條信息是否是最新的。
結(jié)論
通用的類包以易于使用的形式包含了前面幾章中討論的解決方案。
本文中提到的解決方案能夠?yàn)殚_發(fā)使用Flex和Grails的JEE應(yīng)用程序提供堅(jiān)固的基礎(chǔ)。采用這個(gè)工具箱的JEE開發(fā)人員的開發(fā)將可以更快、更敏捷,也許更重要的是開發(fā)將變得更有趣!
關(guān)于作者
Maarten Winkels是具有五年Java和JEE開發(fā)經(jīng)驗(yàn)的軟件開發(fā)工程師和
本站文章除注明轉(zhuǎn)載外,均為本站原創(chuàng)或翻譯。歡迎任何形式的轉(zhuǎn)載,但請務(wù)必注明出處、不得修改原文相關(guān)鏈接,如果存在內(nèi)容上的異議請郵件反饋至chenjj@fc6vip.cn
文章轉(zhuǎn)載自:infoq