Introduction

Dans ce tutoriel, nous allons mettre en place une application RIA composée d'un client riche Flex 3 s'exécutant au sein d'un navigateur web via le plugin Flash Player et d'un serveur d'application JEE exploitant Spring et BlazeDS.

Architecture logicielle
Architecture logicielle

Dans cette architecture, le client Flex réalise des appels de service s'exécutant au sein du serveur d'application JEE. Pour cela, j'ai décidé d'utiliser les appels de type RPC (Remote Procedure Call), et plus spécifiquement le composant RemoteObject de Flex, permettant d'invoquer des services Java, sans avoir à se soucier de la sérialisation/désérialisation des objets échangés entre Flex et Java.

BlazeDS est une brique open-source, fournit par Adobe, supportant les appels RPC via RemoteObject. En pratique, il s'agit d'une servlet responsable, entre autres, de :

  • localiser et invoquer les services Java
  • désérialiser les données Flex reçues en instances d'objets Java et vice-versa

BlazeDS s'interpose donc entre le client Flex et les services Java à la manière d'un proxy.

Enfin, nous intégrons ici le framework Spring, non par nécessité, mais parce que celui-ci est très populaire et que l'intégration entre Spring et BlazeDS est une problématique récurrente.

Dans notre architecture, Spring est utilisé comme container IoC. Spring gèrera donc le cycle de vie de nos services. BlazeDS devra s'adresser à Spring afin de localiser un service à invoquer.

Nous allons commencer par installer l'ensemble des outils nécessaires, puis nous mettrons en place un projet mixte Flex/WTP configuré pour l'utilisation de BlazeDS. Nous développerons un service Java et une IHM Flex très simple afin de valider notre travail.

Enfin, nous ajouterons Spring et adapterons la configuration de BlazeDS.

I. Récupération des produits logiciels

II. Préparation de l'environnement de développement

Nous allons installer Eclipse, le plugin Flex Builder 3 et le serveur d'application Apache Tomcat, puis les framework BlazeDS et Spring.
Pour les besoins de l'article, l'ensemble sera installé dans le répertoire c:\appl.

II-a. Pré-requis

Vous devez disposer d'un JDK 1.5 ou plus d'installé. Pour vérifier, ouvrir un invité de commande et taper :

Vérification de la version de Java installée
Sélectionnez

C:\>java -version

java version "1.5.0_08"
Java(TM) 2 Runtime Environment, Standard Edition (build 1.5.0_08-b03)
Java HotSpot(TM) Client VM (build 1.5.0_08-b03, mixed mode)

Se reporter à la FAQ java en cas de problème : La FAQ Java

II-b. Installations

Décompresser l'archive d'Eclipse dans le répertoire c:\appl\eclipse. Pour la suite de cet article, nous considérerons qu'Eclipse est installé dans ce répertoire.

Une fois Eclipse installé, lancer l'installation du plugin Flex Builder 3 Professional :

Ceci nécessite les droits d'administration sur le poste de travail.

Choisir c:\appl\fb3 comme répertoire d'installation, et c:\appl\eclipse comme répertoire où est installé Eclipse :

Choix d'installation
Choix d'installation

Décompresser l'archive de Tomcat 6.0 dans c:\appl\tomcat, celle de Spring dans c:\appl\spring et enfin celle de BlazeDS dans c:\appl\blazeds.

Vous trouverez, dans c:\appl\blazeds, 2 fichiers, dont blazeds.war qui contient une webapp JEE pré-configurée pour BlazeDS.

III. Création et configuration du projet

  • Démarrer Eclipse
  • Sélectionner un répertoire pour la création d'un workspace. Par ex : c:\worspace
  • Ouvrir la perspective Flex Development (Menu Windows / Open Perspective / Other… / Flex Development)

Perspective Flex Development

  • Créer un nouveau projet Flex (Menu File / New / Flex Project) :
  • project name : FBSIntegration pour Flex BlazeDS Spring Integration
  • application server type : J2EE
  • laisser coché “LiveCycle Data Services” et Create combined Java/Flex project using WTP

Assitant de création de projet Flex

  • Cliquer sur le bouton Next afin d'atteindre l'étape Configure JEE Server
  • Cliquer sur le bouton New... afin de configurer une instance du serveur Tomcat dans Eclipse
  • Sélectionner Apache / Tomcat v6.0 Server, puis cliquer sur Next>

Assitant de création de projet Flex

  • Renseigner le champ “Tomcat installation directory” pour qu'il pointe sur le répertoire d'installation de Tomcat (c:\appl\tomcat). Puis cliquer sur Finish

Assitant de création de projet Flex

  • De retour au wizard de configuration du projet Flex/WTP, renseigner le champ Flex WAR File pour désigner le fichier blazeds.war situé dans le répertoire d'installation de BlazeDS (c:\appl\blazeds\blazeds.war).

Assitant de création de projet Flex

  • Cliquer sur Next >
  • L'écran suivant nous indique que les fichiers sources Flex sont situés dans flex_src/, tandis que les fichiers sources Java sont situés dans src/
  • Cliquer sur Finish

Assitant de création de projet Flex

Il reste un dernier paramétrage à réaliser :

  • Sélectionner le projet FBSIntegration dans la vue Flex Navigator, puis afficher les propriétés du projet (menu Project / Properties / Flex Server)
  • Modifier le champ Context root pour que celui-ci désigne correctement le nom du contexte de notre application, à savoir /FBSIntegration.

Propriétés du projet

Enfin, il faut configurer le serveur Tomcat au sein d'Eclipse pour qu'il déploie notre application :

  • Si ce n'est pas déjà fait, afficher la vue Servers (menu Windows / Show View / Other / Server / Servers)
  • Ajouter le projet FBSIntegration au serveur Tomcat (Bouton droit sur le serveur “Tomcat v6.0 server at localhost, et ajouter le projet FBSIntegration à la liste des Configured Projects (menu Add and Remove Projects...)

Propriétés du serveur Tomcat

Ajout du projet au serveur Tomcat

IV. Première application

Nous allons développer une application trés simple, de type "Hello World" afin de valider le bon fonctionnement de l'ensemble.
Le scénario est le suivant :

  • L'utilisateur saisit un login, et clique sur le bouton « Appel de WelcomeService »
  • Ceci déclenche un appel à la méthode sayHello du service distant welcomeService
  • Le message retourné par la méthode sayHello est affiché dans la zone de réponse.
  • En cas d'échec de l'appel, le message d'erreur est affiché dans la zone de réponse.
Vue design
Vue design

IV-a. Développement de l'IHM Flex

Le fichier flex_src/FBSIntegration.mxml contient principalement :

  • le champ de saisie du login : tiLogin
  • le bouton déclenchant l'appel : btnAppel
  • le composant RemoteObject permettant d'effectuer l'appel distant : roWelcomService
  • la zone de texte permettant d'afficher le résultat de l'appel : taReponse
FBSIntegration.mxml
Sélectionnez

<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="vertical" width="451" height="168" 
	paddingBottom="0" paddingLeft="0" paddingRight="0" paddingTop="0">

	<mx:Script>
		<![CDATA[
			import mx.rpc.events.FaultEvent;
			import mx.rpc.events.ResultEvent;
			
			private function doCall():void{
				// Appeler le service distant
				roWelcomeService.sayHello(tiLogin.text);
			}
			
			/**
			 * Méthode invoquée en cas de succès de l'appel RPC 
			 */ 
			private function onResult(event : ResultEvent):void{
				// Afficher la réponse
				taReponse.text = event.result as String;
			}
			
			/**
			 * Méthode invoquée en cas d'échec de l'appel RPC 
			 */ 
			private function onFault(event : FaultEvent):void{
				// Afficher le message d'erreur
				taReponse.text = event.fault.message;
			}
		
		]]>
	</mx:Script>

	<mx:RemoteObject id="roWelcomeService" 
					 destination="welcomeServiceDest"
					 result="onResult(event)"
					 fault="onFault(event)"/>
	<mx:Panel width="451" height="168" layout="absolute" title="Hello World">
		<mx:Label x="10" y="10" text="Login : "/>
		<mx:TextInput id="tiLogin" text="Guest"
					  x="66" y="8" />
		<mx:Button id="btnAppel" label="Appel de WelcomeService"
			       x="234" y="8" 
			       click="doCall()"/>
		<mx:Label x="10" y="38" text="Réponse :"/>
		<mx:TextArea id="taReponse" 
			         x="81" y="37" width="327" height="81"/>
	</mx:Panel>
</mx:Application>

IV-b. Premier test

Nous allons tester l'application en l'état. Pour cela, il faut :

Premier test...
Premier test...

Bien entendu, l'appel échoue, car nous n'avons pas encore développé le service "WelcomeService" appelé par l'IHM Flex, ni déclaré celui-ci auprès de BlazeDS !

IV-c. Création de WelcomeService

Dans le répertoire src/, créer la classe WelcomeService dans le package com.developpez.service :

WelcomeService.java
Sélectionnez

package com.developpez.service;

public class WelcomeService {

	public String sayHello(String pLogin){
		return "Bienvenue "+pLogin;
	}
}

Ce service trés simple définit la méthode sayHello, qui prend en argument un login et retourne un message de bienvenue incluant le login fourni.

IV-d. Déclaration du service auprès de BlazeDS

Une fois le service Java créé, il faut configurer BlazeDS pour que celui-ci sache le localiser et l'associe à une « destination ».

  • Editer le fichier remoting-service.xml, situé dans WebContent/WEB-INF/flex/
  • Il faut y ajouter la destination welcomeServiceDest :
remoting-service.xml
Sélectionnez

<?xml version="1.0" encoding="UTF-8"?>
<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="welcomeServiceDest">
		<properties>
			<source>com.developpez.service.WelcomeService</source>
			<scope>application</scope>
		</properties>
	</destination>	
</service>

Flex Builder ne re-compile pas l'application Flex suite à la modification de ce fichier !
Il faut donc soit faire un Project / clean, soit légèrement modifier le fichier mxml pour que celui-ci soit recompilé et intègre la déclaration de la destination.

IV-e. Tester l'application

Re-testons l'application afin de s'assurer que nous avons correctement configuré BlazeDS (se reporter à la section IV-b).
Cette fois, l'appel au service réussit et nous retourne le message de bienvenue ! :-)

Second test... réussi !
Second test... réussi !

V. Intégration de Spring

Nous allons intégrer le framework Spring dans notre environnement, en suivant les étapes suivantes :

  • Ajout des dépendances au projet
  • Initialisation du context Spring
  • Déclaration de WelcomeService dans le context Spring
  • Configuration de BlazeDS

V-a. Ajout des dépendances au projet

Copier le fichier spring.jar, situé dans c:\appl\spring\dist dans le répertoire WebContent/WEB-INF/lib du projet, et rafraîchir le projet si nécessaire.

La librairie spring.jar doit apparaître dans les Web App Librairies de la section Java Build Path des propriétés du projet :

Ajout de spring.jar
Ajout de spring.jar

V-b. Configuration du contexte Spring

Pour une introduction à Spring, vous pouvez consulter ce tutorial : Introduction au framework Spring (par Erik Gollot)

Nous allons tout d'abord ajouter le fichier webApplicationContext.xml de configuration du contexte Spring puis modifier le fichier web.xml afin d'initialiser ce contexte.

Ajouter le fichier webApplicationContext.xml suivant à la racine du répertoire src/ :

webApplicationContext.xml
Sélectionnez

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
	   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	   xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd">
	   
</beans>

Puis il faut modifier le fichier web.xml situé dans WebContent/WEB-INF.
Nous y ajoutons une section « context-param » et un « listener », comme suit :

web.xml
Sélectionnez

<?xml version="1.0" encoding="UTF-8"?>
<web-app>
    <display-name>FBSIntegration</display-name>
    <description>BlazeDS Application</description>

	<context-param>
		<param-name>flex.class.path</param-name>
		<param-value>/WEB-INF/flex/hotfixes,/WEB-INF/flex/jars</param-value>
	</context-param>

    <!-- Chemin d'acces au fichier de configuration Spring -->
    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>/WEB-INF/classes/webApplicationContext.xml</param-value>
    </context-param>

    <!-- Spring listener -->
    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>
    
    ...
</web-app>

Nous allons déclarer le bean welcomeServiceBean en tant qu'instance de la classe WelcomeService, en ajoutant une déclaration au fichier webApplicationContext.xml précédemment créé :

webApplicationContext.xml
Sélectionnez

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
	   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	   xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd">
	<bean id="welcomeServiceBean" class="com.developpez.service.WelcomeService"/>	   						   
</beans>

V-c. Intégration de Spring à BlazeDS

En l'état, BlazeDS n'utilise pas le context Spring pour localiser les instances du service.

Il est nécessaire de configurer, auprès de BlazeDS, une Factory spécifique, qui s'occupera d'interroger le contexte Spring.
Cette technique a été initialement proposée par Jeff Vroom : Spring and Flex Integration by Jeff Vroom
Il vous faut ajouter le fichier SpringFactory.java suivant dans le package com.developpez.web :

SpringFactory.java
Sélectionnez

package com.developpez.web;

import org.springframework.beans.BeansException;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.context.ApplicationContext;
import org.springframework.web.context.support.WebApplicationContextUtils;

import flex.messaging.FactoryInstance;
import flex.messaging.FlexFactory;
import flex.messaging.config.ConfigMap;
import flex.messaging.services.ServiceException;

/**
 * This interface is implemented by factory components which provide
 * instances to the flex messaging framework.  To configure flex data services
 * to use this factory, add the following lines to your services-config.xml
 * file (located in the WEB-INF/flex directory of your web application).
 *
 *	&lt;factories&gt;
 *     &lt;factory id="spring" class="flex.samples.factories.SpringFactory" /&gt;
 *  &lt;/factories&gt;
 *
 * You also must configure the web application to use spring and must copy the spring.jar
 * file into your WEB-INF/lib directory.  To configure your app server to use spring,
 * you add the following lines to your WEB-INF/web.xml file:
 *
 *   &lt;context-param&gt;
 *        &lt;param-name&gt;contextConfigLocation&lt;/param-name&gt;
 *        &lt;param-value&gt;/WEB-INF/applicationContext.xml&lt;/param-value&gt;
 *   &lt;/context-param&gt;
 *
 *   &lt;listener&gt;
 *       &lt;listener-class&gt;org.springframework.web.context.ContextLoaderListener&lt;/listener-class&gt;
 *   &lt;/listener&gt;
 * 
 * Then you put your spring bean configuration in WEB-INF/applicationContext.xml (as per the
 * line above).  For example:
 * 
 *  &lt;?xml version="1.0" encoding="UTF-8"?&gt;
 *  &lt;!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd"&gt;
 *   
 *  &lt;beans&gt;
 *    &lt;bean name="weatherBean" class="dev.weather.WeatherService" singleton="true"/&gt;
 *  &lt;/beans&gt;
 *  
 * Now you are ready to define a destination in flex that maps to this existing service. 
 * To do this you'd add this to your WEB-INF/flex/remoting-config.xml:
 *
 *  &lt;destination id="WeatherService"&gt;
 *      &lt;properties&gt;
 *          &lt;factory&gt;spring&lt;/factory&gt;
 *          &lt;source&gt;weatherBean&lt;/source&gt;
 *      &lt;/properties&gt;
 *  &lt;/destination&gt;
 *
 * @author Jeff Vroom
 */
public class SpringFactory implements FlexFactory
{
    private static final String SOURCE = "source";

    /**
     * This method can be used to initialize the factory itself.  It is called with configuration
     * parameters from the factory tag which defines the id of the factory.  
     */
    public void initialize(String id, ConfigMap configMap) {}


    /**
     * This method is called when we initialize the definition of an instance 
     * which will be looked up by this factory.  It should validate that
     * the properties supplied are valid to define an instance.
     * Any valid properties used for this configuration must be accessed to 
     * avoid warnings about unused configuration elements.  If your factory 
     * is only used for application scoped components, this method can simply
     * return a factory instance which delegates the creation of the component
     * to the FactoryInstance's lookup method.
     */
    public FactoryInstance createFactoryInstance(String id, ConfigMap properties)
    {
        SpringFactoryInstance instance = new SpringFactoryInstance(this, id, properties);
        instance.setSource(properties.getPropertyAsString(SOURCE, instance.getId()));
        return instance;
    } // end method createFactoryInstance()

    /**
     * Returns the instance specified by the source
     * and properties arguments.  For the factory, this may mean
     * constructing a new instance, optionally registering it in some other
     * name space such as the session or JNDI, and then returning it
     * or it may mean creating a new instance and returning it.
     * This method is called for each request to operate on the
     * given item by the system so it should be relatively efficient.
     * &lt;p&gt;
     * If your factory does not support the scope property, it
     * report an error if scope is supplied in the properties
     * for this instance.
     */
    public Object lookup(FactoryInstance inst)
    {
        SpringFactoryInstance factoryInstance = (SpringFactoryInstance) inst;
        return factoryInstance.lookup();
    } 


    static class SpringFactoryInstance extends FactoryInstance
    {
        SpringFactoryInstance(SpringFactory factory, String id, ConfigMap properties)
        {
            super(factory, id, properties);
        }


        public String toString()
        {
            return "SpringFactory instance for id=" + getId() + " source=" + getSource() + " scope=" + getScope();
        }

        public Object lookup() 
        {
            ApplicationContext appContext = WebApplicationContextUtils.getWebApplicationContext(flex.messaging.FlexContext.getServletConfig().getServletContext());
            String beanName = getSource();

            try
            {
            	return appContext.getBean(beanName);
            }
            catch (NoSuchBeanDefinitionException nexc)
            {
                ServiceException e = new ServiceException();
                String msg = "Spring service named '" + beanName + "' does not exist.";
                e.setMessage(msg);
                e.setRootCause(nexc);
                e.setDetails(msg);
                e.setCode("Server.Processing");
                throw e;
            }
            catch (BeansException bexc)
            {
                ServiceException e = new ServiceException();
                String msg = "Unable to create Spring service named '" + beanName + "' ";
                e.setMessage(msg);
                e.setRootCause(bexc);
                e.setDetails(msg);
                e.setCode("Server.Processing");
                throw e;
            } 
        }
    } 
}

Une fois la SpringFactory créée, il est nécessaire de la déclarer pour que BlazeDS sache la localiser.
Pour cela, ajouter une section <factories> au début du fichier services-config.xml, situé dans WebContent/WEB-INF/flex :

Mise à jour de services-config.xml
Sélectionnez

<?xml version="1.0" encoding="UTF-8"?>
<services-config>
	<factories>
		<factory id="SpringFactory" class="com.developpez.web.SpringFactory" />
	</factories>
...
</services-config>

Enfin, il faut modifier la destination welcomeServiceDest dans le fichier remoting-config.xml, pour que celle-ci utilise la factory SpringFactory afin de localiser le bean Spring welcomeServiceBean.
La balise <factory> est ajoutée, afin de spécifier la SpringFactory déclarée dans le services-config.xml.
La balise <source> est modifiée afin de spécifier le nom du bean Spring à utiliser.
Enfin, la balise <scope> est supprimée, car c'est Spring qui gère dorénavant le cycle de vie du service.

Mise à jour de remoting-config.xml
Sélectionnez

<destination id="welcomeServiceDest">
	<properties>
		<factory>SpringFactory</factory>
		<source>welcomeServiceBean</source>
	</properties>
</destination>	

V-d. Tester l'application finale

Démarrer le serveur d'application Tomcat
Dans la console, les traces suivantes permettent de vérifier l'initialisation correcte de Spring :

Traces de démarrage du serveur d'application
Sélectionnez

INFO: Initializing Spring root WebApplicationContext
...…
INFO: Loading XML bean definitions from ServletContext resource [/WEB-INF/classes/webApplicationContext.xml]
...
INFO: Pre-instantiating singletons in org.springframework.beans.factory.support.DefaultListableBeanFactory@7f58ef: defining beans [welcomeServiceBean]; root of factory hierarchy
...
INFO: Root WebApplicationContext: initialization completed in 242 ms
				

Lancer à nouveau un navigateur sur l'URL suivante : http://localhost:8080/FBSIntegration/FBSIntegration.swf

VI. Pour aller plus loin...

Vous disposez maintenant d'un projet mixte WTP/Flex correctement configuré pour exploiter Flex, BlazeDS et Spring. A vous maintenant de construire sur cette base votre application !
Voici quelques ressources utiles :