Programmation par composant avec la technologie OSGi (1ère partie)
Date de publication : 10/01/2008 , Date de mise à jour : 01/02/2008
Par
Thierry Templier (co-auteur du livre Spring par la pratique)
Cette série d'articles décrit la mise en oeuvre de la programmation orientée composant et d'architectures orientées
service en se fondant sur la technologie OSGi. Nous y détaillerons les différents concepts de cette
technologie afin de permettre sa prise en main.
1. Introduction
2. Programmation orientée composant et architecture orientée service avec OSGi
3. Architecture OSGi
3.1. Gestion des composants OSGi
3.2. Cycle de vie des bundles
3.3. Services
3.4. Conclusion
4. Bundle OSGi
4.1. Caractéristiques d'un bundle
4.2. en-têtes OSGi du fichier MANIFEST.MF
5. Interactions entre bundles
5.1. Mise à disposition de packages
5.2. Importation de packages
6. Principales API OSGi
6.1. BundleActivator - Entité d'activation
6.2. BundleContext - Contexte de composant
7. Mise en oeuvre de services
8. Conclusion
9. Bibliographie
1. Introduction
Ce premier article a pour objectif de fournir une introduction à la technologie OSGi (Open Service
Gateway Initiative), technologie qui après avoir été longtemps utilisée dans le monde de l'embarqué est
de plus en plus mise en oeuvre dans les applications classiques et serveurs.
Nous verrons dans un premier temps les différents concepts relatifs à OSGi et détaillerons à
quelles problématiques cette technologie s'adresse tout en soulignant ses apports en terme
d'architecture et de structuration des applications.
Nous rentrerons par la suite dans les aspects techniques d'OSGi afin de détailler ses caractéristiques
et de voir concrètement comment mettre en oeuvre cette technologie au sein d'applications Java. Nous
laisserons le soin à un prochain article de décrire la manière d'utiliser OSGi dans des applications
Java EE tout en se fondant sur des bibliothèques et frameworks Java EE.
2. Programmation orientée composant et architecture orientée service avec OSGi
Avant d'entrer dans le coeur de la technologie, nous allons détailler les caractéristiques de
la programmation orientée composant et des architectures orientée service afin d'identifier
les problématiques qu'elles visent à résoudre. Nous verrons alors que la technologie OSGi adresse
ces deux types de technologies.
Tout d'abord, la programmation orientée composant [1] vise à adresser les
limitations de la programmation orientée objet [2]. Cette dernière offre d'intéressants
mécanismes afin de modulariser les traitements et les rendre réutilisables mais n'apporte aucun support
afin de mettre en relation les classes. De plus, plus les traitements de l'application augmentent et se
complexifient, plus ces limitations sont visibles et pénalisantes au niveau de la maintenabilité et de
l'évolutivité de l'application.
Ainsi, cet aspect peut devenir très problématique lors de la mise en oeuvre de réseaux complexes d'objets.
En effet, cela se traduit très souvent par des couplages forts entre objets, ce point nuisant énormément
à la réutilisabilité des traitements. Chaque développeur a alors le choix des outils afin d'adresser
au mieux au sein de ses applications ces aspects. L'approche la plus adaptée consiste en la
mise en oeuvre du patron de conception injection de dépendance par l'intermédiaire de frameworks tels
que Spring [3] utilisés conjointement avec la programmation par interface.
Nous retrouvons ce problème notamment au niveau des instanciations des objets, directement ou
par l'intermédiaire de l'introspection. En effet, même avec la programmation par interface, la classe
appelante est liée à la classe appelée. Les patrons de conception tels que les fabriques [4]
et l'injection de dépendance [5] permettent d'adresser cette problématique en déléguant les
traitements d'instanciation à une entité autonome (respectivement la fabrique et le conteneur).
En parallèle de la programmation orientée composant, les architectures orientées service peuvent être
mises en oeuvre afin de mettre à disposition des traitements tout en diminuant les couplages entre les
briques techniques. La SOA (Service Oriented Architecture) [6] n'implique pas
nécessairement l'utilisation des services Web avec les technologies SOAP [7] et
WSDL [8] et peut être mise en oeuvre au sein d'un même processus Java notamment
avec des conteneurs légers (entités mettant en oeuvre l'injection de dépendances) tels que Spring
[3] ou Hivemind [9]. Dans ce contexte, nous parlons de fournisseur de services
et de consommateurs de services, les fournisseurs mettant à disposition des composants. La programmation orientée
composant [1] et les architectures orientées service peuvent donc être mises en
oeuvre de manière complémentaire afin de bénéficier des avantages des deux types de technologies.
La figure suivante illustre différents composants exposant des services par l'intermédiaire d'interfaces
et reliés entre eux par des services:
Il est à noter que des composants OSGi peuvent être également reliés entre eux par l'intermédiaire
des packages, packages qu'ils exportent et importent. Dans ce cas, les services ne sont donc pas
l'unique manière de les relier.
Dans ce contexte, la technologie OSGi vise à adresser les différentes problématiques vues précédemment,
problématiques récapitulées ci-dessous:
- Adresser les limitations de la programmation orientée objet;
- Permettre la gestion des applications complexes et de taille importante;
- Améliorer la qualité de service des applications en permettant une administration à chaud;
- Permettre la mise en oeuvre d'architectures orientées service légères.
Une des autres caractéristiques de la technologie OSGi est sa portabilité puisqu'elle peut être
mise en oeuvre aussi bien dans des terminaux de manière embarquée que dans des applications classiques ou
serveurs. Le premier aspect a été à la base d'OSGi, les seconds types d'application s'étant développés
ces dernières années par l'intermédiaire d'outils tels qu'Eclipse [10]. Le fait qu'OSGi repose sur la
technologie Java pour son exécution a été également un important facteur de portabilité.
3. Architecture OSGi
Dans cette section, nous allons décrire les caractéristiques des conteneurs et composants OSGi. Le
composant correspond à l'entité centrale de la technologie et désignée par le terme bundle avec la
terminologie de cette dernière. Nous verrons également les spécificités du cycle de vie des bundles,
la manière de les gérer au niveau du conteneur ainsi que la façon de les configurer par l'intermédiaire
de leur descripteur de déploiement.
La figure suivante décrit les différentes briques de l'architecture de la technologie OSGi, briques
que nous allons détaillées conceptuellement tout au long de cette section puis techniquement dans
le reste de l'article:
La couche Module adresse la gestion des bundles, aussi bien au niveau du chargement des
classes (classloading) que de la gestion de leurs visibilités et de leurs versions. La couche Cycle de
vie (life cycle) prend quant à elle en charge les états des bundles supportés par le conteneur
ainsi que les API correspondantes. Pour finir, la couche Services offre la possibilité de
mettre à disposition des services au sein d'une même machine virtuelle tout en masquant leurs implémentations
à leurs utilisateurs. Les applications OSGi peuvent se fonder et tirer parti de ces différentes couches
afin de mettre en oeuvre des composants et des services.
3.1. Gestion des composants OSGi
La couche module permet la mise en oeuvre des bundles dans le conteneur. Elle
a la responsabilité de la gestion des différents chargeurs de classes et des versions
des dépendances.
En effet, une des caractéristiques de la technologie consiste en l'isolation des
classloaders des composants. En effet, chaque composant ou bundle OSGi dispose
d'un classloader indépendant pour ses traitements. Cet aspect permet par exemple la mise en
oeuvre dans deux bundles différents d'une même bibliothèque avec deux versions différentes
et incompatibles.
Cet aspect offre la possibilité à OSGi de complètement maîtriser les classes et packages
du composant visibles depuis l'extérieur et visibles par les autres composants. Par défaut,
la stratégie est la plus restrictive possible, à savoir que rien n'est visible. Par contre,
lors de la mise à disposition d'un package d'un composant, un numéro de version spécifique peut
être spécifié. Ainsi, deux versions de packages peuvent coexister dans le conteneur OSGi
sans aucun problème.
3.2. Cycle de vie des bundles
Comme dans tout conteneur, les éléments contenus dans ce dernier possèdent un cycle de vie bien
particulier puisque le conteneur OSGi a la responsabilité de les gérer. Nous pouvons distinguer
deux grandes parties dans les différents états supportés:
- Non opérationnel. Les états correspondent à la présence du bundle dans le
conteneur, mais ce dernier n'est pas utilisable par les autres bundles pour leurs traitements;
- Opérationnel. Les états de ce type correspondent aux moments où le bundle
est utilisable ou en phase de l'être ou de ne plus l'être.
La figure suivante illustre les différents états de gestion des bundles par le conteneur ainsi
que leurs enchaînements possibles:
Nous verrons par la suite que le conteneur OSGi offre une API standardisée afin
de gérer le cycle de vie des composants. Veuillez vous reporter à la section 5.2 BundleContext pour
plus de précisions. De plus, ces différents états sont mis en oeuvre par le bundle manager
du conteneur qui a la responsabilité de gérer la plateforme.
Le tableau suivant récapitule les différents états supportés par le bundle manager
des conteneurs OSGi ainsi que leurs principales caractéristiques:
| Etat |
Descriptif |
|
Installé (installed)
|
Etat dans lequel se trouve un bundle juste après avoir été installé, la
résolution des dépendances n'ayant pas encore été réalisée.
|
|
Résolu (resolved)
|
Etat dans lequel se trouve un bundle après avoir été installé, la
résolution des dépendances ayant juste été réalisée.
|
|
En train de démarrer (starting)
|
Etat dans lequel se trouve un bundle lorsqu'il est en train d'être démarré. Cet
état correspond à un état transitoire entre les événements Résolu et Actif.
|
|
Actif (active)
|
Etat dans lequel se trouve un bundle lorsqu'il a été démarré avec succès. Le bundle
ainsi que les services qu'il expose sont disponibles pour les autres bundles.
|
|
En train de s'arrêter (stopping)
|
Etat dans lequel se trouve un bundle lorsqu'il est en train d'être arrêté. Cet
état correspond à un état transitoire entre les événements Actif et Résolu.
|
|
Désinstallé (uninstalled)
|
Etat dans lequel se trouve un bundle une fois qu'il a été désinstallé.
|
Nous pouvons noter qu'une entité peut être associée au cycle de vie des bundles
et correspond à l'entité d'activation, entité appelée lors du démarrage et de l'arrêt d'un bundle.
Cette entité peut être mise en oeuvre par l'intermédiaire de l'interface BundleActivator et configurée
avec l'en-tête Bundle-Activtator. Nous reviendrons plus en détail sur ce mécanisme dans la section
5.1 BundleActivator.
Nous pouvons noter que la plupart des conteneurs OSGi fournissent des outils afin de gérer
le cycle de vie des bundles et de visualiser leurs états. Dans cette optique, le conteneur OSGi
Felix [11] offre une console d'administration en ligne de commande possédant les
commandes suivantes:
| Commande |
Descriptif |
|
install
|
Installation de bundle(s).
|
|
ps
|
Affichage de la liste des bundles installés.
|
|
refresh
|
Rafraichissment des packages des bundles.
|
|
resolve
|
Tentative de résolution des bundles spécifiés.
|
|
services
|
Affichage de la liste des services enregistrés ou utilisés.
|
|
start
|
Démarrage de bundle(s).
|
|
stop
|
Arrêt de bundle(s).
|
|
uninstall
|
Désinstallation de bundle(s).
|
|
update
|
Mise à jour d'un bundle.
|
Avec la console de cet outil, le conteneur peut être complètement administré à chaud en ligne de commandes.
Le scénario suivant peut être mis en oeuvre afin d'installer un bundle, de le démarrer, puis l'arrêter avec
les commandes respectives install, start et stop. Le code suivant illustre concrètement
l'enchaînement de ces différentes commandes:
-> ps
START LEVEL 1
ID State Level Name
[ 0] [Active ] [ 0] System Bundle (1.0.0)
[ 1] [Active ] [ 1] Apache Felix Shell Service (1.0.0)
[ 2] [Active ] [ 1] Apache Felix Shell TUI (1.0.0)
-> install /home/templth/developpez/OSGi/simplebundle.jar
Bundle ID: 3
-> ps
START LEVEL 1
ID State Level Name
[ 0] [Active ] [ 0] System Bundle (1.0.0)
[ 1] [Active ] [ 1] Apache Felix Shell Service (1.0.0)
[ 2] [Active ] [ 1] Apache Felix Shell TUI (1.0.0)
[ 3] [Installed ] [ 1] Simple Bundle (1.0.1)
-> start 3
-> ps
START LEVEL 1
ID State Level Name
[ 0] [Active ] [ 0] System Bundle (1.0.0)
[ 1] [Active ] [ 1] Apache Felix Shell Service (1.0.0)
[ 2] [Active ] [ 1] Apache Felix Shell TUI (1.0.0)
[ 3] [Active ] [ 1] Simple Bundle (1.0.1)
-> stop 3
-> ps
START LEVEL 1
ID State Level Name
[ 0] [Active ] [ 0] System Bundle (1.0.0)
[ 1] [Active ] [ 1] Apache Felix Shell Service (1.0.0)
[ 2] [Active ] [ 1] Apache Felix Shell TUI (1.0.0)
[ 3] [Resolved ] [ 1] Simple Bundle (1.0.1)
|
3.3. Services
La couche Services offre la possibilité de mettre à disposition certains
traitements des composants par l'intermédiaire de services. Nous désignons par
le terme service des traitements structurés de la manière suivante. Il
s'agit d'une entité dont le contrat est clairement défini par l'intermédiaire
d'une interface Java. Seule cette interface est connue par les consommateurs du
service. La ou les implémentations du service doivent rester internes aux composants
et implémenter la précédente interface.
Le mécanisme de gestion des services est complètement dynamique. En effet, dès
qu'un service est enregistré, il est automatiquement utilisable par tous les
composants disponibles dans le conteneur OSGi. Comme nous le verrons par la suite,
une API est disponible afin d'enregistrer des implémentations de services. De plus,
par convention, il convient de faire correspondre le nom du service au nom de l'interface
implémentée.
Dans le cadre de la technologie OSGi, l'architecture orientée services a la caractéristique
d'être très "légère" et de pouvoir fonctionner de manière autonome dans un seul processus
Java. Elle correspond à un modèle de services dans une unique machine virtuelle (in-VM).
Nous verrons dans la section 5.1 comment mettre en oeuvre concrètement des services dans le cadre
de la technologie OSGi.
3.4. Conclusion
L'architecture de la technologie OSGi permet de mettre en oeuvre des composants et de mettre
à disposition des services applicatifs. Elle offre également un cycle de déploiement de services très
rapides par l'intermédiaire du cycle de vie des bundles tout en offrant une meilleure structuration
des fondations des applicatifs.
Nous pouvons remarquer que la sécurité est prévue en standard puisqu'elle fait partie intégrante
de la spécification OSGi. Elle offre la possibilité de spécifier des permissions d'accès aux entités
gérées par le conteneur. Une de ses principales caractéristiques consiste dans le fait que sa
configuration est complètement dynamique et ne nécessite aucun redémarrage après une modification.
Rentrons maintenant dans le détail technique de la technologie OSGi et voyons concrètement comment
la mettre en oeuvre.
4. Bundle OSGi
Dans cette section, nous allons détailler les différentes caractéristiques de l'entité
centrale d'OSGi, le composant ou bundle. Nous verrons comment le mettre en oeuvre par
l'intermédiaire d'un simple fichier JAR de Java.
4.1. Caractéristiques d'un bundle
Avec la technologie OSGi, le concept de composant est mis en oeuvre par l'intermédiaire
des bundles, un bundle correspondant à un composant. Ces derniers permettent de mettre en oeuvre
les différents concepts des composants et ce sont eux qui sont déployés dans les conteneurs OSGi.
Avec OSGi, un composant (bundle) est simplement stocké dans un fichier JAR de Java. Les
informations de déploiement sont spécifiées par l'intermédiaire du fichier standard MANIFEST.MF
présent dans le répertoire META-INF. En effet, OSGi reprend ce fichier en y ajoutant
différents en-têtes afin de configurer le composant.
Il n'est pas nécessaire d'ajouter d'autres éléments au fichier JAR si ce n'est les classes bien sûr et
les éventuelles bibliothèques utilisées, uniquement les bibliothèques n'étant pas définies en tant que
dépendances. Nous pouvons constater que cet aspect favorise de manière importante la simplicité de mise en
oeuvre de composants dans des conteneurs de ce type. Les conteneurs OSGi sont de ce fait très légers.
N'oublions pas qu'OSGi était à l'origine utilisé dans des systèmes embarqués donc très soucieux de la
consommation mémoire.
Les conteneurs possèdent diverses caractéristiques quant à la gestion des composants. Tout d'abord,
ils ont la particularité de permettre la gestion des dépendances entre composants. En effet, comme
nous l'avons vu précédemment, un composant s'appuie la plupart du temps sur d'autres afin de réaliser
ses traitements et de fonctionner correctement. Pour ce faire, OSGi offre la possibilité de rendre
visible dans le conteneur les packages de composants de manière très fine. La gestion des versions
des dépendances est également supportée.
4.2. en-têtes OSGi du fichier MANIFEST.MF
Comme nous l'avons précisé dans la précédente section, le fichier MANIFEST.MF correspond au
descripteur de déploiement du bundle. Ce fichier est lu par le conteneur OSGi afin de configurer
le bundle.
La spécification OSGi définit un ensemble d'en-têtes standards pour un composant, en-têtes utilisables
dans le fichier MANIFEST.MF et dont la liste des principales est récapitulée dans le tableau suivant:
| En-tête |
Descriptif |
|
Bundle-ManifestVersion
|
Correspond à la version de fichier MANIFEST du bundle
|
|
Bundle-SymbolicName
|
Spécifie l'identifiant symbolique du bundle
|
|
Bundle-Name
|
Spécifie le nom du bundle
|
|
Bundle-Version
|
Spécifie la version du bundle
|
|
Bundle-DocURL
|
Permet de préciser l'adresse de la documentation du bundle.
|
|
Bundle-Category
|
Spécifie la catégorie du bundle.
|
|
Import-Package
|
Spécifie les noms et les versions des packages utilisés par le bundle.
|
|
Export-Package
|
Spécifie les noms et les versions des packages mis à disposition par le bundle.
|
|
DynamicImport-Package
|
Spécifie les noms et les versions des packages utilisés par le bundle. Cet en-tête
se différencie de Import-Package par le fait qu'il ne soit pas nécessaire que les
dépendances soient présentes au démarrage du bundle. Il suffit qu'elles le soient
au moment de l'exécution.
|
|
Bundle-NativeCode
|
Spécifie la liste des bibliothèques natives présentes dans le bundle.
|
|
Require-Bundle
|
Spécifie les identifiants symboliques des bundles nécessaires au bon fonctionnement du
bundle.
|
|
Bundle-Activator
|
Spécifie le nom de la classe dont les traitements sont exécutés lors du démarrage et
de l'arrêt du bundle. Cette classe doit être présente dans le bundle et implémenter
l'interface BundleActivator.
|
|
Bundle-Classpath
|
Spécifie le classpath du bundle. Par défaut, la valeur implicite correspond
à . et il faut veiller à ne pas l'oublier lorsque des bibliothèques sont spécifiées
explicitement.
|
Le code suivant illustre la configuration d'un composant OSGi simple dans le fichier
MANIFEST.MF. Ce bundle est identifié par simple.bundle, possède le nom "Simple Bundle",
exporte le package org.developpez.osgi.service et utilise l'entité d'activation
org.developpez.osgi.SimpleActivator.
Manifest-Version = 1
Bundle-Activator = org.developpez.osgi.SimpleActivator
Bundle-SymbolicName = simple-bundle
Bundle-Name = Simple Bundle
Bundle-Description = Simple Bundle.
Import-Package = org.osgi.framework;version=1.3
Export-Package = org.developpez.osgi.services
Bundle-Version = 1.0.1
Bundle-License = http:
|
5. Interactions entre bundles
Comme nous l'évoquions dans la section précédente, un conteneur OSGi offre la possibilité de
gérer la visibilité des ressources en composant. Par défaut, un composant est complètement
opaque depuis l'extérieur et rien n'est accessible. Cet aspect implique de
préciser toutes les dépendances utilisées par un composant et tous les packages mis à disposition.
La spécification OSGi offre la possibilité de jouer sur cet aspect par l'intermédiaire d'en-têtes
dans le fichier MANIFEST.MF, fichier contenant les données relatives au déploiement et décrit dans la
précédente section. Détaillons maintenant les spécificités de ces en-têtes et la manière de les
mettre en oeuvre.
Notons qu'il existe une manière complémentaire de faire interagir les bundles entre eux par
l'intermédiaire des services. Nous décrirons cet aspect dans une prochaine section.
5.1. Mise à disposition de packages
Afin de mettre à des dispositions des packages (ou exporter) d'un bundle, l'en-tête
Export-Package doit être mise en oeuvre dans le fichier MANIFEST.MF du composant.
Cet en-tête contiendra la liste des packages pour lesquels l'accès est possible.
Il est à noter que la spécification d'un package dans ce cadre autorise l'accès à toutes les
classes du package tout en gardant inaccessible les classes contenues dans les sous packages
du package.
Lors de l'exportation d'un package, OSGi offre la possibilité d'ajouter des informations
complémentaires telles que la version et dont la liste est récapitulée dans le tableau ci-dessous:
| Paramètre |
Descriptif |
|
uses
|
Spécifie la liste des packages utilisés par le package exporté.
|
|
mandatory
|
Spécifie une liste de paramètres obligatoires à spécifier lors de l'importation
du package exporté.
|
|
include
|
Spécifie une liste de packages devant être visibles par le bundle important le
package.
|
|
exclude
|
Spécifie une liste de packages devant être invisibles par le bundle important le
package.
|
|
version
|
Spécifie la version avec laquelle est mis à disposition le package.
|
La syntaxe complète d'une valeur pour la propriété Export-Package est donc la suivante:
Export-Package: nom-package;parametre:=valeur,nom-package;parametre:=valeur,...
|
Notons que, si la valeur d'un paramètre est une liste, les éléments doivent être séparés par des virgules
et la valeur globale doit être entourée par des guillemets.
Le code suivant correspond à un extrait de la valeur de l'en-tête Export-Package du
fichier MANIFEST.MF d'un bundle OSGi pour Hibernate 3 [12]. Nous
remarquons la présence des paramètres uses et version pour le package
org.hibernate.stat.
Export-Package: org.hibernate.stat;uses:="org.hibernate.util,org.hibernate.cache,
org.apache.commons.logging,org.hibernate.engine";version=3.1.3,
org.hibernate.cache;uses:="org.hibernate.util,net.sf.swarmcache,
org.hibernate.cfg,org.hibernate,net.sf.ehcache,org.hibernate.impl,...
|
5.2. Importation de packages
La technologie OSGi offre la possibilité de spécifier les packages utilisés afin qu'ils soient
visibles dans le bundle. Une fois spécifiées, les classes sont utilisables dans les classes
implémentées par le bundle. Cet aspect se configure dans le fichier MANIFEST.MF par l'intermédiaire
des en-têtes Import-Package et DynamicImport-Package. Détaillons tout d'abord l'usage du
premier en-tête.
Lors de l'importation d'un package, OSGi offre la possibilité d'ajouter des
informations complémentaires telles que la version et le mode de résolution dont la liste simplifiée
est récapitulée dans le tableau ci-dessous:
| Paramètre |
Descriptif |
|
resolution
|
Spécifie le type de résolution du package. La valeur par défaut est mandatory.
|
|
version
|
Spécifie la version du package à utiliser. Une plage de versions peut être spécifiée.
|
La syntaxe complète d'une valeur pour la propriété Import-Package est donc la suivante:
Import-Package: nom-package;parametre:=valeur,nom-package;parametre:=valeur,...
|
Notons que, si la valeur d'un paramètre est une liste, les éléments doivent être séparés par des virgules
et la valeur globale doit être entourée par des guillemets.
Le code suivant correspond à un extrait de la valeur de l'en-tête Import-Package du
fichier MANIFEST.MF du bundle OSGi de Spring AOP 2.0.5. Nous remarquons la présence
des paramètres resolution pour le package com.jamonapi et version pour
le package org.springframework.aop.
Import-Package: com.jamonapi;resolution:=optional,net.sf.cglib.core;resolution:=optional,
net.sf.cglib.proxy;resolution:=optional,net.sf.cglib.transform.impl;resolution:=optional,
...
org.springframework.aop;version=2.0.5,org.springframework.aop.aspectj;version=2.0.5,...
|
Dans le cas de l'importation de packages, l'en-tête DynamicImport-Package est également
disponible. Il se différencie de la précédente par le fait que la résolution des
dépendances spécifiées à ce niveau n'est réalisée qu'au moment du chargement des classes
et non lors du passage du bundle de l'état Installé à Résolu.
L'en-tête fonctionne sinon de la même manière que l'en-tête Import-Package, si ce n'est qu'il
ne supporte logiquement pas le paramètre resolution.
6. Principales API OSGi
Par cette section, nous allons introduire deux interfaces des APIs de la technologie OSGi relatives
respectivement à l'entité d'activation et au contexte des bundles. Elles permettent notamment d'initialiser
et de finaliser les ressources des bundles et d'interagir avec le conteneur OSGi.
L'entité relative au contexte OSGi nous sera utile dans la section relative aux services puisque cette
interface met à disposition des méthodes afin de manipuler et d'avoir accès aux services présents dans le
conteneur OSGi.
6.1. BundleActivator - Entité d'activation
La technologie OSGi offre la possibilité de spécifier une entité appelée lors de l'activation
(état Démarrage) et de la désactivation (état En cours d'arrêt) d'un composant,
c'est-à-dire lorsque ce dernier est démarré ou arrêté. La classe implémentant cette entité doit
nécessairement posséder l'interface BundleActivator et être configurée par l'intermédiaire de l'en-tête
Bundle-Activator dans le fichier MANIFEST.MF.
L'interface BundleActivator définit la structure d'une classe d'activation en définissant les
signatures des méthodes appelées lors des différents événements précédemment cités:
- Méthode start, appelée lors le démarrage du composant (état Démarrage);
- Méthode stop, appelée quant à elle lors de l'arrêt du composant (état En cours
d'arrêt).
Ces deux méthodes prennent en paramètre le contexte OSGi du composant par l'intermédiaire de l'interface
BundleContext, interface permettant d'interagir aussi bien avec le conteneur qu'avec le composant. Le
contenu de l'interface BundleActivator est décrit ci-dessous:
public interface BundleActivator {
void start(BundleContext context);
void stop(BundleContext context);
}
|
Le code suivant illustre la mise en oeuvre d'une entité d'activation pour un bundle par l'intermédiaire
d'une implémentation simple appelée ici SimpleActivator:
public class SimpleActivator implements BundleActivator {
private ServiceRegistration serviceRegistration;
public void start(BundleContext context) {
SimpleService service = new SimpleServiceImpl();
this.serviceRegistration = context.registerService(
SimpleService.class.getName(), service, null);
}
public void stop(BundleContext context) {
if( this.serviceRegistration!=null ) {
this.serviceRegistration.unregister();
}
}
}
|
Le code suivant décrit la configuration de cette entité par l'intermédiaire de l'en-tête Bundle-Activator
dans le fichier MANIFEST.MF du bundle:
Manifest-Version = 1
Bundle-Activator = org.developpez.OSGi.SimpleActivator
Bundle-SymbolicName = simple-bundle
Bundle-Name = Simple Bundle
Bundle-Description = Simple Bundle.
Import-package = org.OSGi.framework;version=1.3
Bundle-Version = 1.0.1
Bundle-License = http:
|
6.2. BundleContext - Contexte de composant
Nous avons vu dans la précédente section que l'entité d'activation s'appuie sur le contexte du composant.
Détaillons maintenant les spécificités de l'interface BundleContext, interface relative au contexte du
composant. Cette dernière permet d'interagir aussi bien au niveau du conteneur OSGi lui-même que du
bundle dans lequel il est utilisé. Elle offre la possibilité de réaliser les différentes opérations
suivantes:
| Opération |
Descriptif |
|
Manipulation des bundles
|
Permet de récupérer les instances de bundles du conteneur et d'installer de nouvelles instances.
|
|
Manipulation des services
|
Permet de récupérer les instances de services du conteneur et d'installer de nouveaux. Nous détaillerons
cet aspect dans la section suivante.
|
|
Observateurs
|
Permet de spécifier et de supprimer des observateurs à différents niveaux.
|
Le contenu de l'interface BundleContext est décrit dans le code ci-dessous:
 public interface BundleContext {
void addBundleListener(BundleListener listener);
void addFrameworkListener(FrameworkListener listener);
void addServiceListener(ServiceListener listener);
void addServiceListener(ServiceListener listener,
Java.lang.String filter);
Filter createFilter(Java.lang.String filter);
ServiceReference[] getAllServiceReferences(Java.lang.String clazz,
Java.lang.String filter);
Bundle getBundle();
Bundle getBundle(long id);
Bundle[] getBundles();
Java.io.File getDataFile(Java.lang.String filename);
Java.lang.String getProperty(Java.lang.String key);
Java.lang.Object getService(ServiceReference reference);
ServiceReference getServiceReference(Java.lang.String clazz);
ServiceReference[] getServiceReferences(Java.lang.String clazz,
Java.lang.String filter);
Bundle installBundle(Java.lang.String location);
Bundle installBundle(Java.lang.String location,
Java.io.InputStream input);
ServiceRegistration registerService(Java.lang.String[] clazzes,
Java.lang.Object service,
Java.util.Dictionary properties);
ServiceRegistration registerService(Java.lang.String clazz,
Java.lang.Object service,
Java.util.Dictionary properties);
void removeBundleListener(BundleListener listener);
void removeFrameworkListener(FrameworkListener listener);
void removeServiceListener(ServiceListener listener);
boolean ungetService(ServiceReference reference);
}
|
Détaillons maintenant les spécificités relatives aux bundles et aux observateurs. Cette interface
offre tout d'abord la possibilité de gérer et récupérer les instances des bundles présents dans le
conteneur. Tout d'abord, l'installation de bundles se réalise par l'intermédiaire des méthodes
installBundle, méthode prenant en paramètre le chemin du fichier JAR du bundle. Les
méthodes getBundle et getBundles retournent quant à elle une ou plusieurs instances de
bundles. Sont supportées les récupérations du bundle courant, d'un bundle en se fondant
sur son identifiant dans le conteneur ou de tous les bundles du conteneur.
Le code suivant illustre la mise en oeuvre de ces méthodes dans une entité ayant accès au contexte
OSGi:
Bundle bundleInstalle = contexte.installBundle(
"file:///home/templth/developpez/OSGi/simplebundle.jar");
long idBundle = bundleInstalle.getBundleId();
Bundle bundle = contexte.getBundle(idBundle);
int etatBundle = bundle.getState();
|
Comme vous pouvez le constater, le code précédent repose sur l'interface Bundle correspondant
à l'entité relative à un composant OSGi. Cette dernière permet de récupérer des informations sur
le bundle mais également de gérer son état. Le code suivant décrit le contenu de l'interface
Bundle:
public interface Bundle {
public static final int UNINSTALLED = 0x00000001;
public static final int INSTALLED = 0x00000002;
public static final int RESOLVED = 0x00000004;
public static final int STARTING = 0x00000008;
public static final int STOPPING = 0x00000010;
public static final int ACTIVE = 0x00000020;
Enumeration findEntries(String path, String filePattern, boolean recurse)
BundleContext getBundleContext()
long getBundleId()
URL getEntry(String path)
Enumeration getEntryPaths(String path)
Dictionary getHeaders()
Dictionary getHeaders(String locale)
long getLastModified()
String getLocation()
ServiceReference[] getRegisteredServices()
URL getResource(String name)
Enumeration getResources(String name)
ServiceReference[] getServicesInUse()
int getState()
String getSymbolicName()
boolean hasPermission(Object permission)
Class loadClass(String name)
void start()
void start(int options)
void stop()
void stop(int options)
void uninstall()
void update()
void update(InputStream in)
}
|
Il est ainsi possible par la programmation, après l'installation d'un bundle, de réaliser le
démarrage du bundle et de récupérer les différents services mis à disposition par le bundle puis
de l'arrêter, comme l'illustre le code suivant:
Bundle bundleInstalle = contexte.installBundle(
"file:///home/templth/developpez/OSGi/simplebundle.jar");
bundle.start();
ServiceReference[] serviceReferences = bundle.getRegisteredServices();
bundle.stop();
|
Pour finir, le contexte offre la possibilité de spécifier des observateurs d'événements sur
le conteneur OSGi lui-même, sur les bundles et sur les services. Cet aspect est couramment utilisé
dans les bonnes pratiques de mise en oeuvre de la technologie OSGi, notamment par l'intermédiaire
du patron de conception Extender [13] au niveau des bundles.
Le principe consiste en la mise en oeuvre d'un observateur utilisant les données des bundles afin
de réaliser des traitements. Ce patron permet de mieux gérer les états des bundles lors de traitements
généraux, traitements pouvant être réalisés de manière paresseuse. Cette approche est mise en oeuvre
dans des outils tels que Declarative Services [14], iPOJO
[15], Spring Dynamic Modules [16].
Un observateur de bundles peut être mis en oeuvre par l'intermédiaire des interfaces BundleListener
et SynchronousBundleListener, respectivement déclenchées de manière asynchrone et synchrone. Les
deux interfaces mettent en oeuvre la méthode bundleChanged afin de notifier un changement pour un
bundle.
Cette méthode prend en paramètre un objet de type BundleEvent, objet permettant d'avoir accès au
bundle
impacté et à l'événement déclencheur dans le cycle vie. Le code suivant illustre le contenu de l'interface
BundleListener:
public interface BundleListener extends EventListener {
void bundleChanged(BundleEvent event);
}
|
La mise en oeuvre d'un observateur de bundles se réalise de la manière suivante:
BundleListener observateur = new BundleListener() {
public void bundleChanged(BundleEvent evenement) {
Bundle bundle = evenement.getBundle();
int etat = evenement.getType();
(...)
}
};
context.addBundleListener(observateur);
|
7. Mise en oeuvre de services
Comme nous l'avons évoqué précédemment, la technologie OSGi offre la possibilité de mettre
des services au niveau des composants. Elle permet de bien séparer le contrat du service (interface)
de la ou des implémentations fournies par les composants. Les consommateurs du service n'ont
connaissance que de son interface. De plus, les services mis en oeuvre avec OSGi n'ont aucune
adhérence avec les API de conteneurs et consistent en de simples POJOs [17].
La technologie OSGi, par l'intermédiaire de l'interface BundleContext, offre la
possibilité à un composant de mettre à disposition des services par l'intermédiaire de la méthode
registerService. Le premier paramètre de cette méthode correspond au nom du service,
nom visible au niveau du conteneur. Un usage courant consiste à spécifier le nom de l'interface du
service comme nom du service.
Le code suivant décrit la manière d'enregistrer un service dans un conteneur OSGi par l'intermédiaire
du contexte OSGi:
SimpleService service = new SimpleServiceImpl();
ServiceRegistration serviceRegistration = context.registerService(
SimpleService.class.getName(), service, null);
|
Le désenregistrement d'un service OSGi se réalise par l'intermédiaire de la méthode unregister
de l'instance de ServiceRegistration renvoyée précédemment par la méthode register. Une
bonne pratique consiste donc à le garder en variable d'instance (de l'activateur par exemple). Le code
suivant illustre la mise en oeuvre d'un désenregistrement de service:
ServiceRegistration serviceRegistration = (...)
serviceRegistration.unregister();
|
Différentes méthodes sont également fournies afin d'avoir accès aux services afin de les utiliser.
La récupération d'une instance de service se réalise en deux étapes. La première consiste en la
récupération d'une instance de l'interface ServiceReference pour le service par l'intermédiaire de
la méthode getServiceReference ou getServiceReferences. L'utilisation de la méthode getService
avec en paramètre l'instance précédente permet d'avoir accès à l'instance du service et de l'utiliser.
Le code suivant décrit la manière d'avoir accès à une instance d'un service à partir de son nom et
par l'intermédiaire du contexte OSGi:
ServiceReference reference = context.getServiceReference(
SimpleService.class.getName());
SimpleService service = context.getService(reference);
(...)
|
Il est à noter que, dans le cas d'une utilisation d'un service en dehors du conteneur, le transtypage
dans le type du service ne fonctionnera pas. En effet, les classes et interfaces gérées par le conteneur
ne sont visibles et utilisables qu'en son sein. Un service OSGi reste néanmoins utilisable en se fondant
sur l'introspection.
Nous pouvons également noter la présence d'une méthode ungetService au niveau du contexte afin de
libérer l'instance du service référencée par l'instance de ServiceReference correspondante.
8. Conclusion
Dans ce premier article, nous avons introduit la technologie OSGi et les
différentes problématiques qu'elle vise à adresser. Nous avons également décrit les caractéristiques
de la technologie et comment mettre en oeuvre des composants OSGi, composants désignés par le terme
bundle dans la terminologie d'OSGi. Nous avons ainsi abordés les différentes couches
structurant les conteneurs de ce type, à savoir les couches Module, Cycle de vie et
Service.
Dans le prochain article, nous verrons comment mettre concrètement en oeuvre des composants OSGi
dans un conteneur embarqué dans un processus Java. Pour ce faire, nous utiliserons le conteneur OSGi
libre Felix. Nous verrons également comment tirer partie de l'environnement Eclipse afin de
développer des bundles OSGi.
9. Bibliographie


Ce document est issu de http://www.developpez.com et reste la propriété exclusive de son auteur.
La copie, modification et/ou distribution par quelque moyen que ce soit est soumise à l'obtention préalable
de l'autorisation de l'auteur.