Comprendre les concepts OSGi. Essayez de suivre l'approche du puzzle
Publié: 2013-04-20OSGi
est devenu très populaire aujourd'hui, grâce à son approche de modularité et sa capacité à imposer des frontières logiques entre les modules. Quand on le découvre la première fois, la question est de savoir par où commencer pour comprendre comment cela fonctionne ?
Pour comprendre les concepts OSGi, nous allons essayer de suivre l'approche du puzzle, l'idée est de commencer par la partie triviale de cette technologie et de rechercher d'autres parties liées à celles trouvées. Et pour assembler le puzzle, nous serons assistés par JArchitect qui sera utile pour détecter la conception interne OSGi.
Pour être plus concret nous analysons avec JArchitect une application utilisant la technologie OSGi, il s'agit du fameux IDE eclipse qui utilise le conteneur OSGi equinox.
Commençons par la définition typique d'OSGi :
OSGi réduit la complexité en fournissant une architecture modulaire pour les systèmes distribués à grande échelle d'aujourd'hui ainsi que pour les petites applications embarquées. La construction de systèmes à partir de modules internes et prêts à l'emploi réduit considérablement la complexité et donc les dépenses de développement et de maintenance. Le modèle de programmation OSGi réalise la promesse des systèmes à base de composants.
La partie triviale d'OSGi est la modularité, découvrons quels sont les modules OSGi ?
Les modules OSGI sont appelés Bundles et chaque application consiste donc en au moins un bundle.
Ces bundles sont exécutés à l'intérieur d'un conteneur, et comme chaque approche modulaire où un conteneur gère les modules, la question est de savoir quel contrat doit implémenter chaque module à intégrer dans le conteneur ?
Prenons comme exemple le bundle org.eclipse.equinox.jsp.jasper et recherchons toutes ses interfaces implémentées, pour cela nous pouvons exécuter la requête CQLinq suivante :
1 2 3 4 |
from t in Types where t . ParentProject . Name ==” org . eclipse . equinox . jsp . jasper_1 . 0.300.v20110502 “ let interfaces = t . InterfacesImplemented from i in interfaces select i |
L'interface BundleActivator du package OSGi est implémentée, cette interface contient deux méthodes start et stop utiles pour personnaliser le démarrage et l'arrêt du bundle.
Une autre spécificité d'un bundle est son fichier manifest, voici une partie du fichier manifest org.eclipse.equinox.jsp.jasper :
1 2 3 4 5 6 7 8 9 |
Manifest - Version : 1.0 Bundle - Localization : plugin Bundle - RequiredExecutionEnvironment : CDC - 1.0 / Foundation - 1.0 , J2SE - 1.3 Bundle - SymbolicName : org . eclipse . equinox . jsp . jasper Eclipse - LazyStart : true Eclipse - SourceReferences : scm : cvs : pserver : dev . eclipse . org : / cvsroot / rt : org . eclipse . equinox / server - side / bundles / org . eclipse . equinox . jsp . jaspe r ; tag = v20110502 Bundle - Activator : org . eclipse . equinox . internal . jsp . jasper . Activator |
Comme nous pouvons le constater, ce manifeste contient certaines méta-informations nécessaires au conteneur, comme la spécification de la classe d'activateur de bundle qui implémente l'interface BundleActivator.
Le bundle représente notre première pièce du puzzle, et voici une représentation simplifiée d'un bundle :
Et juste pour avoir une idée de tous les bundles utilisés par eclipse, recherchons toutes les classes implémentant l'interface BundleActivator.
1 2 |
from t in Types where t . Implement ( “ org . osgi . framework . BundleActivator “ ) select new { t , t . NbBCInstructions } |
Qui gère le bundle et invoque les méthodes BundleActivator ?
Pour découvrir cela, recherchons des méthodes appelant directement ou indirectement BundleActivator.start
1 2 3 4 |
from m in Methods let depth0 = m . DepthOfIsUsing ( “ org . eclipse . osgi . framework . internal . core . BundleContextImpl . startActivator ( BundleActivator ) “ ) where depth0 > = 0 orderby depth0 select new { m , depth0 } |
Le bundle est activé par le framework OSGi lors de son lancement. La classe framework est démarrée par le conteneur equinox. Et pour mieux comprendre ce qui se passe au démarrage du conteneur, voici quelques actions exécutées lors du lancement du conteneur :
Lorsque le conteneur est lancé, il initialise le framework OSGi, le framework obtient tous les bundles installés, et pour chacun d'eux, il crée une instance de la classe BundleHost et stocke dans un référentiel le bundle trouvé.
La classe BundleHost implémente l'interface Bundle, qui contient des méthodes telles que start,stop,uninstall et update, ces méthodes sont nécessaires pour gérer le cycle de vie du bundle.
Donc, notre deuxième pièce du puzzle est le conteneur OSGi, il est lancé par l'Equinoxlauncher, qui initialise le framework. La classe framework est responsable du chargement des bundles et les active.
Après avoir découvert quelques concepts de base sur les bundles et les conteneurs, approfondissons les bundles et découvrons comment ils fonctionnent en interne.
Prenons comme exemple le bundle org.eclipse.equinox.http.servlet et recherchons les méthodes invoquées par la méthode start de sa classe Activator.
Ce bundle crée un service et l'enregistre dans le conteneur. Un service dans OSGi est défini par une classe ou une interface Java standard. Généralement, une interface Java est utilisée pour définir l'interface de service. Le service est la méthode préférée que les bundles doivent utiliser pour communiquer entre eux.
Voici quelques scénarios utiles d'utilisation des services :

- Exportez les fonctionnalités d'un bundle vers d'autres bundles.
- Importez des fonctionnalités à partir d'autres bundles.
- Enregistrez des écouteurs pour les événements d'autres bundles.
Une autre remarque du graphique de dépendance précédent est qu'une usine de service est utilisée pour créer l'instance de service.
Notre troisième pièce du puzzle est la couche de services OSGi, chaque bundle peut utiliser ou déclarer certains services, ce qui applique l'approche de conception de composants, voici la nouvelle représentation du bundle OSGi.
Si le bundle utilise des services pour communiquer avec d'autres bundles, comment communique-t-il avec d'autres jars ?
Si nous développons un bundle et essayons d'utiliser une classe d'un autre jar, nous pouvons être surpris que cela ne fonctionne pas comme prévu, la raison en est que le ClassLoader est accroché par le conteneur OSGi, pour vérifier cela, cherchons quelle méthode invoquer java. lang.Thread.setContextClassLoader.
1 2 |
from m in Methods where m . IsUsing ( “ java . lang . Thread . setContextClassLoader ( ClassLoader ) “ ) select new { m , m . NbBCInstructions } |
De nombreuses méthodes l'invoquent, y compris EquinoxLauncher. Ainsi, chaque fois que le bundle essaie de créer une instance de classe, le conteneur OSGi vérifie si le code est autorisé à effectuer cette action ou non, et voici le rôle du package importé et exporté dans le fichier manifeste.
1 2 3 4 |
Export - Package : org . eclipse . equinox . http . servlet ; version =” 1.1.0 ″ Import - Package : javax . servlet ; version =” 2.3 ″ , javax . servlet . http ; version =” 2.3 ″ , org . osgi . framework ; version =” 1.3.0 ″ , org . osgi . service . http ; versi on =” [ 1.2 , 1.3 ) ” |
Le bundle déclare explicitement les packages exportés et importés, et pour vérifier cela, recherchons les packages utilisés par le bundle org.eclipse.equinox.http.servlet et vérifions s'il utilise uniquement le package importé.
1 2 |
from n in Packages where n . IsUsedBy ( “ org . eclipse . equinox . http . servlet_1 . 1.200.v20110502 “ ) select new { n , n . NbBCInstructions } |
Comme nous pouvons le constater, tous les packages utilisés sont spécifiés dans le fichier manifeste, dans la section package d'importation.
Le package exporté représente cependant le package qui peut être utilisé à partir d'autres bundles. il est représenté par l'interface ExportedPackage et nous pouvons rechercher les classes de conteneur à l'aide de cette interface.
1 2 |
from t in Types where t . IsUsing ( “ org . osgi . service . packageadmin . ExportedPackage “ ) select new { t , t . NbBCInstructions } |
Ce qui est intéressant avec cette nouvelle capacité, c'est que le bundle aura une frontière bien définie, et ce qu'il utilise et ce qu'il expose en tant que services est très bien spécifié.
Nous pouvons appliquer la vérification de l'utilisation de packages importés à l'aide d'autres outils, par exemple avec CQLinq, nous pouvons écrire des règles avertissant chaque fois qu'un projet utilise des packages autres que ceux spécifiés, mais il est préférable d'avoir cette vérification dans l'environnement d'exécution, donc le développeur ne peut pas enfreindre ces règles.
Cette capacité à traiter les packages importés et exportés est gérée par la couche des modules OSGi et c'était notre quatrième pièce du puzzle.
Revenons au conteneur OSGi et découvrons quels services il fournit ?
Services de conteneurs
Comme nous l'avons découvert avant que le conteneur ne soit lancé par la classe EquinoxLauncher et que la classe framework initialise et lance les bundles, pour détecter quels services sont fournis, recherchons toutes les classes utilisées dans la méthode d'initialisation du framework.
1 2 |
from t in Types where t . IsUsedBy ( “ org . eclipse . osgi . framework . internal . core . Framework . initialize ( FrameworkAdaptor ) “ ) select new { t , t . NbBCInstructions } |
Certaines classes sont déjà découvertes auparavant comme BundleRepository,BundleHost,PackageAdminImpl et ServiceRegistry.
Et les autres classes :
- StartLevelManager :
Chaque ensemble OSGi est associé à un niveau de démarrage qui permet au serveur de contrôler l'ordre relatif de démarrage et d'arrêt des ensembles. Seuls les bundles dont le niveau de démarrage est inférieur ou égal au niveau de démarrage actif de l'infrastructure de serveur doivent être actifs. Habituellement, un bundle avec un niveau de démarrage plus petit a tendance à être démarré plus tôt. - Administrateur de sécurité :
La couche de sécurité gère les aspects de sécurité en limitant les fonctionnalités du bundle à des capacités prédéfinies. - Responsable de l'événement:
Le service Event Admin fournit un modèle de publication-abonnement pour la gestion des événements. Il est réalisé conformément à la spécification du service d'administration d'événements OSGi. Le service d'administration d'événements répartit les événements entre les éditeurs d'événements et les abonnés aux événements (gestionnaires d'événements) en interposant un canal d'événements. Les éditeurs publient des événements sur le canal et le canal d'événements définit les gestionnaires qui doivent être notifiés. Ainsi, les éditeurs et les gestionnaires n'ont aucune connaissance directe les uns des autres, ce qui simplifie la gestion des événements.
L'image globale de l'OSGi
Assemblons toutes les pièces du puzzle décrites précédemment, et nous aurons l'image OSGi suivante :
Cette architecture présente les avantages intéressants suivants :
- Simple.
- Complexité réduite.
- Déploiement facile.
- Sécurise.
Ce qui le rend très attractif et mérite un détour, et vous ne perdrez pas votre temps si vous l'étudiez en profondeur.