1. Introducción
Varias aplicaciones Java de misión crítica y middleware tienen algunos requisitos tecnológicos estrictos.
Algunos tienen que admitir la implementación en caliente, para no interrumpir los servicios en ejecución, y otros deben poder trabajar con diferentes versiones del mismo paquete para admitir sistemas heredados externos.
Las plataformas OSGi representan una solución viable para soportar este tipo de requisitos.
La iniciativa Open Service Gateway es una especificación que define un sistema de componentes basado en Java. Actualmente está administrado por OSGi Alliance , y su primera versión se remonta a 1999.
Desde entonces, ha demostrado ser un gran estándar para los sistemas de componentes y se usa ampliamente en la actualidad. El Eclipse IDE , por ejemplo, es una aplicación basada en OSGi .
En este artículo, exploraremos algunas características básicas de OSGi aprovechando la implementación proporcionada por Apache .
2. Conceptos básicos de OSGi
En OSGi, un solo componente se denomina paquete.
Lógicamente, un paquete es una pieza de funcionalidad que tiene un ciclo de vida independiente, lo que significa que se puede iniciar, detener y eliminar de forma independiente.
Técnicamente, un paquete es solo un archivo jar con un archivo MANIFEST.MF que contiene algunos encabezados específicos de OSGi.
La plataforma OSGi proporciona una forma de recibir notificaciones sobre la disponibilidad de paquetes o cuando se eliminan de la plataforma. Esto permitirá que un cliente correctamente diseñado siga funcionando, tal vez con una funcionalidad degradada, incluso cuando un servicio del que depende, no esté disponible momentáneamente.
Por eso, un paquete tiene que declarar explícitamente a qué paquetes necesita tener acceso y la plataforma OSGi lo iniciará solo si las dependencias están disponibles en el paquete o en otros paquetes ya instalados en la plataforma.
3. Obtener las herramientas
Comenzaremos nuestro viaje en OSGi descargando la última versión de Apache Karaf desde este enlace. Apache Karaf es una plataforma que ejecuta aplicaciones basadas en OSG ; se basa en la Apache implementación de 's OSGi especificación llamada Apache Felix .
Karaf ofrece algunas funciones útiles además de Felix que nos ayudarán a familiarizarnos con OSGi , por ejemplo, una interfaz de línea de comandos que nos permitirá interactuar con la plataforma.
Para instalar Karaf , puede seguir las instrucciones de instalación de la documentación oficial.
4. Punto de entrada del paquete
Para ejecutar una aplicación en un entorno OSGi, tenemos que empaquetarla como un paquete OSGi y definir el punto de entrada de la aplicación, y ese no es el método principal público vacío estático (String [] args) .
Entonces, comencemos por construir una aplicación “Hello World” basada en OSG .
Comenzamos a configurar una dependencia simple en la API central de OSGi :
org.osgi org.osgi.core 6.0.0 provided
La dependencia se declara como se proporciona porque estará disponible en el tiempo de ejecución de OSGi y el paquete no necesita incrustarla.
Escribamos ahora la clase simple 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 es una interfaz proporcionada por OSGi que debe ser implementada por clases que son puntos de entrada para un paquete.
La plataforma OSGi invoca el método start () cuando se inicia el paquete que contiene esta clase. Por otro lado, stop () se invoca antes justo antes de que se detenga el paquete.
Tengamos en cuenta que cada paquete puede contener como máximo un BundleActivator . El objeto BundleContext proporcionado a ambos métodos permite interactuar con el tiempo de ejecución de OSGi . Volveremos pronto.
5. Construyendo un paquete
Modifiquemos el pom.xml y conviértalo en un paquete OSGi real.
En primer lugar, tenemos que indicar explícitamente que vamos a crear un paquete, no un frasco:
bundle
Luego aprovechamos el plugin maven-bundle-plugin, cortesía de la comunidad Apache Felix , para empaquetar la clase HelloWorld como un paquete 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
En la sección de instrucciones, especificamos los valores de los encabezados OSGi que queremos incluir en el archivo MANIFEST del paquete.
Bundle-Activator es el nombre completo de la implementación de BundleActivator que se utilizará para iniciar y detener el paquete, y se refiere a la clase que acabamos de escribir.
Private-Package no es un encabezado OSGi, pero se usa para decirle al complemento que incluya el paquete en el paquete pero que no lo ponga a disposición de otros. Ahora podemos construir el paquete con el comando habitual mvn clean install .
6. Instalación y ejecución del paquete
Comencemos Karaf ejecutando el comando:
/bin/karaf start
dónde es la carpeta donde está instalado Karaf . Cuando aparezca el indicador de la consola Karaf , podemos ejecutar el siguiente comando para instalar el paquete:
> bundle:install mvn:com.baeldung/osgi-intro-sample-activator/1.0-SNAPSHOT Bundle ID: 63
Esto indica a Karaf que cargue el paquete desde el repositorio local de Maven.
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.
Luego, el cliente obtiene una referencia al servicio y la usa para invocar la implementación entregada a través del paquete de servicios.
10. Conclusión
En este artículo, exploramos las características esenciales de OSGi con un ejemplo sencillo de que es suficiente para comprender el potencial de OSGi.
En conclusión, siempre que tengamos que garantizar que una sola aplicación debe actualizarse sin ningún perjuicio, OSGi puede ser una solución viable.
El código de esta publicación se puede encontrar en GitHub.