Test et Maintenance - Intégration Continue (GIS2A5, Polytech'Lille)

Conception Agile des logiciels (Master 2 IAGL, Université de Lille 1)

Architectures Logicielles (GIS4, Polytech'Lille)

Cours assuré par Olivier Caron (support de cours).
Intervenant en Travaux Pratiques et en tutorat Projet

Comprendre les communications distantes

client/serveur
Figure 1 : Envoi de messages client / serveur

Dans le monde Java, on parle de communications à distance dès lors qu'un objet d'une machine virtuelle (JVM) échange des données avec un objet d'une autre JVM, même si celle-ci est exécutée sur la même machine : ce sont 2 processus différents qui ne peuvent dialoguer que via la couche réseau.

Pour masquer la complexité de la couche réseau, les frameworks sont capables de générer des objets : stub (rôle client) et skeleton (rôle serveur), de manière transparente pour l'utilisateur, qui permettent de manipuler des objets distants tout en ayant l'impression que ce sont des objets locaux.

Explications

Comme un objet A côté client ne peut pas pas dialoguer avec un objet B côté serveur, nous devons utiliser une librairie d'accès à distance. Il en existe plusieurs, fonctionnant sur le même principe mais différent par le protocole utilisé (IIOP, HTTP, etc.). Quand l'objet B (rôle serveur) sera instancié, un squelette (skeleton) sera instantié en même temps afin de gérer la couche réseau et être à l'écoute des requêtes entrantes. Quand le client voudra dialoguer avec l'objet B, il ne va pas instantier un objet B (dans ce cas, nous aurions 2 objets B différents : un côté serveur et un côté client) mais va demander à la librairie de créer un objet de type B (ou en récupérer un enregistré dans un annuaire) à notre place. La librairie va alors créer un objet stub lié à l'objet distant B dans la machine virtuelle du client. L'objet retourné n'est pas de type B mais du type de l'interface de B (MyServiceInterface): la librairie a construit une sorte de proxy ayant le même comportement que l'objet B (mêmes méthodes) et permettant de dialoguer avec lui en cachant la complexité de la couche réseau.

Les communications entre client et serveur:

  1. Le client souhaite envoyer un message au serveur: sayHello("Christophe"). Cette requête contient un paramètre qui va devoir être encodé (marshalling) pour transiter via la couche réseau.
  2. Le serveur reçoit la requête et doit maintenant décoder le paramètre envoyé par le client (unmarshalling)
  3. Le serveur traite le message (exécute le code associé) et produit le résultat. Ce résultat doit à son tour être encodé (marshalling) pour être renvoyé au client.
  4. Enfin, le client décode le résultat (unmarshalling) renvoyé par le serveur.

JSP (Java Server Pages)

Les JSP sont une technologie permettant d'ajouter du contenu dynamique à des pages Webs en s'appuyant sur Java. Ils offrent un confort au programmeur en permettant de mixer du code Java (à l'aide de balises JSP) et du code HTML. Les JSP, avant de pouvoir être utilisées, sont transformées en Servlet Java. Le code HTML est encapsulé dans des instructions out.write(""), les déclarations de méthodes et d'attributs deviennent membres de la classe générée, et le code entre balises <% et %> se retrouve dans la méthode _jspService(). Cette méthode est appelée lorsque le browser fait une requête au JSP.

Descripteur d'applications J2EE

application.xml : décrit une application J2EE, notamment les modules (web, ejb) la composant. La balise context-root désigne l'url à laquelle sera accessible notre application. Cette url est relative à l'url de déploiement du serveur d'applications (ex: http://localhost:8080).

EJB Entité

Afin de pouvoir dialoguer avec la base de données dans un EJB Entité, vous devez déclarer une instance de gestionnaire d'entité :

@PersistenceContext(unitName="myAppDB")
protected EntityManager em;

Celui-ci est associé à un contexte de persitance déclaré dans le descripteur persistence.xml

<persistence>
  <persistence-unit name="myAppDB">
    <jta-data-source>java:/PostgresDS</jta-data-source>
    ...
  </persistence-unit>
<persistence>

Le lien se fait grâce au nom de l'unité de persistance (unit-name). Le lien avec la base de données se fait, quant à lui, grâce à la source de données (jta-data-source) déclarée dans l'unité de persistance (toujours dans persistence.xml). Cette source de données a été déclarée au niveau du serveur d'application en précisant l'adresse de la base de données, l'utilisateur et le mot de passe à utiliser pour s'y connecter.

Associations et EJB Entités

La convention de nommage veut que les accesseurs utilisent les noms de rôle des associations.

mappedBy
Figure 2 : Utilisation des rôles dans la définition d'associations

L'association peut être bidirectionnelle. Dans une relation bidirectionnelle, une des extrémités (et seulement une) doit être la propriétaire : la propriétaire est responsable de la mise à jour des colonnes de l'association. Cela signifie que pour persister les données en base, il faut obligatoirement utiliser le setter du propriétaire de l'association.

Pour déclarer une extrémité comme non responsable de la relation, l'attribut mappedBy est utilisé. mappedBy référence le nom de la propriété de l'association du côté du propriétaire. Dans l'exemple ci-dessous, A est propriétaire de l'association.

@Entity
public class A implements Serializable {
    @ManyToMany
    public Collection<B> getRoleB() {
        ...
    }

@Entity
public class B implements Serializable {
    @ManyToMany(mappedBy = "roleB")
    public Collection<A> getRoleA() {
    ...
}

Pour mettre à jour les données, il faut donc procéder comme suit:

A anInstanceOfA;
B anInstanceOfB;
...
anInstanceOfA.getRoleB().add( anInstanceOfB );

La mise à jour des données via anInstanceOfB ne provoquera pas d'erreur mais les données ne seront pas persistées en base.

JNDI et jboss

Si, lorsque vous tentez de récupérer une référence vers un EJB session distant, une exception NoSuchEJBException est lancée, vérifiez:

  1. que l'URL de lookup est bien au format suivant (attention au '!') : ejb:<app-name>/<module-name>/<distinct-name>/<bean-name>!<interface-FQN>.
    • <app-name> correspond au nom du fichier ear (sans l'extension) de l'application
    • <module-name> corresponfd au nom du fichier jar (sans l'extension) contenant le bean recherché
    • <interface-FQN> correspond au nom complet de l'interface (incluant le nom complet du package) remote ou locale.
    • Si l'EJB recherché est un EJB Stateful, ajoutez ?stateful à la fin de l'URI précédente.
  2. que votre composant session est bien déployé, qu'il est annoté avec @Stateless ou @Stateful, qu'il implémente bien l'interface locale/remote, que les interfaces sont elles aussi bien annotées, et enfin que vous avez spécifié le déploiement du module concerné dans le descripteur application.xml.

Pour un accès local, on utilisera une adresse commançant par java:. Pour un accès distant, il faut utiliser une adresse commançant par ejb:.

Entity Manager

La persistance des EJB entités est assurée par l'EntityManager. L'Entity manager sait comment persister les données en base en cherchant dans la configuration de l'ORM (mapping objet-relationnel, ex: hibernate) utilisé par le serveur d'application. Il réalise les opération CRUD (create, read, update et delete) nécessaires. En résumé, l'Entity Manager gère le cycle de vie des EJB entités. Il est le pont entre le monde objet et le monde relationnel. Il réalise les opérations suivantes:

  • à la création d'un EJB entité, l' EntityManager traduit l'entité en un enregistrement en base de données
  • à la MAJ d'un entité, il piste les données en base correspondant aux modification de l'entité et les met à jour
  • à la suppression d'un entité, il détruit les données en base correspondantes.
  • L'entityManager essaye également de garder les données en base et l'entité synchronisés tant que l'entité est dans un état géré (managed).

Un EJB entité devient détaché dès que l'EntityManager n'est plus accessible: sérialisation d'un entité pour le passer à un autre environnement d'exécution (ex: web tier), suppression de l'entité ou copie de l'entité. Lorsqu'un EJB entité est détaché, il n'est plus synchronisé avec la base.

Eager / Lazy fetching

Lorsque l'on manipule des objets, on manipule très rarement des objets isolés mais plutôt des objets inter-connectés formant un graphe d'objets. Si lors de l'accés à un objet, nous devions charger complétement le graphe d'objets auquels il se réfère, ce serait très coûteux en temps d'exécution (et nécessiterait aussi un espace mémoire non négligeable). C'est pour cela que, dans la mesure du possible, on ne charge les objets qu'à la demande. Afin de pouvoir gérer la stratégie de chargement, JPA fournit 2 modes pour la récupération des objets:

  • Eager fetching: chargement du graphe d'objets. En mode détaché, tout le graphe d'objet est accessible
  • Lazy fetching: chargement à la demande des objets. Un inconvénient de cette méthode est le nombre important de requêtes pouvant être effectuées sur la base de données. En mode détaché, les informations non chargés en mémoire ne sont pas disponibles.

Mode de chargement par défaut selon les cardinalités:

Eager fetchingLazy fetching
@OneToOne@OneToMany
@ManyToOne@ManyToMany

En effet, lorsqu'il n'y a qu'une entité à charger plutôt qu'une collection, il n'est pas trés coûteux de le faire.