JAVA exPress > Archive > Issue 8 (2010-09-03) > Architektura aplikacji Flex i JAVA

Architektura aplikacji Flex i JAVA

Platforma Java EE jest wiodącym rozwiązaniem dla technologii webowych. Natomiast Adobe Flash jest wiodącym rozwiązaniem wśród technologii RIA (Rich Internet Application). Dzięki użyciu obu tych technologii, twórcy aplikacji są w stanie dostarczyć atrakcyjne wizualnie, zorientowane na dane (ang. data centric) aplikacje, które łączą w sobie zalety stabilnej i wydajnej części serwerowej z zaletami intuicyjnego interfejsu użytkownika znanego z wielu stron www.

Poniższy artykuł omawia architekturę aplikacji opartej o połączenie technologii Flex i Java, skupiając się na przedstawieniu:

  • architektury klient-serwer,
  • różnych sposobów komunikacji między klientem i serwerem,
  • zaletach i sposobach użycia Flash Remotingu,
  • sposobie zapewnienia bezpieczeństwa aplikacji Flex,
  • budowie aplikacji z użyciem zdarzeń, stanów, komponentów MXML i modułów (ang. Flex modules)

Dla lepszego zrozumienia tych zagadnień warto zapoznać się z materiałem video przygotowanym przez Adobe:  Introduction to Flex 4 and Java integration.

Dla osób, które chcą dodatkowo zgłębić temat omawianych technologii przeznaczony jest artykuł:  The technologies for building Flex and Java applications.

Architektura klient/serwer

Aplikacje Flex i Java wykorzystują architekturę trójwarstwową, w której warstwą prezentacji jest aplikacja Flex, warstwą biznesową jest działający na serwerze kod Java, a warstwą przechowywania danych jest baza danych. Sposób tworzenia kodu części serwerowej aplikacji nie odbiega od sposobu w jaki zwykle tworzy się aplikacje w języku Java. Obejmuje on projektowanie struktury obiektów, bazy danych, obiektów mapujących bazą danych (np. Hibernate lub EJB 3) oraz logikę biznesową operującą na stworzonych strukturach. Dostęp do całości aplikacji zapewnia warstwa prezentacji (Flex) poprzez protokół HTTP. Za sprawne przetwarzanie, przesyłanie i utrwalanie wprowadzonych przez użytkownika danych odpowiada logika zawarta w kodzie Java.

Typowe aplikacje HTML składają się ze zbioru stron między którymi użytkownik może swobodnie nawigować, jednak informacje o danych i stanach nie są przechowywane pomiędzy kolejnymi wywoływania tej samej strony. Aplikacje Flex, wprost przeciwnie, są z natury stanowe (ang. stateful). Flexowa aplikacja jest osadzona w kodzie strony HTML, gdzie Flash Player generuje kolejne widoki aplikacji bez przechodzenia oraz odświeżania strony w której aplikacja została osadzona. Generowanie widoków następuje dynamicznie z jednoczesną asynchroniczną wymianą danych między warstwą widoku a warstwą logiki biznesowej (patrz Rysunek 1) (podobnie jak to się odbywa w przypadku funkcjonalności XMLHttpRequest API w JavaScript).

Rys. 1: Architektura klient/serwer

Komunikacja klient-serwer

Aplikacje Flex mogą wymieniać dane z warstwą logiki biznesowej przy użyciu socketów lub, co częstsze, za pomocą protokołu HTTP. Framework Flex udostępnia trzy rodzaje zdalnych wywołań procedur (RPC) po HTTP do komunikacji z serwerem: HTTPService, WebService i RemoteObject. Wszystkie te procedury opakowują połączenie HTTP nawiązane przez Flash Playera, które to natomiast korzysta z bibliotek zawartych w przeglądarce. Żadna z tych metod komunikacji nie umożliwia aplikacji Flex bezpośredniego sięgnięcia do zdalnej bazy danych.

HTTPService umożliwia wysyłanie żądań HTTP do plików JSP, XML, usług RESTful web serwisu lub do innych usług na serwerze, które zwrócą odpowiedź po HTTP. Chcąc użyć HTTPService musimy zdefiniować URL do klasy endpointu, funkcje nasłuchujące (ang. listener functions) - przeznaczone do asynchronicznego przetwarzania rezultatów wywołań HTTPService - oraz typ zwracanych danych (określający typ danych na który ma zostać przetłumaczona odpowiedz na wywołanie przesłana do aplikacji Flex). Możliwe jest przypisanie odpowiedzi, otrzymywanej w postaci tekstowej, do zmiennej typu String, konwersja do XML, E4X lub zwyczajnego obiektu ActionScript .W sytuacji gdy odpowiedź jest w formacie JSON można zastosować do deserializacji zewnętrzną biblotekę Adobe Flex corelib, która zapewni przekształcanie obiektów JSON w obiekty ActionScriptowe. Do wywołania serwisu webowego opartego o SOAP możemy użyć HTTPService API lub bardziej specializowanego WebService API, który to automatycznie dokonuje serializacji i deserializacji pomiędzy tekstowym komunikatem protokołu SOAP a obiektami ActionScript.

Trzecią możliwością zdalnych wywołań procedur (RPC) jest RemoteObject API. Wykonuje ono wywołanie Flash Remoting do klasy Javowej znajdującej się po stronie serwera, otrzymując za pośrednictwem HTTP binarny strumień Action Message Format. Jeśli to tylko możliwe, wydajniej jest stosować Flash Remoting, który dzięki binarnemu formatowi przesyłania danych umożliwia do 10 razy szybszy transfer niż "rozgadane" formaty tekstowe takie jak JSON lub SOAP (patrz Rysunek 2). Porównanie AMF z technologiami opartymi o tekstowy format można przeczytać na stronie James Ward's Census RIA Benchmark application.

Rysunek 2. Metody łączenia Flexa i Javy.

Flash Remoting

Flash Remoting to połączenie funkcjonalności zlokalizowanej po stronie klienta i po stronie serwera, które tworzy mechanizm dający możliwość pracy na obiektach zlokalizowanych po stronie serwera w taki sposób jakby były one obiektami lokalnymi platformy Flash. Remoting zapewnia transparentny transfer danych pomiędzy ActionScript, a danymi na serwerze, wykonując serializacje danych do Action Message Format (AMF), deserializacje oraz konwersje pomiędzy typami po stronie klienta i serwera.

Flash Remoting wykorzystuje mechanizmy wbudowane w Flash Player oraz mechanizmy po stronie serwera wbudowane w niektóre serwery (takie jak ColdFusion i Zend) lub moduły, które możemy doinstalowywać do większości popularnych serwerów (dla platformy Java EE są to BlazeDS i LiveCycle Data Services, dla .NET WebORB for .NET lub FluorineFX, dla PHP Zend framework lub amfphp i wiele innych). Więcej informacji o BlazeDS i LiveCycle Data Services można znaleźć w artykule The technologies for building Flex and Java applications.

BlazeDS i LiveCycle Data Services dostarczają mechanizmy do komunikacji po obu stronach kanału komunikacyjnego. Po stronie klienckiej oba te rozwiązania używają framework oparty o komunikaty (ang. message-based framework), który zapewnia interakcje z serwerem. Framework po stronie klienta wystawia kanały komunikacyjne, które opakowują połączenie pomiędzy Flexem a serwerową częścią BlaseDS lub LiveCycle Data Services. Kanały są grupowane w zbiory, które odpowiadają za nawiązywanie połączeń i obsługę błędów. Cześć serwerowa zawarta jest w aplikacji webowej J2EE. Aplikacja kliencka Flex wysyła żądanie za pośrednictwem kanału komunikacyjnego, które jest przekierowywane do endpointu zlokalizowanego w serwerze BlaseDS lub LiveCycle DS. Z endpointu żądanie jest przesyłane przez szereg obiektów Java, poczynając od obiektu MessageBroker, poprzez obiekt serwisu i obiekt docelowy(ang. destination object), a kończąc na obiekcie adaptera. Obiekty docelowe udostępniają serwisy Remoting, Proxying i Messaging, a LiveCycle DS dodatkowo serwis Data Management. Realizacja żądania następuje w adapterze (patrz Rysunek 3).

Rysunek 3. Architektura Flash Remoting.

AMF

AMF jest binarnym formatem danych służącym do serializacji obiektów ActionScript i przesyłania ich za pośrednictwem Internetu między aplikacją Flash a zdalnym serwisem. Twórcą tego protokołu jest Adobe, który jako ostatnią opublikował specyfikacje AMF 3 dla ActionScript 3. Pod linkiem: link, można znaleźć tabele konwersji miedzy typami ActionScript i Java oraz konwersji odwrotnej miedzy Java i ActionScript.

Dla obiektów użytkownika jak i dla obiektów silnie typowanych, własności z modyfikatorem public (włączając w to te zdefiniowane z metodami get i set)  są serializowane i wysyłane z aplikacji Flex do serwera lub z serwera do aplikacji Flex, jako własności generalnego obiektu Object. Aby umożliwić mapowanie pomiędzy odpowiadającymi sobie obiektami po stronie klienta i serwera, należy zastosować te same nazwy własności w klasach Java i ActionScript. W dalszej kolejnośc w klasie ActionScript należy zastosować metatag [RemoteClass], aby utworzyć obiekt ActionScriptowy, który będzie bezpośrednio mapowany do obiektu Java.

Poniżej przykład klasy ActionScript o nazwie Employee, która mapuje serwerowy obiekt DTO o tej samej nazwie znajdujący się w pakiecie services.

    package valueobjects.Employee{
        [Bindable]
        [RemoteClass(alias="services.Employee")]
        public class Employee {
            public var id:int;
            public var firstName:String;
            public var lastName:String;
            (...)
        }
    }

Instalacja BlazeDS i LiveCycle Data Services

Chcąc używać Flash Remotingu z wykorzystaniem BlazeDS lub LiveCycle Data Services niezbędne jest zainstalowanie i skonfigurowanie dodatkowej aplikacji na serwerze. Dla BlazeDS dostępne są do ściągnięcia wersje w postaci archiwum WAR, które można zainstalować podobnie jak aplikacje webową lub w postaci w pełni gotowego rozwiązania. W tym przypadku gotowym rozwiązaniem jest Tomcat wraz z osadzonym archiwum WAR, skonfigurowany i posiadający zbiór różnych przykładowych aplikacji. Instalator LiveCycle Data Service w podobny sposób umożliwia wybór pomiędzy instalacją rozwiązania wraz z integrowanym Tomcatem a instalacją aplikacji webowej na serwerze wybranym przez instalującego.

W takim scenariuszu na serwerze aplikacyjnym posiadać będziemy aplikację webową o nazwie blazeds lub lcds (często z dołączonym numerem wersji). Aplikacje tą można modyfikować i rozbudowywać własnym kodem, choć częstsze jest przekopiowanie JARów i konfiguracji aplikacji blazeds lub lcds do istniejącej aplikacji webowej Java.

Rysunek 4.Wymagane pliki BlazeDS lub LiveCycle Data Services.

Modyfikowanie web.xml

Chcąc przekopiować zawartość blazeds lub lcds do innej aplikacji webowej, często niezbędne okazuje się zmodyfikowanie web.xml'a, aby zdefiniować w nim funkcje nasłuchującą dla HttpFlexSession i servlet mapujący MessageBroker, który odbiera wszystkie żądania i przekazuje je do odpowiednich Javowych endpointów. Zawartość takiego web.xml'a można skopiować i przekleić z oryginalnego pliku web.xml zawartego w projekcie blazeds lub lcds.

    <!-- Http Flex Session attribute and binding listener support -->
    <listener>
       <listener-class>flex.messaging.HttpFlexSession</listener-class>
    </listener>
    <!-- MessageBroker Servlet -->
    <servlet>
       <servlet-name>MessageBrokerServlet</servlet-name>
       <display-name>MessageBrokerServlet</display-name>
       <servlet-class>flex.messaging.MessageBrokerServlet</servlet-class>
       <init-param>
          <param-name>services.configuration.file</param-name>
            <param-value>/WEB-INF/flex/services-config.xml</param-value>
       </init-param>
       <load-on-startup>1</load-on-startup>
    </servlet>
    <servlet-mapping>
       <servlet-name>MessageBrokerServlet</servlet-name>
       <url-pattern>/messagebroker/*</url-pattern>
    </servlet-mapping>

Opcjonalnie można skopiować i wkleić (i odkomentować) mapowanie dla RDSDispatchServlet, który używany jest przez funkcje Flash Builder 4 dla połączenia się i odczytania szczegółów wystawionych usług aby wygenerować przykładowy kod aplikacji klienckiej. Więcej szczegółów o technologiach modelujących Adobe można znaleźć w artykule The technologies for building Flex and Java applications i kursie Building a Flex application that connects to a BlazeDS Remoting destination using Flash Builder 4.

    <servlet>
       <servlet-name>RDSDispatchServlet</servlet-name>
       <display-name>RDSDispatchServlet</display-name>
       <servlet-class>flex.rds.server.servlet.FrontEndServlet</servlet-class>
       <init-param>
           <param-name>useAppserverSecurity</param-name>
           <param-value>false</param-value>
       </init-param>       
       <load-on-startup>10</load-on-startup>
    </servlet>
     
    <servlet-mapping id="RDS_DISPATCH_MAPPING">
       <servlet-name>RDSDispatchServlet</servlet-name>
       <url-pattern>/CFIDE/main/ide.cfm</url-pattern>
    </servlet-mapping>

Przegląd services-config.xml

Stosując Flash Remoting klient wysyła żądanie do serwera, gdzie jest ono przetwarzane, a następnie tworzona jest odpowiedz, która jest odsyłana do klienta. Konfiguracja żądań klienta zawarta jest w plikach services-config.xml i remoting-config.xml zlokalizowanych w katalogu /WEB-INF/flex/

Plik services-config.xml zawiera definicje różnych kanałów używanych do wysyłania żądań. Każda definicja kanału określa typ protokołu sieciowego i format wiadomości, który będzie stosowany w żądaniu i endpointu do którego ma ono dotrzeć. Javowy endpoint deserializuje wiadomość zgodnie z regułami protokołu określonego w konfiguracji i przekazuje ją w postaci Javowej do MessageBrokera, który przesyła ją dalej do właściwego serwisu docelowego (następny rozdział pokazuje jak należy go zdefiniować).

    <channels>
       <channel-definition id="my-amf" class="mx.messaging.channels.AMFChannel">
          <endpoint url="http://{server.name}:{server.port}/{context.root}/messagebroker/amf" class="flex.messaging.endpoints.AMFEndpoint"/>
       </channel-definition>
       <channel-definition id="my-secure-amf" class="mx.messaging.channels.SecureAMFChannel">
       <endpoint url="https://{server.name}:{server.port}/{context.root}/messagebroker/amfsecure"
          class="flex.messaging.endpoints.SecureAMFEndpoint"/>
       </channel-definition>
         (...)
    </channels>

Definiowanie miejsc docelowych (ang. destinations)

W pliku remoting-config.xml definiowane są miejsca docelowe (nazywane mapowaniem na klasy Javowe) do których MessageBroker przekazuje komunikaty. W tagu source umieszcza się pełną (wraz z nazwą pakietu) nazwe javowej klasy POJO, która musi posiadać bezargumentowy konstruktor i być widoczna na classpath. Zwykle klasy umieszcza się w katalogu /WEB‑INF/classes/ lub w archiwach JAR zlokalizowanym w /WEB‑INF/lib/. Możliwy jest również dostęp do EJB i innych obiektów zarejestrowanych w Java Naming and Directory Interface (JNDI) poprzez wywoływanie metod na miejscach docelowych, które są klasami fasady serwisu. Klasy te wyszukują obiekty w JNDI i wywołują ich metody.

Możliwy jest dostęp do stanowych i bezstanowych obiektów Java poprzez ustawienie atrybutu scope na jedną z wartości: application, session lub request (wartość domyślna). Tworzenie i zarządzanie obiektami referencyjnymi po stronie serwera obsługuje BlazeDS lub LiveCycle Data Services.

    <service id="remoting-service" class="flex.messaging.services.RemotingService">
        <adapters>
            <adapter-definition id="java-object" class="flex.messaging.services.remoting.adapters.JavaAdapter" default="true"/>
        </adapters>
        <default-channels>
            <channel ref="my-amf"/>
        </default-channels>
        <destination id="employeeService">
            <properties>
               <source>services.EmployeeService</source>
               <scope>application</scope>
            </properties>
        </destination>
    </service>

Możliwe jest określenie kanału dla wybranego miejsca docelowego.

    <destination id="employeeService " channels="my-secure-amf">

Na koniec, należy użyć tych miejsc docelowych przy definiowaniu instancji RemoteObject w aplikacji Flex.

    <s:RemoteObject id="employeeSvc" destination="employeeService"/>

Bezpieczeństwo

W większości aplikacji dostęp do części lub wszystkich zasobów zgromadzonych na serwerze zastrzeżony jest dla określonych użytkowników. Wiele aplikacji Java EE używa kontenera zarządzania bezpieczeństwem (ang. Container-Managed Security), który dokonuje autentykacji (sprawdzenia tożsamości użytkownika) i autoryzacji (określenie zasobów do których użytkownik ma dostęp – często wynika to ze zdefiniowanej w systemie roli użytkownika) w oparciu o Realm, kolekcją nazw użytkowników, wraz z ich hasłami i rolami. Realm skonfigurowany jest na serwerze Java EE i może mieć postać relacyjnej bazy danych, usługi katalogowej LDAP, dokumentu XML lub wykorzystywać dedykowany framework do autentykacji i autoryzacji.

Aby zintegrować aplikacje Flex z Javowym frameworkiem bezpieczeństwa, w celu odpowiedniego ograniczenia dostępu do zasobów serwera, należy dodać wpisy do plików konfiguracyjnych BlazeDS lub LiveCycle Data Services (szczegóły poniżej), a następnie, jak to ma miejsce w większości tworzonych aplikacji Flex, stworzyć formularz, który zostanie wypełniany danymi uwierzytelniającymi przez użytkownika i przesyłany do serwera w celu autentykacji. Dane uwierzytelniające użytkownika są przekazywane do serwera automatycznie wraz z wszystkimi późniejszymi żądaniami.

Modyfikacja services-config.xml

W pliku services-config.xml znajdującym się w BlazeDS lub LiveCycle Data Services należy zdefiniować login command dla stosowanego serwera aplikacji wewnątrz taga security. BlazeDS i LiveCycle Data Services dostarczają następujące login command: TomcatLoginCommand (dla Tomcata jak i dla Jbossa), JRunLoginCommand, WeblogicLoginCommand, WebSphereLoginCommand, OracleLoginCommand. Wszystkie one są zdefiniowane w pliku XML, w którym wystarczy jedynie odkomentować właściwy wpis.

Często pojawia się potrzeba zdefiniowania wymogów bezpieczeństwa (tag security-constraint), do czego można zastosować autentykację Basic lub Custom i jeśli jest to pożądane, jedną lub więcej ról. Do stworzenia autentykacji Custom przy użyciu Tomcat lub JBoss, często niezbędne jest dodanie dodatkowych klas do aplikacji webowej w celu integracji z frameworkiem bezpieczeństwa wykorzystywanym przez serwer aplikacji Java EE i zmodyfikowanie kilku plików konfiguracyjnych. Szczegółowe przykłady znajdują się pod następującym linkiem: link.

    <services-config>   
       <security>
       <login-command class="flex.messaging.security.TomcatLoginCommand"
          server="Tomcat">            
          <per-client-authentication>false</per-client-authentication>       
       </login-command>
       <security-constraint id="trusted">        
          <auth-method>Custom</auth-method>        
          <roles>            
             <role>employees</role>            
             <role>managers</role>         
          </roles>    
          </security-constraint>
       </security> 
       ...
    </services-config>

Modyfikacja remoting-config.xml

W następnej kolejności, w definicji miejsca docelowego, niezbędne jest dodanie odniesienia do uprzednio zdefiniowanych wymogów bezpieczeństwa:

    <destination id="employeeService">
        <properties>
           <source>services.EmployeeService</source>
        </properties>
        <security>
           <security-constraint ref="trusted"/>
        </security>
    </destination>

Możliwe jest również zdefiniowanie domyślnych wymogów bezpieczeństwa dla wszystkich miejsc docelowych i/lub ograniczeń dostępu jedynie dla wybranych metod, które będą stosowały inne wymogi bezpieczeństwa.

Domyślny kanał my-amf używa HTTP. Możliwa jest zmiana jednego lub więcej miejsc docelowych tak aby używały kanału my-secure-amf wykorzystującego HTTPS:

    <destination id="employeeService">    
       <channels>        
          <channel ref="my-secure-amf"/>    
       </channels>
        ...
    </destination>

Poniżej definicja my-secure-amf znajdujaca się w services-config.xml.

    <!-- Non-polling secure AMF -->
    <channel-definition id="my-secure-amf"
        class="mx.messaging.channels.SecureAMFChannel">   
     
     <endpoint url="https://{server.name}:{server.port}/{context.root}/messagebroker/amfsecure"
        class="flex.messaging.endpoints.SecureAMFEndpoint"/>
    </channel-definition>

Rozbudowa aplikacji Flex

Spójrzmy na aplikacje od strony serwera. Jeśli nasza aplikacja wykorzystuje autentykacje Custom, niezbędne jest stworzenie w aplikacji Flex formularza, który służy do wprowadzania przez użytkownika loginu i hasła, który przekazuje je do serwera poprzez wywołanie metody ChannelSet.login() i nasłuchuje zdarzeń result i fault. Pojawienie się zdarzenia result oznacza, że logowanie zakończyło się pomyślnie, zdarzenie fault oznacza niepowodzenie. Wprowadzone dane uwierzytelniające wykorzystywane są we wszystkich serwisach podłączonych za pomocą tego samego ChannelSet. Dla podstawowej autentykacji nie ma potrzeby dodawania czegokolwiek do aplikacji Flex. Otwiercie dialogu do logowania następuje w przeglądarce w sytuacji gdy aplikacja po raz pierwszy próbuje się połączyć do miejsca docelowego.

Zabezpieczona aplikacja może wysłać żądanie Flash Remoting do miejsca docelowego podobnie jak to było wykonywane wcześniej, z ta różnicą, że dane uwierzytelniające użytkownika są wysyłąne automatycznie z każdym wysyłanym żądaniem (dla obu typów autentykacji Basic i Custom). Jeśli miejsce docelowe lub metoda miejsca docelowego wymaga innego poziomu uprawnień niż ma zalogowany użytkownik wywołanie zwróci zdarzenie fault. W celu usunięcia danych uwierzytelniających i wylogowania użytkownika należy wywołać metodę ChannelSet.logout().

Arcitektura aplikacji Flex

Po zapoznaniu się z Flash Remoting od strony serwera oraz sposobem w jaki tworzymy instancje RemoteObject w Flex przyjrzyjmy się sposobowi w jaki można zbudować aplikacje aby wykorzystać te obiekty.

Stosowanie zdarzeń (ang. events)

Typowa aplikacja Flex składa się z kodu MXML tworzącego interfejs użytkownika i kodu ActionScript zawierającego logikę. Ta para technologii, tak jak ma to miejsce w przypadku JavaScript i przeglądarki obiektów DOM, jest powiązana za pomocą zdarzeń i procedur ich obsługi. Aby użyć w aplikacji RemoteObjectów, należy stworzyć ich instancje, wywołać zdalne metody miejsc docelowych, zdefiniować metody odbierające i przetwarzające otrzymane z serwera zdarzenia result i fault.

Poniżej przestawiony jest przykład prostej aplikacji, w której dane o pracownikach są wyszukiwane w bazie danych i wyświetlane w komponencie Flex DataGrid. Po zainicjalizowaniu aplikacji, następuje wywołanie metody getEmployees() z miejsca docelowego employeeService zdefiniowanego w pliku remoting-config.xml i jeśli uda się poprawnie odczytać dane z serwera następuje wypełnienie zmiennej  employees, w przeciwnym wypadku wiadomość o niepowodzeniu zostaje wyświetlona w Alert box, nie jest istotne z jakiej przyczyny odczyt się nie powiódł. Do powiązania zmiennej employees do własności dataProvider DataGridu stosuje się Data binding.

    <s:Application xmlns:fx="http://ns.adobe.com/mxml/2009"
       xmlns:s="library://ns.adobe.com/flex/spark"
       xmlns:mx="library://ns.adobe.com/flex/mx"           
       initialize="employeeSvc.getEmployees()">
       <fx:Script>
          <![CDATA[
             import mx.collections.ArrayCollection;
             import mx.controls.Alert;
             import mx.rpc.events.FaultEvent;
             import mx.rpc.events.ResultEvent;
             [Bindable]private var employees:ArrayCollection;
             private function onResult(e:ResultEvent):void{
                employees=e.result as ArrayCollection;              
             }
             private function onFault(e:FaultEvent):void{
                Alert.show("Error retrieving data.","Error");
             }
          ]]>
       </fx:Script>
       <fx:Declarations>
          <s:RemoteObject id="employeeSvc" destination="employeeService"
             result="onResult(event)" fault="onFault(event)" />
       </fx:Declarations>
       <mx:DataGrid dataProvider="{employees}"/>
    </s:Application>

Stosując RemoteObject możliwe jest definiowanie procedur obsługi zdarzeń result  i fault na poziomie serwisu:

    <s:RemoteObject id="employeeSvc" destination="employeeService" result="onResult(event)"
    fault="onFault(event)"/>

lub na poziomie metody:

    <s:RemoteObject id="employeeSvc" destination="employeeService">
       <s:method name="getEmployees" result="onResult(event)" fault="onFault(event)"/>
       <s:method name="getDepartments" result="onResult2(event)" fault="onFault2(event)"/>
    </RemoteObject>

lub dla każdego wywołania:

    <s:Application xmlns:fx=http://ns.adobe.com/mxml/2009"
       xmlns:s="library://ns.adobe.com/flex/spark"              
       xmlns:mx="library://ns.adobe.com/flex/mx"
       initialize="getEmployeesResult.token=employeeSvc.getEmployees()">
       <fx:Declarations>
          <s:RemoteObject id="employeeSvc" destination="employeeService"/>
          <s:CallResponder id="getEmployeesResult" result="onResult(event)"
             fault="onFault(event)"/>
       </fx:Declarations>

        

Zastosowania data bindingu

Data binding jest mocną stroną frameworka Flex pozwalającą zaktualizować interfejs użytkownika, w sytuacji gdy wyświetlane dane ulegną zmianie, bez jawnego tworzenia w tym celu rejestrów i obserwatorów zdarzeń (ang. listeners). We wcześniej zaprezentowanym kodzie, tag [Bindable] poprzedzający deklaracje zmiennej jest dyrektywą kompilatora, która definiuje zachowanie translatora podczas kompilacji pliku, skutkuje ona automatyczną generacją kodu ActionScript, dzięki któremu zdarzenia są rozgłaszane za każdym razem, gdy zmienna employees ulegnie zmianie.

    [Bindable]private var employees:ArrayCollection;

Nawiasy klamrowe wystepujące w przypisaniu dla własności dataProvider w deklaracji DataGridu realnie generują kod, który odpowiada za nasłuchiwanie zmian zmiennej employees i właściwe aktualizowanie widoku DataGridu.

    

W tej aplikacji zmienna employees jest inicializowana wartością null, a DataGrid pozostaje pusty, jednak jak tylko zmienna employees zostanie wypełniona danymi otrzymanymi z serwera, widok DataGridu zostanie zaktualizowany i wyświetli otrzymane dane.

Zastosowanie statusów widoku

Chcąc dokonać bardziej radykalnych zmian w interfejsie użytkownika podczas działania aplikacji, na przykład chcąc dodać, usunąć, przenieść lub zmodyfikować komponenty, należy użyć statusów widoku (ang. view status). Dla każdego widoku lub komponentu Flex możliwe jest zdefiniowanie różnych statusów, a następnie zdefiniowanie statusu(-ów) dla każdego obiektu w widoku, jakie powinien zawierać ten obiekt oraz wyglądu i zachowania które powinno być powiązane z danym statusem. Zmiany statusów odbywają się poprzez ustawianie własności currentState na nazwę jednego ze zdefiniowanych statusów.

    <s:states>
     <s:State name="employees"/>
     <s:State name="departments"/>
    </s:states>
    <mx:DataGrid dataProvider="{employees}" includeIn="employees"/>
    <s:Button label.employees="Switch to departments"
       label.departments="Switch to employees"
       click.employees="currentState='departments'"
       click.departments="currentState='employees'"/>

Zastosowanie komponentów MXML

Wraz z rozrostem aplikacji niezbędne okaże się porozdzielanie logiki na pakiety klas ActionScriptu i widoków na oddzielne pliki MXML (nazywane komponentami MXML). Każdy komponent MXML rozszerza istniejący komponent i może być jedynie włączony do aplikacji, ale nie może być uruchomiony samodzielnie. Chcąc użyć komponentu w MXML'u należy zainstancjonować komponent (nazwa jego klasy jest taka sama jak nazwa pliku w którym się znajduje) oraz dołączyć właściwą przestrzeń nazw (ang. namespace) tak aby kompilator był wstanie go zlokalizować.

Poniżej przedstawiono kod MXML'owego komponentu MasterView zapisanego w pliku MasterView.mxml zlokalizowanego w pakiecie com.adobe.samples.views.

    <s:Group xmlns:fx="http://ns.adobe.com/mxml/2009"        
       xmlns:s="library://ns.adobe.com/flex/spark" >
       <fx:Metadata>
          [Event(name="masterDataChange",type="flash.events.Event")]
       </fx:Metadata>
       <fx:Script>
          <![CDATA[
             import mx.collections.ArrayList;
            [Bindable]private var masterData:ArrayList=new ArrayList(["data1", "data2", "data3"]);
             public var selectedData:String;
             private function onChange(e:Event):void{
               selectedData=dataList.selectedItem;
               this.dispatchEvent(new Event("masterDataChange"));
             }
          ]]>
       </fx:Script>
       <s:DropDownList id="dataList" dataProvider="{masterData}" change="onChange(event)"/>
    </s:Group>

Natomiast ten kod przedstawia przykład instancjonowania i używania uprzednio stworzonego przez nas komponentu MasterView.

    <s:Application xmlns:fx="http://ns.adobe.com/mxml/2009"
       xmlns:s="library://ns.adobe.com/flex/spark"
       xmlns:views="com.adobe.samples.views.*">
       <fx:Script>
          <![CDATA[
            import mx.controls.Alert;
            private function onMasterDataChange(e:Event):void{
              Alert.show(e.currentTarget.selectedData,"Master data changed");
            }
          ]]>
       </fx:Script>
       <views:MasterView masterDataChange="onMasterDataChange(event)"/>
    </s:Application>
       <views:MasterView masterDataChange="onMasterDataChange(event)"/>
    </s:Application>

Rozgłaszanie zdarzeń

Aby stworzyć luźno powiązane komponenty, niezbędne jest zdefiniowanie publicznego API dla komponentu (z zastosowaniem modyfikatorów public) i/lub zdefiniować i rozgłosić stworzone przez siebie zdarzenia jak to pokazano powyżej w przykładowym kodzie MasterView. Metatag [Event] jest stosowany do definiowania tych zdarzeń jako części API komponentu i określenia jaki typ zdarzenia jest przez niego rozgłaszany.

    <fx:Metadata>
      [Event(name="masterDataChange",type="flash.events.Event")]
    </fx:Metadata>

Kiedy jakieś zdarzenie pojawia się w komponencie (przykładowo dla DropDownList  zdarzenie change), komponent ten tworzy instancje zdarzenia o typie określonym w metadanych i rozgłasza go.

    this.dispatchEvent(new Event("masterDataChange"));

Kod który instancjonuje stworzony przez nas komponent może w tym samym momencie zarejestrować się do nasłuchiwania zdarzenia naszego komponentu oraz zarejestrować procedurę obsługi zdarzeń (ang. event handler).

    <views:MasterView masterDataChange="onMasterDataChange(event)"/>

Luźno powiązane komponenty, jak ten nasz przykładowy, które definiują i rozgłaszają własne zdarzenia są podstawą modułowej konstrukcji aplikacji Flex. W rzeczywistości jest to właśnie sposób w jaki budowane są komponenty w frameworku Flex. Więcej informacji o rozgłaszaniu własnych zdarzeń można znaleźć w video Define events in Flex 4 with Flash Builder 4.

Tworzenie modułów

Domyślnie zostało przyjęte, że cały kod stworzony przez programistę jest kompilowany do jednego pliku SWF. Jeśli wynikowy plik SWF zacznie osiągać zbyt duże rozmiary, albo zawiera on funkcjonalność przeznaczona jedynie dla określonej grupy użytkowników, możliwe jest zastosowanie modułów do podziału aplikacji na większa liczbę plików SWF, które mogą być załadowywane i usuwane dynamicznie przez główną aplikację w czasie jej działania. W celu stworzenia modułu, niezbędne jest stworzenie klasy (ActionScript lub MXML) rozszerzającej klasę Module a następnie skompilowanie jej. Chcąc dynamicznie załadować moduł do aplikacji w czasie jej działania należy użyć tagu <mx:ModuleLoader> lub metod klasy ModuleLoader.

Zastosowanie mikroarchitektury

Przedstawiona wiedza o budowie aplikacji Flex to podstawy tej technologi, którą wraz z rozrostem aplikacji będzie trzeba wzbogacić o metodologie organizacji plików, centralizacji danych i data serwisów oraz zapewnienia komunikacji miedzy wszystkimi komponentami. W tym calu budując aplikacje Flex można zastosować wszystkie wzorce projektowe, które przez lata sprawdzały się przy tworzeniu aplikacji biznesowych. Tak naprawdę wiele konkretnych mikroarchitektur było i nadal jest rozwijanych. Najstarszą i najbardziej rozwiniętą jest Cairngorm, open source'owa mikroarchitektura, która stosuje polecenia (ang. command) i delegacje, wzorzec Front Controller, model i usługę obsługi danych opartą o singleton oraz dyspozytora zdarzeń (ang. event dispatcher). Innymi popularnie stosowanymi frameworkami są Pure MVC, Mate, Parsley, Swiz i Spring ActionScript. Więcej informacji na temat tych i innych frameworków można znaleźć na stronach poświęconych architekturze Flex w Adobe Developer Center.

Gdzie szukać więcej informacji

Ten artykuł przedstawia architekturę aplikacji Flex i Java. Dodatkowe informacje na ten temat można znaleźć na stronach do których prowadzą linki w tekście oraz te poniższe:

Nobody has commented it yet.

Only logged in users can write comments

Developers World