novembre
2009
Vous pouvez trouver ce billet sur mon nouveau blog avec les informations mises a jour.
Dans le billet précédant [step0], j’ai présenté ce que je souhaitais effectuer dans les billets intitulés Conception d’un client Eclipse RCP et serveur OSGI avec Spring DM. Pour rappel, mon idée est d’expliquer pas à pas comment créer une application cliente eclipse RCP qui communiquera avec des services hébérgés sur un serveur OSGI. L’application RCP affichera une liste d’utilisateurs User récupérée via un service UserService qui sera hébérgé sur le serveur OSGI. Dans ce billet nous n’allons pas encore faire d’OSGI, mais nous allons créer un client (Java main) qui va communiquer avec un service UserService qui retournera une liste de User. Ce service qui est une interface sera récupéré via une factory de services ServicesFactory. Nous découperons chacune de ces couches (Client/Service/Domain (POJO User)) dans un projet Java distinct.
Voici un schéma de ce que nous allons effectuer dans ce billet :
Ce schéma met en évidence trois projets Java :
- org.akrogen.gestcv.domain (POJO) : projet Java qui contient les POJO (domaine de l’application) entre autres le POJO User.
- org.akrogen.gestcv.services (Services) : projet Java qui contient le service UserService qui permet de retourner une liste d’utilisateurs User.
- org.akrogen.gestcv.simplemainclient (Client) : projet Java qui consomme dans un main Java le service UserService.
Les dépendances entre les projets sont gérées classiquement via le Java Build Path du projet. Nous verrons en fin de ce billet les 2 problémes courant que nous rencontrons avec ce type de dépendance classique :
- il est impossible de rendre inaccéssible l’utilisation de certaines classes d’un projet Java aux classes d’un autre projet Java. Ce problème sera résolu via OSGI dans le prochain billet en indiquant les packages que l’on souhaite exposer.
- tous les projets Java partagent le même ClassLoader, ce qui peut poser des problèmes lorsque l’on souhaite utiliser des versions différentes d’une librairie dans les projets Java. Ce problème sera résolu via OSGI dans le prochain billet car chaque Bundle OSGI a son propre ClassLoader.
Vous pouvez télécharger les projets org.akrogen.gestcv_step1.zip et org.akrogen.gestcv_step1-commons-lang.zip (zip qui contient les projets Java qui utilisent 2 versions de la librairie Apache commans-lang*.jar et qui montre en évidence le problème de ClassLoader) présentés dans ce billet.
Prérequis
Pour la rédaction de mes billets, j’ai téléchargé la dernière version d’Eclipse qui actuellement est Eclipse Galileo (Eclipse 3.5). Je conseille plus exactement de télécharger la distribution Eclipse for RCP/Plug-in Developers (183 MB ). Cette distribution est faite pour développer des applications RCP, les sources de l’API RCP y sont disponibles. Cependant je pense que dans un billet je vais devoir utiliser une application WEB classique. Un serveur (Tomcat) sera nécéssaire et j’utiliserais sûrement la distribution Eclipse IDE for Java EE Developers (189 MB ). Dans la suite des billets nous aurrons aussi besoin d’une Base de Données (mais j’y reviendrais).
org.akrogen.gestcv.domain
Le projet org.akrogen.gestcv.domain est le domaine applicatif du projet, il contiendra tous les POJO de notre projet.
Création Java Project
Ici nous allons créer le projet Java org.akrogen.gestcv.domain pour cela sélectionnez le menu File/New/Other puis Java Project :
Cliquez sur Next et renseignez le champs Project name avec org.akrogen.gestcv.domain :
Cliquez sur Finish le projet Java est créé.
Classe User
Créez la classe org.akrogen.gestcv.domain.User comme suit :
public class User {
private String login;
private String password;
public User(String login, String password) {
setLogin(login);
setPassword(password);
}
public String getLogin() {
return login;
}
public void setLogin(String login) {
this.login = login;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
}
org.akrogen.gestcv.services
Nous allons créer le projet Java org.akrogen.gestcv.services qui sera notre couche service qui contiendra le service UserService qui retournera la liste des User. Pour cela, créez le projet Java org.akrogen.gestcv.services. Ce projet doit faire référence au projet Java org.akrogen.gestcv.domain car il doit retourner une liste de POJO User.
Java Build Path/Add Project
Ici nous allons ajouter au projet org.akrogen.gestcv.services une dépendance sur le projet org.akrogen.gestcv.domain. Pour cela sélectionnez le projet org.akrogen.gestcv.services
puis cliquez sur le bouton droit. Sélectionnez l’item Properties puis le noeud Java Build Path et sélectionnez l’onglet Projects :
Cliquez sur le bouton Add. Ceci ouvre uen boîte de dialogue qui propose tous les projets du workspace dont on peut dépendre :
Sélectionnez le projet org.akrogen.gestcv.domain, puis cliquez sur OK. La dépendance est maintenant créée :
Interface UserService
Créez l’interface org.akrogen.gestcv.services.UserService comme suit :
import java.util.Collection;
import org.akrogen.gestcv.domain.User;
public interface UserService {
Collection<User> findAllUsers();
}
Implémentation UserServiceImpl
Créez la classe org.akrogen.gestcv.services.impl.UserServiceImpl comme suit :
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import org.akrogen.gestcv.domain.User;
import org.akrogen.gestcv.services.UserService;
public class UserServiceImpl implements UserService {
private Collection<User> users = null;
public Collection<User> findAllUsers() {
if (users == null) {
users = createUsers();
}
return users;
}
private Collection<User> createUsers() {
List<User> users = new ArrayList<User>();
users.add(new User("angelo", ""));
users.add(new User("djo", ""));
users.add(new User("keulkeul", ""));
users.add(new User("pascal", ""));
return users;
}
}
On peut remarquer que les implémentations des services se retrouvent dans le packages *.impl. Ceci est une bonne règle de nommage qui permet de distinguer facilement les interfaces aux implémentations. Nous verrons dans le prochain billet que cette règle de nommage nous servira aussi pour rendre inaccéssible cette classe aux autres Bundles (projet Java) dans un environnement OSGI.
Factory de Services
Créez la factory de services org.akrogen.gestcv.services.ServicesFactory comme suit :
import org.akrogen.gestcv.services.impl.UserServiceImpl;
public class ServicesFactory {
private static ServicesFactory INSTANCE = new ServicesFactory();
private UserService userService = null;
private ServicesFactory() {
}
public static ServicesFactory getInstance() {
return INSTANCE;
}
public UserService getUserService() {
if (userService == null) {
userService = createUserService();
}
return userService;
}
private UserService createUserService() {
UserService userService = new UserServiceImpl();
return userService;
}
}
La factory de services est un singleton qui a une méthode ServicesFactory#getUserService() qui retourne une implémentation de l’interface UserService.
org.akrogen.gestcv.simplemainclient
Créez le projet Java org.akrogen.gestcv.simplemainclient. Ajoutez les dépendances aux 2 projets org.akrogen.gestcv.domain et org.akrogen.gestcv.services .
Créez la classe org.akrogen.gestcv.simplemainclient.SimpleMainClient comme suit :
import java.util.Collection;
import org.akrogen.gestcv.domain.User;
import org.akrogen.gestcv.services.ServicesFactory;
import org.akrogen.gestcv.services.UserService;
public class SimpleMainClient {
public static void main(String[] args) {
UserService userService = ServicesFactory.getInstance()
.getUserService();
Collection<User> users = userService.findAllUsers();
for (User user : users) {
System.out.println("User [login=" + user.getLogin() + ", password="
+ user.getPassword() + "]");
}
}
}
Ce Main Java utilise la méthode UserService#findAllUsers() pour afficher la liste de User. A cette étape on peut lancer le client qui appelle le service qui retourne la liste d’utilisateurs.
Run As – Java Application
Pour lancer le main de la classe SimpleMainClient, sélectionnez la classe, puis ouvrez le menu contextuel (en cliquant sur le bouton droit) et cliquez sur Run As / Java Application :
La console eclipse doit afficher la liste des utilisateurs :
User [login=djo, password=]
User [login=keulkeul, password=]
User [login=pascal, password=]
Problème Java Build Path
Les dépendances entre les projets est effectuées via le Java Build Path classique qui lorsque l’on lance le Main de SimpleMainClient construit le classpath avec ces projets. Nous allons voir les 2 problèmes avec ce type de dépendance.
UserServiceImpl Non protégé
Dans la classe org.akrogen.gestcv.simplemainclient.SimpleMainClient, si vous tentez d’utiliser la classe UserServiceImpl, vous pourrez constater que l’autocomplétion propose cette classe :
Cette classe ne devrait jamais être utilisée par une classe autre que celle de la factory de Service. A ce stade la classe SimpleMainClient peut utiliser UserServiceImpl. Ceci est dangereux surtout si la version suivante du projet
org.akrogen.gestcv.services effectue un refactoring en renommant UserServiceImpl ou en changeant ces méthodes.
En pur Java il est impossible de régler ce problème si ce n’est de déclarer la classe UserServiceImpl en private et de la mettre dans le même package que la classe ServicesFactory. Mais cette solution ne permet plus de mettre les classes dans des packages souhaités ce qui nous ramenerait à avoir un seul niveau de package pour toutes les interfaces et implémentations.
ClassLoader unique
Un ClassLoader permet comme son nom l’indique de charger des classes . Dans notre cas les 3 projets partagent le même ClassLoader. Pour s’en rendre compte nous allons afficher le ClassLoader dans la console Eclipse.
Modifiez la classe SimpleMainClient comme suit :
System.out.println("SimpleMainClient ClassLoader : " + SimpleMainClient.class.getClassLoader());
...
}
et modifiez la classe ServicesFactory comme suit :
System.out.println("ServicesFactory ClassLoader : " + ServicesFactory.class.getClassLoader());
}
Relancez et vous verrez dans la console :
ServicesFactory ClassLoader : sun.misc.Launcher$AppClassLoader@133056f
qui montre que les 2 classes partagent le même ClassLoader.
Ceci est problématique car chaque projet ne peut pas avoir sa propre version de jar d’une librairie donnée. Nous allons illustrer le problème en utilisant 2 versions différentes de la librairie Apache commons-lang*.jar dans 2 projets :
Ce schéma montre que :
- le projet Java Client org.akrogen.gestcv.simplemainclient pointera sur la librairie commons-lang-1.0.jar de version 1.0. Cette librairie est stockée dans le repertoire lib (qu’il faut créer) du projet.
- le projet Java Services org.akrogen.gestcv.services pointera sur la librairie commons-lang-2.4.jar de version 2.4. Cette librairie est stockée dans le repertoire lib (qu’il faut créer) du projet.
Vous pouvez télécharger le zip org.akrogen.gestcv_step1-commons-lang.zip qui contient les projets avec les librairies commans-lang*.jar.
La différence entre ces 2 versions de commans-lang*.jar est que la classe utilitaire org.apache.commons.lang.StringUtils possède une méthode StringUtils#isBlank(String String) dans la version 2.4 et pas dans la version 1.0. Nous souhaitons utiliser StringUtils#isBlank(String String) dans la couche Services.
Java Build Path/Add Jar (commons-lang-2.4.jar)
Ici nous allons ajouter au projet org.akrogen.gestcv.services la librairie jar commons-lang-2.4.jar. Pour cela sélectionnez le projet org.akrogen.gestcv.services puis cliquez sur le bouton droit. Sélectionnez l’item Properties puis le noeud Java Build Path puis sélectionnez l’onglet Libraries :
Cliquez sur le bouton Add JARs…
Puis cliquez sur OK, la librairie est ajoutée :
Utilisation commons dans la couche Services
Modifiez le code de ServicesFactory pour utiliser la méthode utilitaire StringUtils#isBlank(String str) de commons-lang-2.4.jar :
private ServicesFactory() {
StringUtils.isBlank("");
}
...
Java Build Path/Add Jar (commons-lang-1.0.jar)
Ajoutez la librairie commons-lang-1.0.jar dans le projet org.akrogen.gestcv.simplemainclient
Puis changez l’ordre de chargement des librairies pour que la librairie commons-lang-1.0.jar soit chargé AVANT le projet org.akrogen.gestcv.services (Services), autrement dit avant le chargement de la libraire commons-lang-2.4.jar :
Relancez le Main SimpleMainClient et vous aurrez l’erreur suivante :
at org.akrogen.gestcv.services.ServicesFactory.<init>(ServicesFactory.java:23)
at org.akrogen.gestcv.services.ServicesFactory.<clinit>(ServicesFactory.java:19)
at org.akrogen.gestcv.simplemainclient.SimpleMainClient.main(SimpleMainClient.java:23)
La méthode StringUtils.isBlank(String str) n’est pas trouvée car la librairie commons-lang-1.0.jar est chargée AVANT la librairie commons-lang-2.4.jar. Il est donc impossible de faire cohabiter 2 versions différentes avec des dépendances classiques. Ce problème se trouve aussi dans les applications WEB lorsque l’application WEB a une version d’un JAR et que le serveur (Tomcat…) a une autre version du même JAR.
Conclusion
Dans ce billet nous avons mis en place un client java qui appelle un service. Nous avons vu les 2 problemes posés par des dependances classiques que nous règlerons dans le prochain billet à l’aide de OSGI.
Articles récents
- Conception d’un client Eclipse RCP et serveur OSGI avec Spring DM [step5]
- Conception d’un client Eclipse RCP et serveur OSGI avec Spring DM [step4]
- Conception d’un client Eclipse RCP et serveur OSGI avec Spring DM [step3]
- Conception d’un client Eclipse RCP et serveur OSGI avec Spring DM [step2]
- Conception d’un client Eclipse RCP et serveur OSGI avec Spring DM [step1]
Commentaires récents
- Conception d’un Editeur Eclipse de workflow XML [step 0] dans
- Conception d’un Editeur Eclipse de workflow XML [step 19] dans
- Conception d’un Editeur Eclipse de workflow XML [step 7] dans
- Conception d’un Editeur Eclipse de workflow XML [step 7] dans
- Conception d’un Editeur Eclipse de workflow XML [step 7] dans