天天看點

使用Grails和Flex開發JEE應用

作者 Maarten Winkels譯者 沙曉蘭  釋出于 2009年2月1日 下午8時6分

社群 Java

主題 RIA,富用戶端/桌面

标簽  Flex,AJAX,Grails

Java平台已經逐漸發展為一個成熟可靠的企業應用平台,成熟的應用平台的一個标志則是它能夠帶動大量的衍生技術以及可以與其他技術內建的選項。本文将詳細講述怎樣用Grails這項傳統JEE應用開發的衍生技術,結合另一項完全不同但卻可以在Java中使用的Flex技術來開發JEE。這兩個平台都能大幅度提高開發效率。兩者相結合則在為J2EE應用建立富用戶端的同時不影響整體的開發效率。

Grails的前身是一個在JVM中運作的web應用,它使用Groovy以及其它幾個著名的架構,比如Spring和Hibernate。為了實作快速應用開發,它極為依賴“Convention over Configuration”原則。Groovy擁有很多動态的特性,在定義元件間共同行為方面,功能非常強大。Grails采用plug-in構架,是以很容易把它與其他架構內建,而且也很容易在應用間複用各自的功能。

Flex是個RIA開發套件,由它建立的SWF應用隻能在FlashPlayer下應用。這是Adobe(前身為MacroMedia)的一個新型 Flash開發套件。除了擁有豐富的widget和把各種widget粘合在一起的強大的語言之外,它還能提供一些高端通信解決方案,分布式應用程式的開發是以變得相當容易。它使用兩種文法:MXML和ActionScript。MXML建立在XML文法之上,專門用來定義通用元件的使用者接口;而 ActionScript則用來定義元件之間的動态互動。

Grails和Flex的內建——難題所在

要把Grails和Flex這兩個建立在完全不同基礎上的架構結合起來,首先會遇到諸多通信方面的問題:

  1. 一個架構中的元件如何才能在另一個架構中找到正确的通信對象?

    從本質上來說,Grails實際是運作在伺服器的JVM上的一個web應用架構。Flex則是擁有用戶端和(瘦)伺服器元件的RIA平台,伺服器元件以web應用的方式部署。是以,這兩個架構之間的內建實際上在web應用容器内進行。

    使用者在Flex UI發起的通信必須通過Grails元件來調用業務邏輯。那麼,Flex UI元件該如何找到正确的Grails元件呢?

  2. 架構間如何解析彼此的資料?

    Flex采用ActionScript來描述資料,而Grails則采用Java和Groovy對象。Flex UI向伺服器發送的ActionScript對象應該被轉述為應用程式能夠了解的資料結構。這該如何實作?

  3. 某個使用者的修改該如何與該應用程式的其他使用者互動?

    這是多使用者應用程式普遍存在的問題,但同時運用兩個不同的架構使得問題更加複雜。難點在于Grails應用程式,使用者通過Flex UI來啟動這個應用,但如何通過Flex UI與其他使用者通信,讓他們知道該使用者的這一動作呢?

在接下來的三個部分中,我們詳細讨論上文提到的三個問題,尋找采用Grails和Flex的解決方案。

內建——尋找消息接收對象

一個架構中的元件如何才能在另一個架構中找到正确的通信對象呢?

具體到Grails和Flex的話,這個問題其實就是在問Flex元件怎樣才能找到正确的Grails元件,進而發送請求資料,或者以使用者的名義執行一些操作。為了更好的了解解決這個難點的方法,我們首先來了解一下Flex的通信子系統。

Flex中的客戶——伺服器通信

Flex的通信子系統可以分為客戶和伺服器兩個部分。客戶部分包含了那些允許應用程式發送或者接受消息的元件,比如RemoteObject和 Consumer元件。這些元件與伺服器部分特定的“服務”對象相關聯,比如RemotingService和MessagingService。客戶元件及其相關聯的伺服器元件的結合能夠支援典型的通信模式。比方說結合Consumer、Producers和MessagingService,應用軟體就能夠使用Publish-Subscribe機制來通信。

使用Grails和Flex開發JEE應用

客戶和伺服器件的通信通過信道(Channel)來完成。信道的實作方式并不唯一,所有信道中最重要的是AMFChannel和 RTMPChannel。 AMFChannel建立在HTTP基礎上,也就是說建立在請求-回複的構架上。它可以和MessagingService同時使用,進而支援 Publish-Subscribe構架。這種結合下,信道定期從釋出中讀取新的消息,生成請求。RTMPChannel在這樣的配置下效率更高,它能夠在TCP/IP的基礎上支援客戶與伺服器間的連接配接。這樣一來,客戶與伺服器之間能夠立即發送或接受消息。遺憾的是,Adobe免費開源的Flex實作—— BlazeDS不包含這樣的RTMPChannel實作。

Flex中最重要的通信基礎設施是Destinations。Destination是通信信道的伺服器端終點。一個服務提供一個 Destination,而客戶元件則通過這個Destination與這個服務相關聯。關聯的客戶元件可以向Destination發送和讀取消息。 Destinations可以由Factories建立。

Grails暴露的遠端接口:服務

如何把Flex複雜的通信設施和Grails結合起來呢?Grails能夠識别幾類對象:域對象、控制器、視圖和服務。Grails中的每個服務都是通過外部通信信道——比如HTTP——展示某些功能或者服務的一個對象。而在Flex中,每個服務則與一個Destination相對應。

這恰恰就是針對Grails的flex-plugin所提供的解決方案。Grails中所有注明向Flex展示的服務都将在Flex架構中以Destination的形式注冊。Grails通過一個特定的 Factory把注明的服務添加到Flex中特别配置的RemotingService。這個特定的Factory會在Grails使用的Spring上下文中定位到對應的服務。所有這些配置都可以在services-config.xml中找到,flex-plugin會為Grails将這個檔案複制到正确的地方。

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代碼則是對前面這段配置的應用。RemoteObject的 destination是userService,這個userService正是Grails中目标對象提供的服務名。服務對象的所有方法這下都可以作為遠端操作調用。ActionScript可以将這些操作像一般的方法那樣調用,而方法調用的結果或錯誤也可以當作一般的ActionScript事件來處理。

...
  <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提供的針對內建的解決方案非常漂亮,易于使用而且幾乎是自動化的。在Convention-over-Configuration概念下,Destinations動态添加到Flex配置的時候使用命名規範。

資料轉換

架構間如何互相轉換對方的資料(本文中就是Java和ActionScript對象轉換的問題)?

這個問題的關鍵之處在于兩架構相交接的地方。Flex包含Java(web伺服器)和ActionScript(用戶端)兩個元件。是以,Grails和Flex之間的邊界就在web伺服器,而這個伺服器在兩個架構内實際上都是Java應用。

使用Grails和Flex開發JEE應用

Flex的Java元件隻關注于與Flex客戶間的通信。基于ActionScript對象的AMF協定就用于這樣的資料通信。伺服器内部的 Java代碼将資料轉換成ActionScript對象,這些對象在信道上實作系列化。Flex支援Java的基本類型,也支援其标準複雜類型(比如 Date或者 Collection類型)。由于ActionScript是門動态語言,是以它也支援随機對象結構。Java對象域會轉換成ActionScript對象的動态屬性。但把這些非類型ActionScript對象轉換成Groovy域對象的過程則沒那麼直接,它會預設生成一個Map,将屬性以key- Value對的形式存儲到這個Map中。

Flex建立與Groovy域對象擁有同樣屬性的ActionScript類,通過注解将兩者互相關聯起來,這樣一來,資料轉換更加友善。下面的這個例子就是這樣一對關聯的Groovy-ActionScript。

Groovy ActionScript
class User implements Serializable {
    String username
    String password
    String displayName
}      
[RemoteClass(alias="User")]
public class User {
  public var id:*
  public var version:*
  public var username:String;
  public var password:String = "";
  public var displayName:String;


  public function toString():String {
    return displayName;
  } 
}      

注解“RemoteClass”将ActionScript類連結到由alias屬性指明的Java(或Groovy)類。alias這個屬性應該包含完整的類名。Grails中的領域類通常都添加到預設的類包。Grails類中的所有屬性都會複制到ActionScript類。這些屬性的名字都應當完全一樣。Grails會為所有需要“id”和“version”的領域對象動态添加這兩個屬性,領域對象是以可以在與用戶端互動的時候保留這兩個資訊。

結論

Flex提供的與Java(或Groovy)間資料轉換的解決方案會導緻很多重複的代碼。每個領域類都會被定義兩次,一次用Groovy(或 Java)定義,另一次用ActionScript。但是這樣一來,可以添加一些用戶端特定代碼,比如說那些單單用ActionScript編寫的控制對象顯示的代碼。這也推動編輯器同時提供兩種代碼的自動完成功能。至于用于配置的注解則非常簡便。

多使用者

應用程式如何将某個使用者所作的修改通知到其他使用者?

對于一個能同時支援多使用者的應用程式來說,将某個使用者對共享資料所做的修改通知到其他使用者着實是個挑戰。對于其他使用者來說,這個過程可以看作是有伺服器發起的通信。

單個中央結點(通常指伺服器)向很多接收點(通常指客戶)發起通信的時候,釋出-注冊(publish-subscribe)就非常實用。客戶在伺服器上注冊之後,伺服器上任何相關消息的釋出,他們都會收到通知。

由于Grails可以使用Java,自然就可以用到JMS。JMS是應用程式間通信的Java标準,它支援publish-subscribe技術,而且應用程式也可以通過擴充卡來內建JMS。

Grails中的JMS配置

在衆多标準中,有一個特别針對Grails的jms-plugin,它添加了很多有用的方法可以用來向JMS目的地對象、向所有的控制器和服務類發送消息。在上一章中提到的UserService就可以運用這些方法在資料發生變化時通過JMS向所有的使用者發送更新消息。

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);
    }
  }
}      

服務可以決定什麼時候發送什麼樣的消息。無論使用者什麼時候更新或删除資料,都會發送一條包含了完整的資料清單的消息。這條消息會發送到特定話題,也就是這裡的“tpc”。任何注冊了這個話題的使用者都将接收到新資料。清單中的對象類型(本例中也就是“User”)作為中繼資料添加到消息中,接收對象是以在伺服器上注冊的時候特别指明他們所關注的資料類型。

為了讓Grails應用也能夠采用JMS,每個JMS都需要實作provider。Apache有個免費的開源實作,隻需簡單配置就能在 Grails應用程式中使用。你所需要做的是把ApacheMQ類庫添加到Grails應用的lib檔案夾下,再将下列代碼段複制到connection factory所使用的conf/spring檔案夾下的resources.xml中。

...
  <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中接收JMS消息

目前的Flex配置僅僅包含一個RemotingService,依靠它來支援request-response類型的使用者與 UserService間互動。這個服務由flex-plugin向Grails中添加。除此之外,我們還需要一個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檔案中,我們需要添加下列這段包含了一個新的MessagingService和JMSAdapter的代碼段。添加的這個擴充卡将服務中的destination連結到JMS資源。這個服務中還包含一個destination的配置,flex代碼中的使用者可以通過注冊獲得這個destination的資料更新。Destination中含有很多JMS特定的配置。大部分都是常用的JMS屬性。 initial- context-environment中的“topic.tpc”屬性是個可定制的ActiveMQ屬性,這個屬性将在上下文中注冊一個JNDI名為 “tpc”的話題。

...
  <mx:Consumer destination="tpc" selector="type = 'User'"
    message="setList(event.message.body)"/>
...      

Flex用戶端代碼非常簡單。消息根據選擇器(selector)被發送到特定的destination,而Consumer元件是以接受到所對應的 destination中的消息。在這個例子中,我們通過篩選器指定Consumer所關注的消息是中繼資料“type”屬性值為“User”的内容。無論消息是何時收到的,消息的内容,也就是User-objects清單會被置為可顯示的内部清單。消息内容的處理和RemoteObject上“all”處理的傳回值完全一樣。

結論

Grails和Flex中将資料變化傳遞給多使用者的解決方案完全可以通過标準元件來實作。涉及到的元件數量很多,配置和實作是以相當複雜。如果配置正确的話,這個解決方案使用起來就非常友善。

合并解決方案

回顧前三章提出的解決方案,你會發現還可以把他們合并起來得到一個更為通用的解決方案來實作Flex/Grails應用程式客戶與伺服器間的關于領域狀态資訊的通信。本章節中,我們要讨論的就是這樣一個更為通用的解決方案。

泛化伺服器端代碼

問題1和3的解決方案所需要的伺服器端的代碼可以合并到同一個Groovy服務中。我們将把它指明為針對User領域類的服務。通過Groovy這樣一門動态語言,要把這樣一個服務泛化到面向所有領域類的操作非常容易。

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);
    }
  }
}      

要實作這個目的的關鍵在于讓客戶來決定傳回的領域類型。出于這個目的,我們需要為所有服務引入一個參數,通過這個參數為伺服器鑒定各個領域類型。很明顯,對于這個參數來說,領域類型的類名是無非是最好的選擇。為所有領域對象提供C(reate)R(etrieve)U(pdate)D(elete) 操作的服務被稱為CrudService。

一旦有任何資料更改,CrudService都會向JMS話題發送更新消息。這個更新消息包含了應用程式所知道的完整的領域對象清單。為了讓使用者來決定這是否是自己心儀的更新内容,領域類型的類名将以中繼資料方式添加到消息中。

用戶端代碼

解決方案1和3中的用戶端ActionScript代碼也可以綜合到同一個類中。這個類的執行個體可以用來管理用戶端某個特定領域類型的所有執行個體集。

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);
  }
  ...
}
      

實作用戶端的ActionScript基本上包含兩個元件:簡化request-response對話的RemoteObject和用于 producer-subscriber對話的Consumer元件。上一章節中,這些對象通過MXML代碼實作初始化,但他們也可以通過 ActionScript來建立。上面這段代碼段顯示的是這兩個元件共同使用的結構:包含了執行個體和錯誤處理的清單。包含執行個體的清單根據任何一個通信元件發送的消息而更新。

...
  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來建構,這段代碼用來接收伺服器端發送的消息。Consumer的 selector屬性僅僅用來接收那些包括了中繼資料中所指明的領域類型的消息。無論什麼時候接收到這樣的消息,event handler都會被調用,并且清單也會得到更新。

接下來這段代碼段将RemoteObject設定為request-response型通信的一個結點。所有必要的操作都作為操作屬性而添加到RemoteObject上,客戶因而很容易調用這些操作。

...
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);
}
...      

大部分方法都将任務委派到服務的其中一個操作。所有這些操作都不會阻塞其它操作,同時它們都是異步操作。服務的傳回值無論什麼時候都會由注冊的事件處理器(eventhandler,本例中為setListFromInvocation)來處理,這個處理器同時還會更新清單。由于傳回值在很多地方都會用到,“getOperation”就顯得有點特别。CallbackResponder隻有注冊了調用才能得到該調用的傳回值。答複方也将調用一個 Function來處理剛接收到的消息的内容。

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);
  }
}      

使用通用的類包

怎樣使用這個通用的類包呢?我們來看一個例子,這個例子中我們要實作的是在第二個解決方案中提到的管理User對象的執行個體。下面這段MXML代碼定義了一個PopUpDialog,這個PopUpDialog可以用來編輯系統中Users的詳細資訊。這個對話框的外觀就如左圖所示。執行個體變量 “manager”為User領域類型初始為一個DomainInstanceManager執行個體。界面中包含了所有捆綁到這個manager的list 屬性的使用者的清單。它顯示了使用者的displayName值。

<mx:TitleWindow xmlns:mx="http://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>      

一旦點選清單中的資料項,你就可以從服務端讀取對應的user對象的資料,這些資料存儲在界面的“ selectedUser”中。這個屬性在MXML中定義,是以很容易用來與表單中的域綁定。 “selectedUser”屬性的屬性和表單中的input域是雙向綁定,是以“selectedUser”屬性值的改變(由伺服器端的事件引發的修改)會影響到input域,而input域的值的改變(由使用者輸入值所引發的修改)也會影響到“selectedUser”屬性值。界面上的按鈕是連結到 manager的方法,這個方法的參數就是“selectedUser”屬性值。方法調用的結果會影響到manager維護的表單,也會影響到界面上顯示的清單内容,因為這兩者也是互相綁定的。

使用Grails和Flex開發JEE應用

注意事項

需要注意的是,在使用這個通用類庫的時候,你需要在用戶端維護一個包含了系統所識别的某個特定類型的所有對象的清單。有些你所期望使用的引用資料和資料本身可能會在執行個體的數量上有一定的限制,這沒什麼問題。另外還有一些資料你可能不是必須的,甚至不可能維護一個完整的清單,這時候你可以在這個完整的表單的子集上采用同樣的原理。

有趣的是,無論客戶何時修改資料(無論是儲存、更新或是删除領域對象),他都會得到一個包含了新的清單的回複。他還會接收到一個消息表明其他使用者也都收到了更新的清單。是以,使用者會因為自己的每個修改而收到兩條更新消息。第一條(針對他請求的回複)可以被丢棄,但這條消息會添加到系統中,因為直接回複常常比通過JMS發送消息更費時間。

另外一個值得提及的是,由于消息中包含的更新(本例中就是完整的清單)來自于不同的信道,這個模型中可能存在并發問題。消息有可能被延遲,使用者可能在收到一個更新消息之後再收到收到上一個更新的消息。這也意味着使用者看到的是失去實效的資料。解決這個問題的一個方法是在消息中添加一個序列号,然後在每次接收消息的時候通過檢驗這個序列号來檢視這條資訊是否是最新的。

結論

通用的類包以易于使用的形式包含了前面幾章中讨論的解決方案。

本文中提到的解決方案能夠為開發使用Flex和Grails的JEE應用程式提供堅固的基礎。采用這個工具箱的JEE開發人員的開發将可以更快、更靈活,也許更重要的是開發将變得更有趣!

關于作者

Maarten Winkels是具有五年Java和JEE開發經驗的軟體開發工程師和咨詢師。他最近從荷蘭搬遷到印度,宣傳Xebia所提供的分布式靈活開發過程。 Xebia是一家專于Java技術、海外Agile項目、Agile咨詢和教育訓練、IT構架和稽核的公司。請參考:http://www.xebia.com/。

閱讀英文原文:Writing JEE applications with Grails and Flex。

繼續閱讀