Cos'è OSGi? Un approccio diverso alla modularità Java

OSGi facilita la creazione e la gestione di componenti Java modulari (chiamati bundle ) che possono essere distribuiti in un container. In qualità di sviluppatore, utilizzi la specifica e gli strumenti OSGi per creare uno o più bundle. OSGi definisce il ciclo di vita di questi bundle. Inoltre li ospita e supporta le loro interazioni in un contenitore. Puoi pensare a un contenitore OSGi come all'incirca analogo a una JVM, con poteri aggiuntivi. Allo stesso modo, pensa ai bundle come ad applicazioni Java con capacità uniche. I bundle vengono eseguiti all'interno del contenitore OSGi come componenti client e server.

L'alleanza OSGi

OSGi è iniziato nel 1999 e, a differenza di molte altre specifiche, lo standard non è gestito da Oracle, Java Community Process o Eclipse Foundation. Invece, è gestito dall'alleanza OSGi.

In che modo OSGi è diverso

La filosofia di OSGi è diversa da quella di altri framework basati su Java, in particolare Spring. In OSGi, possono esistere più applicazioni all'interno dello stesso contenitore: l' ambiente di runtime del bundle OSGi . Il contenitore garantisce che ogni componente sia sufficientemente isolato e abbia anche accesso a qualsiasi dipendenza richiesta. OSGi può supportare l'inserimento delle dipendenze, standardizzato dal progetto Aries Blueprint. Oltre a fornire il contenitore IoC (inversion of control) di OSGi, Aries supporta framework Java standard come Java Persistence API (JPA).

In OSGi, i bundle possono esporre i servizi utilizzati da altri bundle. Un bundle può anche dichiarare una versione e può definire da quali altri bundle dipende. Il runtime caricherà quindi automaticamente tutti i suoi bundle in ordine di dipendenza. In OSGi, possono coesistere più versioni dello stesso bundle, se richiesto dalle dipendenze del bundle.

OSGi in Eclipse IDE ed Equinox

OSGi esiste in qualche modo da un paio di decenni. Viene utilizzato per molte applicazioni note, dai dispositivi mobili incorporati agli application server e agli IDE.

Il popolare IDE Eclipse è costruito su OSGi. L'implementazione di Eclipse del contenitore OSGi si chiama Equinox. È un ottimo esempio per comprendere OSGi. Essere basato su OSGi significa che Equinox è una piattaforma modulare. Ospita una varietà di servizi che gli sviluppatori possono aggiungere a piacimento. Ognuno di questi offre una funzionalità di cui uno sviluppatore potrebbe aver bisogno nel proprio IDE. È possibile aggiungere editor per Java e JavaScript, un server app e un connettore di database. Ognuno di questi è implementato come bundle OSGi che viene aggiunto al contenitore e può interagire con altri servizi nel contenitore.

Recentemente, c'è stato un aumento di interesse nell'utilizzo di OSGi per l'Internet of Things (IoT). OSGi è una scelta naturale per questo tipo di sviluppo, che ha una varietà di componenti software in esecuzione fianco a fianco sui dispositivi, senza necessariamente conoscersi. Un contenitore OSGi fornisce un modo semplice e standardizzato per ospitare questi componenti software dinamici.

Utilizzo di OSGi in un progetto Java: Knoplerfish OSGi

Lavoreremo attraverso un'applicazione di esempio che renderà i concetti OSGi più concreti. Il nostro esempio si basa sul runtime OSGi Knoplerfish, utilizzato in molte distribuzioni di produzione. Knoplerfish include una GUI e un'interfaccia a riga di comando (CLI) per la gestione del contenitore OSGi e dei suoi bundle.

La prima cosa che farai è scaricare Knoplerfish. La versione corrente al momento della stesura di questo articolo è Knoplerfish OSGi 6.1.3. Puoi sostituire quella versione con quella più recente quando leggi questo articolo.

Dopo aver scaricato e installato Knoplerfish, utilizzare la CLI per cadere nella directory in cui è stato scaricato il file JAR, e immettere: java -jar framework.jar. Questo eseguirà il JAR eseguibile e dovresti essere accolto con una finestra della GUI.

La GUI OSGi di Knoplerfish

La GUI di Knoplerfish OSGi può sembrare opprimente all'inizio, ma le basi sono semplici:

  • Nella parte superiore dello schermo c'è il menu.
  • A sinistra è il set di bundle che sono stati caricati nel runtime.
  • A destra c'è una finestra informativa.
  • In fondo c'è una console di output del testo.
  • In fondo c'è una console di input.
Matthew Tyson

Digita helpnella console di input se desideri visualizzare le opzioni della guida.

Prima di passare all'esempio, dai un'occhiata al set di bundle in esecuzione. Vedrai un bundle chiamato HTTP Server, il che significa che un bundle che esegue un server HTTP è attivo. Vai al tuo browser e controlla // localhost: 8080. Abbastanza sicuro, vedrai una pagina web di Knoplerfish.

Il pacchetto "Hello JavaWorld"

Usiamo il runtime OSGi per creare un semplice bundle, che chiamerò Hello JavaWorld. Questo pacchetto restituisce un messaggio alla console.

Nel listato 1, usiamo Maven per creare il bundle. Ha una sola dipendenza, fornita dall'alleanza OSGi.

Listato 1. Dipendenza OSGi nel POM di Maven

   org.osgi org.osgi.core   

Ora utilizzeremo anche un plug-in, grazie al progetto Apache Felix. Questo plug-in si occupa di impacchettare l'app come bundle OSGi per l'uso. Il Listato 2 mostra la configurazione che useremo.

Listato 2. Plug-in OSGi Felix nel POM di Maven

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

Ora possiamo dare un'occhiata alla semplice classe che produrrà un "Hello".

Listato 3. Hello JavaWorld OSGi bundle

 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) { } } 

Crea il pacchetto andando alla riga di comando e digitando mvn clean install. Questo produrrà un file JAR contenente il bundle. Ora vai al Filemenu nella GUI di Knoplerfish e seleziona Add Bundle. Ciò fornirà un browser di file. Trova il JAR che abbiamo appena creato e selezionalo.

Gestione dei bundle OSGi nel contenitore

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.

Infine, si noti che il precedente articolo della serie "Che cos'è: Java" ha introdotto il Java Platform Module System, che offre un approccio diverso alla stessa sfida della modularità Java.

Questa storia, "Cos'è OSGi? Un approccio diverso alla modularità Java" è stata originariamente pubblicata da JavaWorld.