Conception d’un client Eclipse RCP et serveur OSGI avec Spring DM [step6]
Dans le billet précédant [step5] nous avons mis en place les 3 couches Client/Services/Domain découpées en plusieurs Bundles :
- Couche Domain gérée par le bundle org.dynaresume.domain.
- Couche Services gérée par les 2 bundles :
- API Services org.dynaresume.services qui contient l’interface UserService.
- Implémentation Services org.dynaresume.services.impl qui exporte UserServiceImpl, l’implémentation de l’interface UserService dans le registre de services OSGi.
- Couche Client qui consomme via le registre de services OSGi, le service UserServiceImpl, géré par le bundle org.dynaresume.simpleosgiclient.
Dans ce billet et le prochain nous allons montrer pas à pas l’interêt d’utiliser Spring Dynamic Module en nous concentrant sur le bundle org.dynaresume.services.impl qui a pour rôle d’enregistrer une instance UserServiceImpl dans le registre de services OSGi. Dans ce billet nous allons utiliser basiquement Spring . Voici un schéma de ce que nous allons effectuer dans ce billet :
Ce schéma montre que :
- La classe ServicesFactory est basée sur un fichier XML Spring applicationContext.xml qui permet de déclarer la classe UserServiceImpl. C’est le conteneur Spring qui s’occupera d’instancier cette classe. Spring joue le rôle dans notre cas de factory de services.
- la Target Platform doit être enrichie pour ajouter tous les bundles Spring.
- le bundle org.dynaresume.services.impl fera référence aux bundle Spring de la Target Platform via les dépendances Import Package.
Le but de ce billet est d’introduire Spring en montrant comment déclarer l’instanciation du service UserServiceImpl via Spring :
- dans un contexte non OSGi. Nous repartirons des projets du billet [step1] et notre factory de services ServicesFactory se basera sur Spring. Nous montrerons qu’il existe 2 manières de déclarer le service UserServiceImpl avec Spring:
- XML bean : la déclaration s’effectue tout en XML.
- @Service annotation : la déclaration s’effectue via l’annotation Spring @Service.
Nous verrons comment configurer Log4j dans un contexte non OSGi.
- dans un contexte OSGi. Nous partirons des projets du billet [step5] et notre factory de services ServicesFactory se basera sur Spring. Nous tenterons d’utiliser les 2 modes de déclarations en mettant en évidence toutes les problématiques de mise en oeuvre de Spring dans un contexte OSGi (problème lié au classloader). Attention, ici nous n’utiliserons pas encore Spring DM, mais uniquement Spring Framework et dans le prochain billet nous verrons comment Spring DM simplifie le code expliqué dans cette section.
Nous verrons comment configurer Log4j dans un contexte OSGi via un Fragment OSGi.
I. Spring, mais pourquoi?
Dans le projet DynaResume nous avons choisi d’utiliser Spring Dynamic Module qui permet entre autres de déclarer l’enregistrement/la consommation des services dans le registre de service OSGi. Ceci signifie que l’enregistrement/la consommation des services n’a plus besoin de s’effectuer programatiquement en utilisant l’API de OSGi (ServiceTracker….) mais déclarativement à l’aide de XML, (et avec des annotations Spring).
La spécification OSGi propose aussi la possibilité de déclarer les services OSGi avec Declarative Services. Pourquoi avons nous fait le choix d’utiliser Spring Dynamic Module au lieu de Declarative Services? Nous avons été séduit par Spring, car c’est « une solution tout en un ». En effet nous n’avons pas encore abordé les autres problématiques concernant le développement d’un client RCP et serveur OSGi, mais Spring est capable de gérer aussi les problématiques suivantes:
- Avoir accès aux puissantes fonctionnalités de DI (Dependency Injection) et d’AOP de Spring pour configurer ses services
- le remoting (client/serveur) qui peut être géré via Spring remoting.
- lla gestion des transactions de base de données qui peut être gérée via AOP de Spring.
- la sécurité qui peut être géré via Spring Security.
I. JARs Spring
Distribution (Spring DM)
Vous pouvez trouvez les JARs de Spring utilisés dans ce billet dans le zip org.dynaresume_step6_spring-target-platform.zip. Mais je vous conseille de télécharger les JARs de Spring sur le site officiel de Spring, expliqué ci dessus.
Dans ce billet nous allons utiliser Spring dans un contexte sans OSGi et dans un contexte avec OSGi. Spring est découpé en plusieurs JARs (spring-core.jar…) que nous devons télécharger. Dans le cas d’un contexte sans OSGi, nous aurrions pu télécharger les JARs standard de la distribution Spring Framework. Mais nous n’allons pas utiliser cette distribution mais celle de Spring Dynamic Modules qui contient aussi les même JAR (spring-core.jar) mais OSGifier, autrement dit chacun de ces jars contiennent en plus un fichier MANIFEST.MF enrichit avec les méta données OSGi (dépendances, packages exportés…). Ceci nous montrera qu’il est possible d’utiliser un JAR OSGifier dans un contexte non OSGi.
Installation Spring DM
Il existe plusieurs manières de récupérer les JARs de Spring DM :
- à l’aide du plugin Eclipse Spring IDE
- à l’aide de Maven
- en téléchargeant un zip de la distribution de Spring DM
Téléchargement (Spring DM)
Dans la page de téléchargement des distributions de Spring, téléchargez la distribution Spring Dynamic Modules (la version actuelle est Current GA release – 1.2.0), ceci ouvre le formulaire suivant qui demande des informations pour pouvoir ensuite télécharger Spring :
Après avoir rempli vos informations personnelles et cliquez sur le bouton Acess Download, ou plus simple encore, sauter cette étape en cliquant sur le lien « (I’d rather not fill in the form. Just take me to the download page)« , la page qui propose les liens de téléchargement sur Spring DM s’affiche :
A l’heure actuelle, la dernière version de Spring Dynamic Modules est Current GA release – 1.2.0. Téléchargez la distribution (avec dépendances) spring-osgi-1.2.0-with-dependencies.zip
Contenu distribution (Spring DM)
Ce zip contient plusieurs répertoires dont :
- lib qui contient tous les JArs de Spring (Framework) et ces dépendances.
- dist qui contient tous les JArs de Spring Dynamic Module.
Spring (contexte sans OSGi)
Le bundle org.dynaresume.services.impl enregistre dans le registre de services OSGi une instance de UserServiceImpl récupéré par la classe ServicesFactory. Dans ce billet nous allons nous concentrer sur cette factory de services qui aujourd’hui s’occupe d’instancier l’instance de UserServiceImpl programmatiquement :
public UserService getUserService() { if (userService == null) { userService = createUserService(); } return userService; } private UserService createUserService() { UserService userService = new UserServiceImpl(); return userService; }
Nous allons montrer qu’avec Spring il est possible de gérer l’instanciation des service déclarativement via un fichier XML Spring de configuration. Je ne vais pas faire un tutorial sur Spring car la documentation officielle est très détaillé et il existe une multitude de documentation sur Internet. Et ne vous arrêtez surtout pas à l’idée que Spring permet uniquement de déclarer en XML ses services!!! Il permet beaucoup plus que ca. Spring est un conteneur léger basé sur les principes de AOP et IOC. Cette définition effraie au début mais je tenterais d’expliquer ses concepts au fur et à mesure dans mes billets.
Dans cette section nous allons présenter les 2 mode de déclarations :
- XML bean : la déclaration s’effectue tout en XML.
- @Service annotation : la déclaration s’effectue via l’annotation Spring @Service.
Initialisation Spring
Nous allons partir des projets org.dynaresume_step1.zip du billet [step1] et modifier le projet Java org.dynaresume.services.
Nous allons ajouter les Jars de Spring via Java Build Path. Pour cela :
- Créer un répertoire lib dans le projet java org.dynaresume.services
- copiez coller dans le répertoire lib les libraires Jars Spring suivantes de la distribution Spring :
Jar Description lib/com.springsource.slf4j.api-1.5.0.jar L’API public de SLF4J lib/com.springsource.slf4j.log4j-1.5.0.jar L’implémentation de SLF4J qui utilise log4j pour faire le logging lib/com.springsource.slf4j.org.apache.commons.logging-1.5.0.jar Un remplaçant d’Apache commons-logging (réputé pour ses problèmes liés au classloading) qui utilise SLF4J pour le logging lib/log4j.osgi-1.2.15-SNAPSHOT.jar La version OSGifié de log4j lib/org.springframework.beans-2.5.6.A.jar Extensions à spring (Spring Framework) lib/org.springframework.context-2.5.6.A.jar Extensions à spring (Spring Framework) lib/org.springframework.core-2.5.6.A.jar Extensions à spring (Spring Framework) - Ajoutez ces libraires au classpath du projet :
XML bean
Vous pouvez télécharger org.dynaresume_step6-spring-standalone.zip qui contient le code expliqué ci-dessous.
Créez le fichier XML Spring de configuration applicationContext.xml dans le package org.dynaresume.services :
<?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.5.xsd"> <bean id="userService" class="org.dynaresume.services.impl.UserServiceImpl"></bean> </beans>
Cette configuration permet de définir un bean d’ID userService de classe org.dynaresume.services.impl.UserServiceImpl. Ce fichier XML Spring peut ensuite être chargé dans le contexte d’application Spring. Ce contexte est représenté par l’interface org.springframework.context.ApplicationContext qui hérite de org.springframework.beans.factory.BeanFactory qui possède entre autres la méthode :
Object ApplicationContext#getBean(String name)
qui permet de retourner une instance d’une classe définit dans le fichier XML Spring chargé. Par exemple dans notre cas, pour récupérer une instance de UserServiceImpl, nous devons faire :
UserService UserService = (UserService)applicationContext.getBean("userService")
Voici les classes implémentant l’interface org.springframework.context.ApplicationContext :
Ce schéma met en évidence 2 implémentations :
- org.springframework.context.support.ClassPathXmlApplicationContext, qui permet de charger un fichier XML Spring de configuration stocké dans un package (c’est ce que que nous allons utiliser).
- org.springframework.context.support.FileSystemXmlApplicationContext, qui permet de charger un fichier XML Spring de configuration stocké dans un répertoire
Modifiez la classe org.dynaresume.services.ServicesFactory comme suit :
package org.dynaresume.services; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; public class ServicesFactory { private static ServicesFactory INSTANCE = new ServicesFactory(); private ApplicationContext applicationContext = null; private ServicesFactory() { } public static ServicesFactory getInstance() { return INSTANCE; } public UserService getUserService() { return (UserService) getApplicationContext().getBean("userService"); } private ApplicationContext getApplicationContext() { if (applicationContext == null) { initalizeApplicationContext(); } return applicationContext; } private void initalizeApplicationContext() { try { applicationContext = new ClassPathXmlApplicationContext( "org/dynaresume/services/applicationContext.xml"); } catch (Throwable e) { e.printStackTrace(); } } }
Le code
private void initalizeApplicationContext() { try { applicationContext = new ClassPathXmlApplicationContext( "org/dynaresume/services/applicationContext.xml"); } catch (Throwable e) { e.printStackTrace(); } }
permet de charger la configuration XML Spring applicationContext.xml stocké dans le package org.dynaresume.services
Relancez et vous verrez dans la console :
log4j:WARN No appenders could be found for logger (org.springframework.context.support.ClassPathXmlApplicationContext).
log4j:WARN Please initialize the log4j system properly.
User [login=angelo, password=]
User [login=djo, password=]
User [login=keulkeul, password=]
User [login=pascal, password=]
Configuration Log4j
Le service a bien pu etre récupéré correctement via Spring, mais nous avons le warning log4j qui s’affiche :
log4j:WARN No appenders could be found for logger (org.springframework.context.support.ClassPathXmlApplicationContext).
log4j:WARN Please initialize the log4j system properly.
Pour y remedier, nous allons configurer Log4j correctement. pour cela créez dans le répertoire src du projet, le fichier de propriétés log4j.properties avec ce contenu :
log4j.rootLogger=info, con log4j.appender.con=org.apache.log4j.ConsoleAppender log4j.appender.con.layout=org.apache.log4j.PatternLayout log4j.appender.con.layout.ConversionPattern=%-4r [%t] %-5p %c %x - %m%n
Cette configuration basique de Log4j permet d’afficher dans la console les logs de niveau INFO.
Relancez et vous verrez dans la console :
0 [main] INFO org.springframework.context.support.ClassPathXmlApplicationContext - Refreshing org.springframework.context.support.ClassPathXmlApplicationContext@b42cbf: display name [org.springframework.context.support.ClassPathXmlApplicationContext@b42cbf]; startup date [Tue Nov 17 14:34:41 CET 2009]; root of context hierarchy
63 [main] INFO org.springframework.beans.factory.xml.XmlBeanDefinitionReader - Loading XML bean definitions from class path resource [org/dynaresume/services/applicationContext.xml]
219 [main] INFO org.springframework.context.support.ClassPathXmlApplicationContext - Bean factory for application context [org.springframework.context.support.ClassPathXmlApplicationContext@b42cbf]: org.springframework.beans.factory.support.DefaultListableBeanFactory@1c80b01
235 [main] INFO org.springframework.beans.factory.support.DefaultListableBeanFactory - Pre-instantiating singletons in org.springframework.beans.factory.support.DefaultListableBeanFactory@1c80b01: defining beans [userService]; root of factory hierarchy
User [login=angelo, password=]
User [login=djo, password=]
User [login=keulkeul, password=]
User [login=pascal, password=]
Log4j est configuré correctement. Sa configuration consiste à mettre le fichier de propriétés log4j.properties retrouvés dans le ClassLoader unique de l’application
.
@Service annotation
Vous pouvez télécharger org.dynaresume_step6-spring-standalone-annotations.zip qui contient le code expliqué ci-dessous.
Dans une vrai application, le nombre de services à déclarer peut devenir conséquent. Pour alléger le fichier XML Spring de configuration, Spring est capable de retrouver les services en les scrutant dans un package. Pour effectuez cela :
- il suffit d’indiquer le package de base des implémentation des services dans le fichier XML de configuration à l’aide de l’élement XML component-scan. Par exemple :
<context:component-scan base-package="org.dynaresume.services.impl" />
- indiquer l’ID du bean dans la classe d’implémentation de services à l’aide de l’annotation Spring org.springframework.stereotype.Service. Par exemple :
import org.springframework.stereotype.Service; @Service("userService") public class UserServiceImpl ...
Modifiez le fichier XML Spring de configuration applicationContext.xml dans le package org.dynaresume.services :
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-2.5.xsd"> <context:component-scan base-package="org.dynaresume.services.impl" /> </beans>
Ajoutez l’annotation Spring org.springframework.stereotype.Service dans la classe org.dynaresume.services.impl.UserServiceImpl en indiquant l’ID du bean avec la valeur userService :
package org.dynaresume.services.impl; import java.util.ArrayList; import java.util.Collection; import java.util.List; import org.dynaresume.domain.User; import org.dynaresume.services.UserService; import org.springframework.stereotype.Service; @Service("userService") public class UserServiceImpl implements UserService { ... }
Relancez et vous pourrez constatez que le service UserServiceImpl est instancié correctement via Spring.
Spring (contexte avec OSGi)
Dans cette section nous allons utiliser Spring dans un contexte OSGi et verrons que le ClassLoader/bundle pose problème et verrons comment y remédier. Nous allons partir des projets org.dynaresume_step5-import-package.zip du billet [step5] et modifier le bundle org.dynaresume.services.impl.
Target Platform
Dans cette section nous allons enrichir la Target Platform DynaResume Target Platform [step 2] pour ajouter les bundles Spring. Vous pouvez télécharger org.dynaresume_step6_spring-target-platform.zip du projet Simple Eclipse spring-target-platform qui contient :
- les JARs Spring nécéssaire pour notre Target Platform.
- le fichier target DynaResume Target Platform.target qui est la Target Platform que nous allons créé ci-dessous. Si jamais vous utilisez ce projet, les étapes suivantes ne sont pas nécéssaires. En effet Eclipse détecte l’existence de tous les fichiers *.target ouverts dans Eclipse et les affichent dans la page de Preferences des Target Platform. Il faut juste bien penser à activer cette target.
Avant de modifier le bundle org.dynaresume.services.impl pour utiliser Spring, nous devons ajouter à la Target Platform les bundles Spring nécéssaires qui sont les mêmes JARs utilisés dans la section Spring (contexte sans OSGi). Pour effectuer cela nous allons :
- Créer un projet Simple Eclipse qui contient les JArs Spring
- Référencer dans la Target Platform, les JArs de ce projet Eclipse
- Sauvegarder (si besoin) la Target Platform dans un fichier DynaResume Target Platform.target.
Remarque : ici j’utilise un projet Simple Eclipse qui contiennent les JArs Spring, mais ceux-ci peuvent être contenus dans un répertoire de votre disque sans que ca soit un projet Simple Eclipse.
Target Platform Project
Ici nous allons créer le projet Simple spring-target-platform pour cela sélectionnez le menu File/New/Other puis Project :
Cliquez sur Next et renseignez le champs Project name avec spring-target-platform :
Copiez collez dans le projet Eclipse créé, les JARs Spring suivant de la distribution spring-osgi-1.2.0-with-dependencies.zip :
Jar |
---|
lib/com.springsource.slf4j.api-1.5.0.jar |
lib/com.springsource.slf4j.log4j-1.5.0.jar |
lib/com.springsource.slf4j.org.apache.commons.logging-1.5.0.jar |
lib/log4j.osgi-1.2.15-SNAPSHOT.jar |
lib/org.springframework.beans-2.5.6.A.jar |
lib/org.springframework.context-2.5.6.A.jar |
lib/org.springframework.core-2.5.6.A.jar |
Le projet spring-target-platform est prêt à être référencé par la Target Platform.
Définition Target Platform
Ici nous allons modifier la Target Platform existante en ajoutant une référence sur les bundles Spring du projet Eclipse spring-target-platform.
Editez la Target Platform DynaResume Target Platform [step 2] en cliquant sur le bouton Edit… :
L’ecran suivant s’affiche :
Cliquez sur le bouton Add…, l’écran suivant s’affiche :
Sélectionnez Directory, puis Next, l’écran suivant s’affiche :
Saisissez ${workspace_loc}/spring-target-platform pour faire référence au projet Eclipse spring-target-platform du workspace.
Cliquez sur Finish :
Cliquez sur Finish puis sur Finish. La Target Platform est initialisé correctement avec les bundles Spring du projet spring-target-platform.
DynaResume Target Platform.target
Dans un projet il est important de pouvoir partager ses Target Platform (c’est ce que nous avons fait dans le projet DynaResume). Pour récupérer la Target Platform que nous venons de crééer, le seul moyen que j’ai trouvé est de la récupérer dans le répertoire .metadata de votre workspace.
Accédez au repertoire .metadata\.plugins\org.eclipse.pde.core\.local_targets de votre workspace. Le répertoire .metadata contient toutes les méta données des plugin Eclipse. PDE stocke les targets définies dans les Prefrences dans le répertoire .local_targets :
Dans ce répertoire, vous trouverez plusieurs fichiers *.target. Pour connaître le fichier de la target correspondant à DynaResume Target Platform, vous pouvez ouvrir les fichiers *.target dans Eclipse qui ouvrira un Editor de Target. Voici dans mon cas le fichier 1258470547375.target ouvert dans l’Editor :
Vous pouvez copier coller ce fichier et le renommez en DynaResume Target Platform.target.
XML bean
Dans cette section nous allons utiliser Spring dans le bundle OSGi org.dynaresume.services.impl en full XML. Vous pouvez télécharger org.dynaresume_step6-spring-osgi.zip qui contient le code expliqué ci-dessous.
Créez le fichier XML Spring de configuration applicationContext.xml dans le package org.dynaresume.services.internal :
<?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.5.xsd"> <bean id="userService" class="org.dynaresume.services.impl.UserServiceImpl"></bean> </beans>
Importez les packages org.springframework.context et
org.springframework.context.support pour avoir accès à l’interface org.springframework.context.ApplicationContext et la classe org.springframework.context.support.ClassPathXmlApplicationContext.
Modifiez la classe ServicesFactory pour utiliser Spring et charger le fichier applicationContext.xml comme ce que nous avons effectué dans un contexte sans OSGi :
package org.dynaresume.services.internal; import org.dynaresume.services.UserService; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; public class ServicesFactory { private static ServicesFactory INSTANCE = new ServicesFactory(); private ApplicationContext applicationContext = null; private ServicesFactory() { } public static ServicesFactory getInstance() { return INSTANCE; } public UserService getUserService() { return (UserService) getApplicationContext().getBean("userService"); } private ApplicationContext getApplicationContext() { if (applicationContext == null) { initalizeApplicationContext(); } return applicationContext; } private void initalizeApplicationContext() { try { applicationContext = new ClassPathXmlApplicationContext( "org/dynaresume/services/internal/applicationContext.xml"); } catch (Throwable e) { e.printStackTrace(); } } }
Le probléme de compilation apparaît :
The type org.springframework.core.io.support.ResourcePatternResolver cannot be resolved. It is indirectly referenced from required .class files
Importez le package org.springframework.core.io.support
Le probléme de compilation apparaît :
The type org.springframework.beans.factory.HierarchicalBeanFactory cannot be resolved. It is indirectly referenced from required .class files
Importez le package org.springframework.beans.factory
Le Bundle doit compiler. Avant de relancer, assurez vous que les les 3 bundles com.springsource.* et les 4 bundles org.springframework.* ajoutés à la Target Platform sont bien lancé via la configuration OSGi DynaResume en éditant la configuration OSGi DynaResume :
Relancez (via OSGi DynaResume) Equinox et la console OSGi affiche l’erreur suivante :
org.springframework.beans.factory.BeanDefinitionStoreException: IOException parsing XML document from class path resource [org/dynaresume/services/internal/applicationContext.xml]; nested exception is java.io.FileNotFoundException: class path resource [org/dynaresume/services/internal/applicationContext.xml] cannot be opened because it does not exist
...
Caused by: java.io.FileNotFoundException: class path resource [org/dynaresume/services/internal/applicationContext.xml] cannot be opened because it does not exist
...
La classe ClassPathXmlApplicationContext est incapable de retrouver le fichier XML de configuration dans le package du bundle. Ce problème s’explique par le fait que chaque bundle à son propre ClassLoader. la classe ClassPathXmlApplicationContext qui appartient au bundle org.springframework.context.support cherche ce fichier dans le ClassLoader de son bundle, ce qui explique l’erreur.
Pour résoudre ce problème, il faut indiquer à l’instance ClassPathXmlApplicationContext le ClassLoader à utiliser qui doit être celui du bundle org.dynaresume.services.impl. Pour cela modifier la méthode privée ServicesFactory#initalizeApplicationContext() comme suit :
private void initalizeApplicationContext() { try { ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext(); applicationContext.setConfigLocation("org/dynaresume/services/internal/applicationContext.xml"); applicationContext.setClassLoader(this.getClass().getClassLoader()); applicationContext.refresh(); this.applicationContext = applicationContext; } catch (Throwable e) { e.printStackTrace(); } }
Un problème de compilation apparaît :
Access restriction: The method setClassLoader(ClassLoader) from the type DefaultResourceLoader is not accessible due to restriction on required library D:\_Projets\Personal\workspace-gestcv-osgi-wordpress\step6\spring-target-platform\org.springframework.core-2.5.6.A.jar
Importe les package org.springframework.core.io. Le bundle doit compiler.
Relancez (via OSGi DynaResume) Equinox et la console OSGi affiche l’erreur suivante :
org.springframework.beans.factory.parsing.BeanDefinitionParsingException: Configuration problem: Unable to locate Spring NamespaceHandler for XML schema namespace [http://www.springframework.org/schema/context]
Offending resource: class path resource [org/dynaresume/services/internal/applicationContext.xml]
...
Lors du chargement de la configuration Spring, le schema XML n’est pas trouvé car il est dans un autre Classloader. La solution trouvée à ce jour est de désactiver la validation du schéma XML. Pour cela créez la classe org.dynaresume.services.internal.MyClassPathXmlApplicationContext:
package org.dynaresume.services.internal; import org.springframework.beans.factory.xml.XmlBeanDefinitionReader; import org.springframework.context.support.ClassPathXmlApplicationContext; public class MyClassPathXmlApplicationContext extends ClassPathXmlApplicationContext { @Override protected void initBeanDefinitionReader( XmlBeanDefinitionReader beanDefinitionReader) { beanDefinitionReader .setValidationMode(XmlBeanDefinitionReader.VALIDATION_NONE); } }
Le problème de compilation apparaît, pour résoudre cela Importez le package org.springframework.beans.factory.xml.
Modifiez la méthode ServicesFactory#initalizeApplicationContext() pour utiliser notre classe MyClassPathXmlApplicationContext :
private void initalizeApplicationContext() { try { ClassPathXmlApplicationContext applicationContext = new MyClassPathXmlApplicationContext(); applicationContext.setConfigLocation("org/dynaresume/services/internal/applicationContext.xml"); applicationContext.setClassLoader(this.getClass().getClassLoader()); applicationContext.refresh(); this.applicationContext = applicationContext; } catch (Throwable e) { e.printStackTrace(); } }
Relancez (via OSGi DynaResume) Equinox et la console OSGi affiche :
osgi> Start Bundle [org.dynaresume.domain]
Start Bundle [org.dynaresume.services]
Start Bundle [org.dynaresume.simpleosgiclient]
--- Get UserService from OSGi services registry with ServiceTracker ---
Cannot get UserService=> UserService is null!
Start Bundle [org.dynaresume.services.impl]
log4j:WARN No appenders could be found for logger (org.springframework.core.io.support.PathMatchingResourcePatternResolver).
log4j:WARN Please initialize the log4j system properly.
--- Get UserService from OSGi services registry with ServiceTracker ---
User [login=angelo, password=]
User [login=djo, password=]
User [login=keulkeul, password=]
User [login=pascal, password=]
Cette trace montre que :
- Les 4 bundles sont lancés (trace de type Start Bundle)
- le service UserService n’est pas publié :
--- Get UserService from OSGi services registry with ServiceTracker ---
- Warning log4j qui indique que Log4j n’est pas configuré :
log4j:WARN No appenders could be found for logger (org.springframework.core.io.support.PathMatchingResourcePatternResolver).
log4j:WARN Please initialize the log4j system properly.
- le service UserService est ensuite publié et peut être consommé :
--- Get UserService from OSGi services registry with ServiceTracker ---
User [login=angelo, password=]
User [login=djo, password=]
User [login=keulkeul, password=]
User [login=pascal, password=]
Log4j Fragment
Dans la console nous avons le message d’avertissement :
log4j:WARN No appenders could be found for logger (org.springframework.core.io.support.PathMatchingResourcePatternResolver).
log4j:WARN Please initialize the log4j system properly.
Ce qui signifie que Log4j n’est pas configuré. La configuration de log4j dans un contexte OSGi ne peut pas s’effectuer comme dans un contexte non OSGi car il n’y a pas de ClassLoader unique. Cette configuration s’effectue via un Fragment OSGi. Ce procédé est aussi valable pour d’autres bundles qui demande uen configuration (ex: paramétrage de la base de données).
Ici nous allons créer le Fragment (projet Plug-in) org.dynaresume.config.log4j. Pour cela sélectionnez le menu File/New/Other puis Plug-in Development/Fragment :
Cliquez sur le bouton Next, puis :
- renseignez le champs Project name par org.dynaresume.config.log4j.
- sélectionnez dans la combo standard car notre Fragment n’a pas besoin de fonctionnalités spécifiques à Equinox (conteneur OSGi aussi utilisé pour les Plug-in Eclipses). Il pourra ainsi fonctionner sur n’importe quel conteneur OSGI.
Cliquez sur le bouton Next, le wizard qui permet de configurer le Fragment s’ouvre en pré-remplissant la plupart des champs. Voici une copie d’écran de ce wizard où j’ai mis en rouge ce que j’ai modifié :
- le champs ID est l’identifiant de notre Fragment. Il est pré-rempli en utilisant le nom du projet.
- le champs Version est la version de notre Fragment. Il est pré-remplit avec 1.0.0.qualifier.
- le champs Name est le nom de notre Fragment. Cette information n’est pas très importante. Dans ce billet je l’ai renseigné avec DynaResume Log4j Config
- le champs Provider est généralement le nom de la société qui créé le Fragment. Cette information n’est pas très importante.
- le champs Execution Environment indique la version minimale de la JRE pour que le Fragment puisse être éxécuté.
Cliquez sur le bouton Browse…. puis sélectionnez le bundle org.springframework.osgi.log4j.osgi
Cliquez sur OK :
Le Host est renseigné avec org.springframework.osgi.log4j.osgi. Cliquez sur Finish, le fragment est créé dans le workspace
Créez le fichier log4j.properties dans le répertoire src du fragment :
log4j.rootLogger=info, con log4j.appender.con=org.apache.log4j.ConsoleAppender log4j.appender.con.layout=org.apache.log4j.PatternLayout log4j.appender.con.layout.ConversionPattern=%-4r [%t] %-5p %c %x - %m%n
Vérifiez que le bundle est bien coché dans le Run Configuration, puis, Relancez (via OSGi DynaResume) Equinox. La console OSGi affiche la trace suivante :
osgi> Start Bundle [org.dynaresume.domain]
Start Bundle [org.dynaresume.services]
Start Bundle [org.dynaresume.simpleosgiclient]
--- Get UserService from OSGi services registry with ServiceTracker ---
Cannot get UserService=> UserService is null!
Start Bundle [org.dynaresume.services.impl]
0 [Start Level Event Dispatcher] INFO org.dynaresume.services.internal.MyClassPathXmlApplicationContext - Refreshing org.dynaresume.services.internal.MyClassPathXmlApplicationContext@2ab653: display name [org.dynaresume.services.internal.MyClassPathXmlApplicationContext@2ab653]; startup date [Tue Nov 17 20:39:54 CET 2009]; root of context hierarchy
46 [Start Level Event Dispatcher] INFO org.springframework.beans.factory.xml.XmlBeanDefinitionReader - Loading XML bean definitions from class path resource [org/dynaresume/services/internal/applicationContext.xml]
78 [Start Level Event Dispatcher] INFO org.dynaresume.services.internal.MyClassPathXmlApplicationContext - Bean factory for application context [org.dynaresume.services.internal.MyClassPathXmlApplicationContext@2ab653]: org.springframework.beans.factory.support.DefaultListableBeanFactory@4bfe6b
93 [Start Level Event Dispatcher] INFO org.springframework.beans.factory.support.DefaultListableBeanFactory - Pre-instantiating singletons in org.springframework.beans.factory.support.DefaultListableBeanFactory@4bfe6b: defining beans [userService]; root of factory hierarchy
Cette trace montre que Log4j est configuré correctement. Sa configuration consiste à créer un Fragment OSGi et mettre le fichier de propriétés log4j.properties dans ce fragment.
@Service annotation
Dans cette section je souhaitais utiliser les annotations @Service comme ce que nous avons pu faire dans un contexte non OSGi. Malheureusement je n’ai pas réussi à faire marcher les annotations @Service dans un contexte OSGi. Spring n’arrive pas a retrouver les classes. Nous verrons dans le prochain billet que Spring Dynamic Module permet d’utiliser ce mode de déclaration des services via l’annotation @Service. Je mets quand même les explications de ce qu’il faut faire pour utiliser les annotations @Service.
Créez le fichier de configuration Spring applicationContext.xml dans le package org.dynaresume.services.internal avec le contenu suivant :
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-2.5.xsd"> <context:component-scan base-package="org.dynaresume.services.impl" /> </beans>
Importez le package
org.springframework.stereotype pour pouvoir utiliser l’annotation Spring org.springframework.stereotype.Service. Modifiez la classe UserServiceImpl pour l’annoter en tant que service Spring avec l’ID userService :
package org.dynaresume.services.impl; import java.util.ArrayList; import java.util.Collection; import java.util.List; import org.dynaresume.domain.User; import org.dynaresume.services.UserService; import org.springframework.stereotype.Service; @Service("userService") public class UserServiceImpl implements UserService { ... }
Conclusion
Dans ce billet nous avons introduit et montrer qu’il est possible de déclarer ses services en XML ou via l’annotation Spring @Service. Dans un contexte OSGi, nous avons vu que l’utilisation de Spring n’est pas très simple dù à la problématique des Classloader/bundle. Nous montrerons dans le prochain billet l’interêt de Spring Dynamic Module qui permet de déclarer facilement l’enregistrement/la consommation des services OSGi et simplifie grandement le développement des bundles qui doivent fournir/consommer des services OSGi via Spring.
Vous pouvez lire le billet suivant [step7].
Très intéressante série, bravo.
OSGi est une techno intéressante, que j’utilise beaucoup.
Le problème est que dans une application de taille raisonnable avec Spring DM et les service JEE OSGi, le nombre de bundles tend à exploser.
Chaque bundle doit etre considéré comme une application propre et les implications ne sont pas minces.
Un autre problème est la découverte de certains problèmes de classloading seulement au démarrage du framework, pas pendant le développement, ce qui réduit la productivité.
Les tests d’intégration de chaque bundle avec les autres, étape indispensable avant mise en production, ne sont pas simples à mettre en place.
Chaque bundle doit contenir statiquement la définition de son environnement de test et des versions des bundles associés, via la méthode getTestBundlesNames:
http://static.springsource.org/osgi/docs/2.0.0.M1/reference/html-single/#testing
Je pense que Apache Felix a une solution plus dynamique.
le dernier point concerne la disponibilité des services JEE standards sous forme de bundles OSGi (JTA, JPA, datasources, JMS)
JBoss y travaille:
http://jbossosgi.blogspot.com
et la spécification OSGi EE devrait etre disponible courant 2010.
Tout ceci fait d’OSGi une technologie prometteuse mais selon encore un peu jeune au niveau des outils de développement et de la robustesse en mode production.
Bonjour Fred,
Merci beaucoup pour vos encouragements. Je découvre petit à petit OSGi et Spring DM à travers la rédaction de mes billets. Je n’ai actuellement jamais eu de vrai projet avec ces technologies et mon but est de me former dessus car je pense que ce sont des technologies prometteuses.
C’est d’ailleurs le but de mes billets: mettre le maximum de retour d’experiences (comme ce que vous avez fait), de problèmes rencontrés avec OSGi pour pouvoir démarrer un vrai projet en ayant conscience des problématiques engendrées par OSGi (j’ai encore beaucoup d’étude à faire sur ce point).
Mais nous allons tenter de mettre en oeuvre OSGi, Spring DM dans un vrai projet Open Source avec DynaResume http://code.google.com/p/dynaresume/ ou 2 de mes partenaires utilisent OSGi dans leur vie professionnelle, ce qui me rend la tâche plus facile pour la rédaction de mes billets.
En tout cas merci beaucoup pour vos retours.
Merci pour le tuto,jusqu’à maintenant mis à part des soucis de Unable to load UI activator qua je n’ai pas pu régler.De mon côté, j’ai commencé à suivre les billets depuis 3 jours et pour ce step j’ai utilisé la version spring-osgi-2.0.0.M1-with-dependencies.zip au lieu de 1.2 et quand on lance OSGI Dynaresume on obtient un message d’erreur java.lang.NoClassDefFoundError: org/springframework/asm/ClassVisitor. Dans ce cas, il faut ajouter org.springframework.asm dans ${workspace_loc}/spring-target-platform, reloader la target platform et faire un « add required bundle » dans le run configuration.De même, il faut aussi rajouter org.springframework.expression car la résolution de java.lang.NoClassDefFoundError: org/springframework/asm/ClassVisitor entraîne une nouvelle erreur, java.lang.NoClassDefFoundError: org/springframework/expression/PropertyAccessor. Refaire le même processus. En espérant que celà puisse aider d’autres personnes 🙂
Bonjour dida,
Merci pour ces informations.
Angelo