Qu'est-ce que OSGi? Une approche différente de la modularité Java

OSGi facilite la création et la gestion de composants Java modulaires (appelés bundles ) qui peuvent être déployés dans un conteneur. En tant que développeur, vous utilisez la spécification et les outils OSGi pour créer un ou plusieurs bundles. OSGi définit le cycle de vie de ces bundles. Il les héberge également et prend en charge leurs interactions dans un conteneur. Vous pouvez considérer un conteneur OSGi comme à peu près analogue à une JVM, avec des pouvoirs supplémentaires. De même, considérez les bundles comme des applications Java avec des capacités uniques. Les bundles s'exécutent à l'intérieur du conteneur OSGi en tant que composants client et serveur.

L'alliance OSGi

OSGi a commencé en 1999, et contrairement à de nombreuses autres spécifications, la norme n'est pas gérée par Oracle, le Java Community Process ou la Fondation Eclipse. Au lieu de cela, il est géré par l'alliance OSGi.

En quoi OSGi est différent

La philosophie d'OSGi diffère de celle d'autres frameworks basés sur Java, notamment Spring. Dans OSGi, plusieurs applications peuvent exister dans le même conteneur: l' environnement d'exécution du bundle OSGi . Le conteneur garantit que chaque composant est suffisamment isolé et a également accès à toutes les dépendances dont il a besoin. OSGi peut prendre en charge l'injection de dépendances, qui est normalisée par le projet Aries Blueprint. En plus de fournir le conteneur d'inversion de contrôle (IoC) d'OSGi, Aries prend en charge les frameworks Java standard tels que l'API Java Persistence (JPA).

Dans OSGi, les bundles peuvent exposer des services que d'autres bundles utilisent. Un bundle peut également déclarer une version et définir les autres bundles dont il dépend. Le runtime chargera alors automatiquement tous ses bundles par ordre de dépendance. Dans OSGi, plusieurs versions du même bundle peuvent exister côte à côte, si cela est requis par les dépendances de bundle.

OSGi dans Eclipse IDE et Equinox

OSGi existe sous une forme ou une autre depuis quelques décennies. Il est utilisé pour de nombreuses applications bien connues, des appareils mobiles intégrés aux serveurs d'applications et aux IDE.

Le populaire IDE Eclipse est construit sur OSGi. L'implémentation d'Eclipse du conteneur OSGi s'appelle Equinox. C'est un excellent exemple pour comprendre OSGi. Être basé sur OSGi signifie qu'Equinox est une plate-forme modulaire. Il héberge une variété de services que les développeurs peuvent ajouter à volonté. Chacun de ceux-ci offre une capacité dont un développeur pourrait avoir besoin dans son IDE. Vous pouvez ajouter des éditeurs pour Java et JavaScript, un serveur d'applications et un connecteur de base de données. Chacun de ces éléments est implémenté en tant que bundle OSGi qui est ajouté au conteneur et peut interagir avec d'autres services dans le conteneur.

Récemment, il y a eu un regain d'intérêt pour l'utilisation d'OSGi pour l'Internet des objets (IoT). OSGi est un choix naturel pour ce type de développement, qui comporte une variété de composants logiciels fonctionnant côte à côte sur des appareils, sans nécessairement se connaître. Un conteneur OSGi fournit un moyen simple et standardisé d'héberger ces composants logiciels dynamiques.

Utilisation d'OSGi dans un projet Java: Knoplerfish OSGi

Nous allons travailler sur un exemple d'application qui rendra les concepts OSGi plus concrets. Notre exemple est basé sur le runtime Knoplerfish OSGi, qui est utilisé dans de nombreux déploiements de production. Knoplerfish inclut une GUI et une interface de ligne de commande (CLI) pour gérer le conteneur OSGi et ses bundles.

La première chose que vous ferez est de télécharger Knoplerfish. La version actuelle au moment de la rédaction de cet article est Knoplerfish OSGi 6.1.3. Vous pouvez remplacer cette version par celle qui est la plus récente lorsque vous lisez cet article.

Une fois que vous avez téléchargé et installé Knoplerfish, utilisez la CLI de tomber dans le répertoire où vous avez téléchargé le fichier JAR et entrez: java -jar framework.jar. Cela lancera l'exécutable JAR et vous devriez être accueilli par une fenêtre GUI.

L'interface graphique OSGi de Knoplerfish

L'interface graphique de Knoplerfish OSGi peut sembler écrasante au début, mais les bases sont simples:

  • En haut de l'écran se trouve le menu.
  • À gauche se trouve l'ensemble des bundles qui ont été chargés dans le runtime.
  • À droite se trouve une fenêtre d'informations.
  • En bas se trouve une console de sortie de texte.
  • Tout en bas se trouve une console d'entrée.
Matthew Tyson

Tapez helpdans la console d'entrée si vous souhaitez voir les options d'aide.

Avant de passer à l'exemple, jetez un œil à l'ensemble des bundles en cours d'exécution. Vous verrez un bundle appelé HTTP Server, ce qui signifie qu'un bundle exécutant un serveur HTTP est actif. Accédez à votre navigateur et consultez // localhost: 8080. Effectivement, vous verrez une page Web Knoplerfish.

Le bundle 'Hello JavaWorld'

Utilisons le runtime OSGi pour créer un bundle simple, que j'appellerai Hello JavaWorld. Cet ensemble envoie un message à la console.

Dans le listing 1, nous utilisons Maven pour créer le bundle. Il n'a qu'une seule dépendance, qui est fournie par l'alliance OSGi.

Listing 1. Dépendance OSGi dans le POM Maven

   org.osgi org.osgi.core   

Maintenant, nous allons également utiliser un plug-in, grâce au projet Apache Felix. Ce plug-in prend en charge le conditionnement de l'application en tant que bundle OSGi à utiliser. Le listing 2 montre la configuration que nous utiliserons.

Listing 2. Plug-in OSGi Felix dans le Maven POM

   org.apache.felix maven-bundle-plugin true   org.javaworld.osgi org.javaworld.osgi.Hello     

Nous pouvons maintenant jeter un œil à la classe simple qui affichera un «Hello».

Listing 3. Ensemble Hello JavaWorld OSGi

 package com.javaworld.osgi; import org.osgi.framework.BundleActivator; import org.osgi.framework.BundleContext; public class HelloJavaWorld implements BundleActivator { public void start(BundleContext ctx) { System.out.println("Hello JavaWorld."); } public void stop(BundleContext bundleContext) { } } 

Créez le bundle en accédant à la ligne de commande et en tapant mvn clean install. Cela produira un fichier JAR contenant le bundle. Maintenant, allez dans le Filemenu de l'interface graphique de Knoplerfish et sélectionnez Add Bundle. Cela fournira un navigateur de fichiers. Trouvez le JAR que nous venons de créer et sélectionnez-le.

Gestion des bundles OSGi dans le conteneur

In the output window of the Knoplerfish UI, you’ll see your “Hello, JavaWorld” message appear. Click on the bundle in the Knoplerfish GUI, and you can see the ID the container has assigned to it. When you are ready to stop the bundle, you could click the Stop menu item. Another way is to enter stop [bundle number] on the command line. You can manage bundles in the container using either the GUI or the command line.

Now you have a sense of how a simple bundle works in the OSGi container. Anywhere an OSGi container exists, you will find the same simplicity in starting and stopping bundles. OSGi creates an environment and lifecycle for the bundle.

Bundle Interactions: Services and clients

Next, we’ll look at how bundles communicate with each other.

The first thing we’ll do is create a service bundle. A service bundle is analogous to an EJB session bean: It provides a component that can be accessed by other bundles via a remote interface. To create a service bundle, we need to provide both an interface and an implementation class.

Listing 4. The service bundle interface

 package com.javaworld.osgi.service; public interface WhatIsOsgi { public Integer addNum(Integer x, Integer y); } 

Listing 4 is a simple interface. The only method is a addNum() method that will do what it implies: return the addition of two numbers. The implementation shown in Listing 5 is equally simple but adds a couple of OSGi-specific methods.

Listing 5. The service bundle implementation

 package com.javaworld.osgi.service; public class WhatIsOsgiImpl implements WhatIsOsgi, BundleActivator { private ServiceReference ref; private ServiceRegistration reg; @Override public Integer addNum(Integer x, Integer y){ return x + y; } @Override public void start(BundleContext context) throws Exception { reg = context.registerService( WhatIsOsgi.class, new WhatIsOsgiImpl(), new Hashtable()); ref = reg.getReference(); } @Override public void stop(BundleContext context) throws Exception { reg.unregister(); } } 

Let’s look closer at what’s happening in Listing 5:

  1. public class WhatIsOsgiImpl implements WhatIsOsgi, BundleActivator: Here we are implementing the interface we created. Note that we also implement the BundleActivator interface, as we did with the HelloJavaWorld example. The latter is because this bundle will activate itself.
  2. private ServiceReference ref; private ServiceRegistration reg;: These are variables for the OSGi registration service and the bundle reference for this service, respectively.
  3. public Integer addNum(Integer x, Integer y): This is the simple implementation of the add method.
  4. public void start(BundleContext context): This start method is part of the BundleActivator interface, and is executed by the container. In this example, we obtain a reference to the OSGi registration service and apply it to our WhatIsOsgi interface and implementation. The empty Hashtable is for config params, which we aren’t using here. We also get a reference to the service we have just created.
  5. public void stop(BundleContext context): Here, we simply unregister the service. This simple service just manages the barest elements of its lifecycle. Its main purpose is to expose the addNum method to the OSGi container.

The OSGi client

Next up, let’s write a client that can use the service. This client will again make use of the BundleActivator interface. It will also add the ServiceListener interface, as shown in Listing 6.

Listing 6. The OSGi service client bundle

 public class OsgiClient implements BundleActivator, ServiceListener { private BundleContext ctx; private ServiceReference service; public void start(BundleContext ctx) { this.ctx = ctx; try { ctx.addServiceListener(this, "(objectclass=" + WhatIsOsgi.class.getName() + ")"); } catch (InvalidSyntaxException ise) { ise.printStackTrace(); } } } 

Listing 6 has a start method that will add a service listener. This listener is filtered by the class name of the service we created in Listing 5. When the service is updated, it will call the serviceChanged() method, as shown in Listing 7.

Listing 7. serviceChanged method

 public void serviceChanged(ServiceEvent event) { int type = event.getType(); switch (type){ case(ServiceEvent.REGISTERED): serviceReference = event.getServiceReference(); Greeter service = (Greeter)(ctx.getService(service)); System.out.println("Adding 10 and 100: " + service.addNum(10, 100) ); break; case(ServiceEvent.UNREGISTERING): System.out.println("Service unregistered."); ctx.ungetService(event.getServiceReference()); // Releases reference to service so it can be GC'd break; default: break; } } 

Note that the serviceChanged method is used to determine what event has occurred for a service we are interested in. The service will then respond as specified. In this case, when the REGISTERED event appears, we make use of the addNum() method.

The OSGi alternative

This has been a quick introduction to OSGi, the Open Services Gateway Initiative. As you’ve seen through the Knoplerfish example, OSGi provides a runtime environment where you can define modular Java components (bundles). It provides a defined lifecycle for hosting bundles in the client, and it supports bundles interacting as clients and services within the container. All of these capabilities taken together provide an interesting alternative to standard Java runtimes and frameworks, especially for mobile and IoT applications.

Enfin, notez que l'article précédent de la série «Qu'est-ce que c'est: Java» présentait le Java Platform Module System, qui propose une approche différente du même défi de la modularité Java.

Cette histoire, "Qu'est-ce qu'OSGi? Une approche différente de la modularité Java" a été publiée à l'origine par JavaWorld.