Blog

Tutorial: Master-Detail-Bildschirm in Flex, unterstützt durch eine Grails-Anwendung

Maarten Winkels

Aktualisiert Oktober 23, 2025
11 Minuten

In diesem Blog zeige ich Ihnen, wie Sie eine einfache Master-Detail-Ansicht in Flex erstellen, wie Sie diese mit einer Grails-Anwendung sichern und wie Sie Änderungen über JMS an alle Flash-Clients veröffentlichen. Wichtige Themen sind die Bindung und der Remote-Aufruf von Java-Objekten in Flex sowie die Konfiguration von JMS. Eine Master-Detail-Ansicht ist eine Ansicht mit einer Masterliste, die eine Sammlung von Elementen zeigt, und einer Detailansicht, die meist aus einem Formular besteht, in dem ein einzelnes Element bearbeitet werden kann. Wenn Sie auf ein Element in der Hauptliste klicken, werden die Details in der Detailansicht angezeigt.

Master-Detail

Die Kombination aus einem Framework für die Erstellung von RIAs (Flex) und einem Java-basierten dynamischen Framework für die Erstellung von Diensten (Grails) scheint sehr vielversprechend zu sein. Ich denke, dass die hohe Produktivität, die durch diese Kombination leicht erreicht werden kann, sie zu einer sehr attraktiven Wahl für Ihr nächstes großes Projekt macht.

In diesem Blog werden wir ein "doppeltes" Masterdetail implementieren, wie in der Abbildung oben zu sehen ist. Eine Person kann mehrere Adressen haben. Die Liste ist ein Adressbuch und die Ansicht zeigt eine Reihe von Attributen der Personen im Adressbuch an (Vorname, Nachname sowie Stadt und Land der ersten Adresse). Wenn ein Eintrag ausgewählt wird, wird er im nebenstehenden Formular angezeigt und die Liste in diesem Formular zeigt alle Adressen dieser Person. Wenn Sie auf eine Adresse klicken, werden ihre Details im Formular unten angezeigt.

Binden

In Flex verwenden wir eine ArrayCollection, die die Liste enthält, und diese Liste wird in einem DataGrid angezeigt.

  

...

Der Mechanismus, der verwendet wird, um die Daten aus dem Adressbuch in das DataGrid zu übertragen, heißt "Bindung". Dies geschieht durch die Verwendung geschweifter Klammern um den Ausdruck dataProvider="{addressBook}". Durch die Bindung wird sichergestellt, dass bei einer Änderung des referenzierten Objekts (in diesem Fall des Adressbuchs oder seines Inhalts) die Änderungen im referenzierten Objekt (in diesem Fall dem DataGrid) wiedergegeben werden. Das Binden funktioniert nur in eine Richtung! Wenn der Wert im Verweiser (DataGrid) geändert wird, wird der gebundene Wert (das Adressbuch) nicht aktualisiert. Einige Komponenten (wie das DataGrid) fügen diese Funktionalität hinzu und aktualisieren das zugrunde liegende Modell. In diesem Tutorial verwenden wir das DataGrid im Modus editable="false", da wir die Daten über das Formular bearbeiten wollen. Die Spalten werden gefüllt, indem wir die als dataField angegebenen Eigenschaften der Objekte im addressBook nachschlagen. Aber was sind diese Objekte? Es sind Actionscript-Objekte vom Typ Person.

Paket
{
  import mx.collections.ArrayCollection;
  [Bindbar]
  public class Person
  {
  public var firstName:String;
  public var lastName:String;
  public var Adressen: ArrayCollection = new ArrayCollection();
  public function get ersteAdresseStadt():String {
  if (adressen.länge == 0) {
  zurückgeben "";
  }
  return adressen[0].stadt;
  }
  public function get firstAddressCountry():String {
  if (adressen.länge == 0) {
  zurückgeben "";
  }
  return adressen[0].land;
  }
  }
}

Diese Klasse definiert zwei einfache Eigenschaften und eine Sammlung. Außerdem definiert sie zwei Getter, an die auch in der DataGrid-Ansicht gebunden wird. Es ist wichtig, diese Klasse als [Bindbar] zu markieren, damit alle Eigenschaften der Klasse in Bindungsausdrücken verwendet werden können. Wenn nun eine Zeile in der Liste ausgewählt wird, müssen wir die Werte im ausgewählten Objekt an Felder in einem Formular binden. Wenn sich dann die Daten im Formular ändern, wollen wir die Werte im ausgewählten Objekt aktualisieren. Daher benötigen wir Double Binding.

...

...

Jetzt müssen wir nur noch einige Schaltflächen und Eventhandler zu den Komponenten hinzufügen...

...

...

...

... und die Master-Detailansicht ist fertig! Die doppelte Bindung sorgt dafür, dass die Felder im Formular, die Felder in der Datentabelle und die Daten im Modell synchron bleiben. Die Master-Detailansicht für die Adresse funktioniert ähnlich und ist ein wenig anders. Anstelle eines DataGrid wird eine Liste verwendet, um die Adressen anzuzeigen. In der Liste wird die Methode Address.toString() aufgerufen, um die Adressen anzuzeigen. Auch die Ereignisbehandlung ist etwas anders: Eine neue Adresse wird sofort zur Liste hinzugefügt, anstatt eine Schaltfläche "Adresse aktualisieren" zu haben.

Fernsteuerung

Die Bearbeitung dieser Daten macht sicherlich Spaß, aber sie sind weder dauerhaft gespeichert noch werden sie von den Benutzern gemeinsam genutzt. Um das zu erreichen, müssen wir eine Serverkomponente und Remoting hinzufügen. Das Remoting aus Flex heraus kann mit BlazeDS von Adobe Labs erfolgen. Dabei handelt es sich um eine "Java-Remoting- und Web-Messaging-Technologie, die es Entwicklern ermöglicht, auf einfache Weise eine Verbindung zu verteilten Backend-Daten herzustellen und Daten in Echtzeit an Adobe® Flex™ zu übertragen". Wir können also eine Webanwendung in Java schreiben, die über BlazeDS mit dem Backend kommuniziert. Anstatt mit dem Schreiben einer web.xml und eines Servlets zu beginnen, lassen Sie uns einen Blick auf Grails werfen. Dabei handelt es sich um ein Framework zur Erstellung von Webanwendungen auf der Grundlage von Java und Groovy und es verfügt über ein Flex-Plugin! Das sieht vielversprechend aus. Zunächst erstellen wir eine neue Grails-Anwendung(grails create-app addresses) und laden das flex-Plugin herunter(grails install-plugin flex). Das Herunterladen dauert einige Zeit und gibt uns daher etwas Zeit zum Nachdenken. Was wollen wir in dieser Webanwendung wirklich tun? Wir brauchen nur Domänenklassen (Person und Adresse), die persistiert werden sollen (das ist Standard bei Grails) und dann einen Dienst, der einige Methoden für das flex-Frontend bereitstellt. Dies laut auszusprechen dauert fast so lange wie es in Grails zu schreiben:

class Person {
  String firstName
  String letzterName
  Adressen auflisten
  static hasMany = [Adressen:Adresse]
  static fetchMode = [Adressen: "eager"]
}

Die Klasse Person hat die gleichen zwei einfachen Eigenschaften und die Liste der Adressen. Die Liste ist als hasMany-Beziehung deklariert und der Abrufmodus ist auf eifrig eingestellt. Wir müssen die Liste der Adressen für jede Person, die wir abrufen, laden, da der Client sonst LazyInitializationExceptions erhält.

Klasse Adresse {
  String Street;
  int Nummer;
  String postalCode;
  String Stadt;
  String Land;
}

Die Klasse Address ist viel einfacher. Sie enthält nur die einfachen Eigenschaften, die auch die Actionscript-Klasse enthält.

class AddressBookService {
  static expose = ['flex-remoting'];
  def Liste findAllPersons() {
  return Person.createCriteria().listDistinct {};
  }
  def Person get(id) {
  return Person.get(id);
  }
  def void update(Person p) {
  p.save();
  }
  def void remove(Person p) {
  p.delete(flush:true);
  }
}

Der AddressBookService stellt sich selbst als Flex-Remoting-Service dar. Dadurch wird sichergestellt, dass das Grails-Flex-Plugin das Objekt bei BlazeDS registriert und es dem Flex-Client zur Verfügung gestellt wird. Die Änderungen am Flex-Code sind ziemlich komplex:

  1. Wir müssen die Actionscript-Klassen mit der Annotation RemoteClass versehen, um anzugeben, welche ActionScript-Klasse für welches Java-Objekt verwendet werden muss.
    [RemoteClass(alias= person=""]
    Hier ist der Alias der Name der Java-Klasse, in diesem Fall die Klasse Person im Standardpaket.
  2. Wir müssen unsere ActionScript-Klassen so ändern, dass sie die generierten Domänenklasseneigenschaften id und version enthalten. Diese Eigenschaften sind für die Persistenz der Daten sehr wichtig, da Hibernate (das von Grails verwendet wird) anhand der Version herausfindet, ob es sich um ein neues oder ein bestehendes persistentes Objekt handelt und anhand der id die Identität des bestehenden Objekts. Wenn wir diese Felder in den Action Script-Klassen weglassen, gehen sie beim Remoting verloren und jedes Objekt sieht für Hibernate wie ein neues Objekt aus. Hibernate wird also für jedes neue Objekt ein Insert durchführen.
      public var id:*;
      public var version:*;
    Der Typ dieser Eigenschaften ist uns im ActionScript-Code egal, also verwenden wir einfach die Platzhalter.
  3. Ein RemoteObject wird eingeführt, zusammen mit etwas Code zur Ereignisbehandlung.
      
    
    
    Das Ziel des RemoteObjects wird direkt auf den Klassennamen unseres Grails-Dienstes abgebildet. Die Methodendeklarationen im Remote-Objekt bilden dessen Methoden ab und der Ergebnis-Event-Handler wird asynchron mit dem Ergebnis des Methodenaufrufs aufgerufen, wenn er vom Flex-Code aus aufgerufen wird.
  4. Die Ereignisbehandlungsmethoden für unsere Schaltflächen müssen geändert werden, um den neuen Dienst zu nutzen. Außerdem wird eine Schaltfläche "Aktualisieren" hinzugefügt, um das komplette Adressbuch vom Server abzurufen.
      
    
    
    Beachten Sie, dass die Behandlung des Ergebnisses eines Aufrufs des Dienstes nicht mit dem aufrufenden Code definiert wird: Sie wird in der Methodendeklaration des RemoteObjects definiert. Da wir den Code bereits mit diesen Änderungen im Hinterkopf strukturiert haben, sind die strukturellen Änderungen ziemlich minimal. Wir müssen nur die Implementierung dieser drei Methoden ändern, damit sie mit dem Grails-Dienst funktionieren.

Um die Anwendung zum Laufen zu bringen, müssen die .mxml- und .as-Dateien in das web-app-Verzeichnis der Grails-Anwendung kopiert werden. Hier kann der Flex Webtier Compiler den Quellcode abholen und die Anwendung auf Anfrage kopieren. Wenn Sie auf xebia.com /blog/ addresses/ addresses.mxml gehen, wird die Schnittstelle angezeigt und über BlazeDS mit der Grails-Anwendung verbunden. Das ist wirklich alles! Diese 6 Quelldateien (addresses.mxml, Person.as, Address.as, AddressBookService.groovy, Person.groovy und Address.groovy) bilden eine vollständige Rich GUI für ein Online-Adressbuch. Keiner der Codes ist wirklich komplex, und es wird kaum Klempnerarbeit benötigt. Obwohl das wirklich toll ist, ärgere ich mich ein wenig über die Schaltfläche "Aktualisieren" und die Tatsache, dass der Benutzer nur Änderungen sieht, die von anderen vorgenommen wurden, wenn er auf diese Schaltfläche klickt. Mal sehen, was wir tun können...

Server-Push über JMS

Flex kann so konfiguriert werden, dass es auf ein JMS-Ziel hört und Grails verfügt über ein JMS-Plugin. Damit können wir einen neuen Datensatz an alle Clients senden, sobald sich Daten im Backend ändern. Wenn wir JMS verwenden wollen, brauchen wir einen JMS-Provider, vorzugsweise einen, der in eine Grails-Anwendung eingebettet werden kann. Beim Stöbern im Internet bin ich auf ActiveMQ von Apache gestoßen, das quelloffen ist und problemlos in eine Spring-Anwendung eingebettet werden kann. Nach der Installation des JMS-Plugins, dem Herunterladen von ActiveMQ und dem Hinzufügen der erforderlichen Bibliotheken zum lib-Ordner der Grails-Anwendung ist alles bereit.

  1. Wir müssen eine connectionFactory-Bean in Spring konfigurieren.
     
    
     vm://localhost
    
    
    Dadurch wird die Connection Factory erstellt und außerdem ein eingebetteter Broker gestartet.
  2. Wir müssen die Aktualisierungen an ein Thema senden, sobald eine Änderung vorgenommen wird. Dies geschieht über den AddressBookService.
      def void update(Person p) {
      p.save();
      sendUpdate();
      }
      def void remove(Person p) {
      p.delete(flush:true);
      sendUpdate();
      }
      def private void sendUpdate() {
      versuchen {
      sendPubSubJMSMessage("Adressen", findAllPersons());
      } catch (Exception e) {
      log.error("Das Senden von Updates ist fehlgeschlagen.", e);
      }
      }
    Die Methode sendPubSubJMSMessage (die durch das Grails JMS-Plugin zu allen Serviceklassen hinzugefügt wurde) wird verwendet, um eine Nachricht an das Thema "Adressen" zu senden. Die Nachricht enthält die neue Liste der Adressen.
  3. BlazeDS kann so konfiguriert werden, dass ein JMS-Ziel in JNDI gesucht wird. Die services-config.xml muss geändert werden, damit sie den JmsAdapter und das neue Ziel enthält, das Flex-Clients abonnieren können.
        
    
      javax.jms.ObjectMessage
      ConnectionFactory
      Adressen
      NON_PERSISTENT
      STANDARD_PRIORITÄT
      AUTO_QUITTIEREN
      false
    
      Kontext.PROVIDER_URL
      vm://localhost
    
      Context.INITIAL_CONTEXT_FACTORY
      org.apache.activemq.jndi.ActiveMQInitialContextFactory
    
      Thema.Adressen
      Adressen
    
    
    Die Konfiguration des JNDI-Kontextes für Flex ist tatsächlich ziemlich kompliziert. Die org.apache.activemq.jndi.ActiveMQInitialContextFactory ist ein sehr einfacher JNDI-Kontext und verwendet Eigenschaften zur Registrierung von Warteschlangen und Themen. Normalerweise befinden sich die Eigenschaften in einer jndi.properties-Datei. Hier werden diese Eigenschaften in der Datei service-config.xml selbst angegeben. Die Eigenschaft topic.addresses</span wird auf addresses gesetzt, um das Topic mit dem physischen Namen "addresses" (dem Eigenschaftswert) in JNDI als "addresses" (dem Eigenschaftsschlüssel-suffix) zu registrieren. Dies ist wirklich eine ActiveMQ JNDI Eigenart.
  4. Jetzt kann der Flex-Client so geändert werden, dass er auf das Thema hört
    ...
    
    ...
    Der Verbraucher ist so konfiguriert, dass er auf das "updatesJmsTopic" hört, das in der services-config.xml konfiguriert ist. Immer wenn eine Nachricht eingeht, wird ihr Inhalt von der Methode setAddressBook verarbeitet, die alle Daten in die ArrayCollection kopiert, die im DataGrid angezeigt werden soll. Vergessen Sie nicht, den Verbraucher beim Start zu abonnieren. Wir laden auch die ersten Daten beim Start.

Damit ist unsere RIA-Anwendung fertiggestellt. Die Aktualisierungen werden nun über JMS an alle Flex-Clients gesendet.

Fazit

Die Erstellung einer RIA mit Flex und Grails ist relativ einfach. Die Entwickler können sich ganz auf die Entwicklung der grafischen Benutzeroberfläche oder der serverseitigen Geschäftslogik konzentrieren. Die meisten Installationen können auf den sinnvollen Standardeinstellungen belassen werden und wenn nicht, können Änderungen mit "Standard"-Frameworks wie Spring oder Hibernate vorgenommen werden. Wenn eine fortgeschrittene Funktion erforderlich ist, können die Entwickler auf ihre Java-Kenntnisse oder Frameworks wie Spring und Hibernate zurückgreifen, um die Funktionalität schnell in die Grails-Anwendung zu integrieren. Ich denke, diese Eigenschaften machen die Flex/Grails-Kombination zu einem sehr geeigneten Kandidaten für die Entwicklung von RIAs in Projekten aller Größenordnungen. Ein paar Anmerkungen:

  1. Die Grails-Domain-Klassen und die ActionScript-Klassen zu haben, erscheint wie eine Überschneidung. Auf der Website des Grails Flex-Plugins wird eine Funktion angedeutet, mit der die ActionScript-Klassen on the fly generiert werden können. Andererseits könnte es nützlich sein, die beiden zu trennen, da wir in einigen Fällen clientseitige Geschäftslogik (wie die Eigenschaften firstAddressCity und firstAddressCountry) auf den clientseitigen Domain-Objekten benötigen. Allerdings ist diese Duplizierung etwas brüchig und Fehler (wie Tippfehler in Feldnamen) sind leicht möglich.
  2. Derzeit verwendet die Anwendung einen AMF-Kanal und Polling, um die Aktualisierungen an die Clients zu senden. In Flex Data Services 2 könnte ein anderer Kanaltyp (RTMPChannel) verwendet werden, um Daten vom Server an die Clients zu senden. Leider enthält BlazeDS diese Funktionalität nicht.

Die Quellen dieser Anwendung können heruntergeladen werden:

Das Paket mit der kompletten Grails-Anwendung mit den Plugins war fast 60 MB groß. Um die Anwendung zu testen, müssen Sie also eine Grails-Anwendung erstellen und die Plugins manuell hinzufügen.

Verfasst von

Maarten Winkels

Contact

Let’s discuss how we can support your journey.