Introduzione a OSGi

1. Introduzione

Diverse applicazioni Java mission-critical e middleware hanno alcuni requisiti tecnologici rigidi.

Alcuni devono supportare la distribuzione a caldo, in modo da non interrompere i servizi in esecuzione, e altri devono essere in grado di lavorare con versioni diverse dello stesso pacchetto per supportare sistemi legacy esterni.

Le piattaforme OSGi rappresentano una valida soluzione per supportare questo tipo di requisiti.

Il Gateway Initiative Open Service è una specifica che definisce un sistema di componenti Java-based. Attualmente è gestito da OSGi Alliance e la sua prima versione risale al 1999.

Da allora, ha dimostrato di essere un ottimo standard per i sistemi a componenti ed è ampiamente utilizzato al giorno d'oggi. L' IDE Eclipse , ad esempio, è un'applicazione basata su OSGi .

In questo articolo, esploreremo alcune funzionalità di base di OSGi sfruttando l'implementazione fornita da Apache .

2. Nozioni di base su OSGi

In OSGi, un singolo componente è chiamato bundle.

Logicamente, un bundle è una parte di funzionalità che ha un ciclo di vita indipendente, il che significa che può essere avviato, arrestato e rimosso in modo indipendente.

Tecnicamente, un bundle è solo un file jar con un file MANIFEST.MF contenente alcune intestazioni specifiche di OSGi.

La piattaforma OSGi fornisce un modo per ricevere notifiche sui pacchetti che diventano disponibili o quando vengono rimossi dalla piattaforma. Ciò consentirà a un client progettato correttamente di continuare a lavorare, magari con funzionalità degradate, anche quando un servizio da cui dipende è momentaneamente non disponibile.

Per questo motivo, un bundle deve dichiarare esplicitamente a quali pacchetti deve avere accesso e la piattaforma OSGi lo avvierà solo se le dipendenze sono disponibili nel bundle stesso o in altri bundle già installati nella piattaforma.

3. Ottenere gli strumenti

Inizieremo il nostro viaggio in OSGi scaricando l'ultima versione di Apache Karaf da questo link. Apache Karaf è una piattaforma che esegue applicazioni basate su OSGi ; è basato sull'implementazione di Apache della specifica OSGi chiamata Apache Felix .

Karaf offre alcune utili funzionalità oltre a Felix che ci aiuteranno a familiarizzare con OSGi , ad esempio, un'interfaccia a riga di comando che ci permetterà di interagire con la piattaforma.

Per installare Karaf , puoi seguire le istruzioni di installazione dalla documentazione ufficiale.

4. Punto di ingresso del pacchetto

Per eseguire un'applicazione in un ambiente OSGi, dobbiamo comprimerla come bundle OSGi e definire il punto di ingresso dell'applicazione, e questo non è il solito metodo public static void main (String [] args) .

Quindi, iniziamo creando un'applicazione "Hello World" basata su OSGi .

Iniziamo a impostare una semplice dipendenza dall'API OSGi principale :

 org.osgi org.osgi.core 6.0.0 provided 

La dipendenza è dichiarata come fornita perché sarà disponibile nel runtime OSGi e il bundle non ha bisogno di incorporarla.

Scriviamo ora la semplice classe HelloWorld :

public class HelloWorld implements BundleActivator { public void start(BundleContext ctx) { System.out.println("Hello world."); } public void stop(BundleContext bundleContext) { System.out.println("Goodbye world."); } }

BundleActivator è un'interfaccia fornita da OSGi che deve essere implementata da classi che sono punti di ingresso per un bundle.

Il metodo start () viene richiamato dalla piattaforma OSGi quando viene avviato il bundle contenente questa classe. D'altra parte stop () viene invocato prima che il bundle venga fermato.

Ricordiamo che ogni bundle può contenere al massimo un BundleActivator . L' oggetto BundleContext fornito a entrambi i metodi consente l'interazione con il runtime OSGi . Ci torneremo presto.

5. Creazione di un pacchetto

Modifichiamo il pom.xml e rendiamolo un vero e proprio bundle OSGi.

Prima di tutto, dobbiamo dichiarare esplicitamente che costruiremo un bundle, non un jar:

bundle

Quindi sfruttiamo il plugin maven-bundle, per gentile concessione della comunità di Apache Felix , per impacchettare la classe HelloWorld come bundle OSGi :

 org.apache.felix maven-bundle-plugin 3.3.0 true    ${pom.groupId}.${pom.artifactId}  ${pom.name} ${pom.version}  com.baeldung.osgi.sample.activator.HelloWorld   com.baeldung.osgi.sample.activator    

Nella sezione delle istruzioni, specifichiamo i valori delle intestazioni OSGi che vogliamo includere nel file MANIFEST del bundle.

Bundle-Activator è il nome completo dell'implementazione BundleActivator che verrà utilizzato per avviare e arrestare il bundle e si riferisce alla classe che abbiamo appena scritto.

Private-Package non è un'intestazione OSGi, ma è usata per dire al plugin di includere il pacchetto nel bundle ma non renderlo disponibile ad altri. Ora possiamo costruire il bundle con il solito comando mvn clean install .

6. Installazione ed esecuzione del bundle

Cominciamo Karaf eseguendo il comando:

/bin/karaf start

dove è la cartella in cui è installato Karaf . Quando viene visualizzato il prompt della console Karaf , possiamo eseguire il seguente comando per installare il bundle:

> bundle:install mvn:com.baeldung/osgi-intro-sample-activator/1.0-SNAPSHOT Bundle ID: 63

Questo indica a Karaf di caricare il bundle dal repository Maven locale.

In return Karaf prints out the numeric ID assigned to the bundle that depends on the number of bundles already installed and may vary. The bundle is now just installed, we can now start it with the following command:

> bundle:start 63 Hello World

“Hello World” immediately appears as soon the bundle is started. We can now stop and uninstall the bundle with:

> bundle:stop 63 > bundle:uninstall 63

“Goodbye World” appears on the console, accordingly to the code in the stop() method.

7. An OSGi Service

Let's go on writing a simple OSGi service, an interface that exposes a method for greeting people:

package com.baeldung.osgi.sample.service.definition; public interface Greeter { public String sayHiTo(String name); }

Let's write an implementation of it that is a BundleActivator too, so we'll be able to instantiate the service and register it on the platform when the bundle is started:

package com.baeldung.osgi.sample.service.implementation; public class GreeterImpl implements Greeter, BundleActivator { private ServiceReference reference; private ServiceRegistration registration; @Override public String sayHiTo(String name) { return "Hello " + name; } @Override public void start(BundleContext context) throws Exception { System.out.println("Registering service."); registration = context.registerService( Greeter.class, new GreeterImpl(), new Hashtable()); reference = registration .getReference(); } @Override public void stop(BundleContext context) throws Exception { System.out.println("Unregistering service."); registration.unregister(); } }

We use the BundleContext as a mean of requesting the OSGi platform to register a new instance of the service.

We should also provide the type of the service and a map of the possible configuration parameters, which aren't needed in our simple scenario. Let's now proceed with the configuration of the maven-bundle-plugin:

 org.apache.felix maven-bundle-plugin true    ${project.groupId}.${project.artifactId}   ${project.artifactId}   ${project.version}   com.baeldung.osgi.sample.service.implementation.GreeterImpl   com.baeldung.osgi.sample.service.implementation   com.baeldung.osgi.sample.service.definition    

It's worth noting that only the com.baeldung.osgi.sample.service.definition package has been exported this time, through the Export-Package header.

Thanks to this, OSGi will allow other bundles to invoke only the methods specified in the service interface. Package com.baeldung.osgi.sample.service.implementation is marked as private, so no other bundle will be able to access the members of the implementation directly.

8. An OSGi Client

Let's now write the client. It simply looks up the service at startup and invokes it:

public class Client implements BundleActivator, ServiceListener { }

Let's implement the BundleActivator start() method:

private BundleContext ctx; private ServiceReference serviceReference; public void start(BundleContext ctx) { this.ctx = ctx; try { ctx.addServiceListener( this, "(objectclass=" + Greeter.class.getName() + ")"); } catch (InvalidSyntaxException ise) { ise.printStackTrace(); } }

The addServiceListener() method allows the client to ask the platform to send notifications about the service that complies with the provided expression.

The expression uses a syntax similar to the LDAP's one, and in our case, we're requesting notifications about a Greeter service.

Let's go on to the callback method:

public void serviceChanged(ServiceEvent serviceEvent) { int type = serviceEvent.getType(); switch (type){ case(ServiceEvent.REGISTERED): System.out.println("Notification of service registered."); serviceReference = serviceEvent .getServiceReference(); Greeter service = (Greeter)(ctx.getService(serviceReference)); System.out.println( service.sayHiTo("John") ); break; case(ServiceEvent.UNREGISTERING): System.out.println("Notification of service unregistered."); ctx.ungetService(serviceEvent.getServiceReference()); break; default: break; } }

When some modification involving the Greeter service happens, the method is notified.

When the service is registered to the platform, we get a reference to it, we store it locally, and we then use it to acquire the service object and invoke it.

When the server is later unregistered, we use the previously stored reference to unget it, meaning that we tell the platform that we are not going to use it anymore.

We now just need to write the stop() method:

public void stop(BundleContext bundleContext) { if(serviceReference != null) { ctx.ungetService(serviceReference); } }

Here again, we unget the service to cover the case in which the client is stopped before the service is being stopped. Let's give a final look at the dependencies in the pom.xml:

 com.baeldung osgi-intro-sample-service 1.0-SNAPSHOT provided   org.osgi org.osgi.core 6.0.0 

9. Client and Service

Let's now install the client and service bundles in Karaf by doing:

> install mvn:com.baeldung/osgi-intro-sample-service/1.0-SNAPSHOT Bundle ID: 64 > install mvn:com.baeldung/osgi-intro-sample-client/1.0-SNAPSHOT Bundle ID: 65

Always keep in mind that the identifier numbers assigned to each bundle may vary.

Let's now start the client bundle:

> start 65

Therefore, nothing happens because the client is active and it's waiting for the service, that we can start with:

> start 64 Registering service. Service registered. Hello John

What happens is that as soon as the service's BundleActivator starts, the service is registered to the platform. That, in turn, notifies the client that the service it was waiting for is available.

Il client ottiene quindi un riferimento al servizio e lo utilizza per richiamare l'implementazione fornita tramite il pacchetto di servizi.

10. Conclusione

In questo articolo, abbiamo esplorato le caratteristiche essenziali di OSGi con un semplice esempio che è sufficiente per comprendere il potenziale di OSGi.

In conclusione, ogniqualvolta dobbiamo garantire che una singola applicazione debba essere aggiornata senza alcun disservizio, OSGi può essere una valida soluzione.

Il codice per questo post può essere trovato su GitHub.