¿Qué es OSGi? Un enfoque diferente a la modularidad de Java

OSGi facilita la creación y gestión de componentes Java modulares (denominados paquetes ) que se pueden implementar en un contenedor. Como desarrollador, utiliza la especificación y las herramientas OSGi para crear uno o más paquetes. OSGi define el ciclo de vida de estos paquetes. También los aloja y apoya sus interacciones en un contenedor. Puede pensar en un contenedor OSGi como aproximadamente análogo a una JVM, con poderes adicionales. Del mismo modo, piense en los paquetes como aplicaciones Java con capacidades únicas. Los paquetes se ejecutan dentro del contenedor OSGi como componentes de cliente y servidor.

La alianza OSGi

OSGi comenzó en 1999 y, a diferencia de muchas otras especificaciones, el estándar no es administrado por Oracle, Java Community Process o Eclipse Foundation. En cambio, es administrado por la alianza OSGi.

En qué se diferencia OSGi

La filosofía de OSGi difiere de la de otros marcos basados ​​en Java, sobre todo Spring. En OSGi, pueden existir varias aplicaciones dentro del mismo contenedor: el entorno de tiempo de ejecución del paquete OSGi . El contenedor asegura que cada componente esté suficientemente aislado y también tenga acceso a las dependencias que necesite. OSGi puede admitir la inyección de dependencia, que está estandarizada por el proyecto Aries Blueprint. Además de proporcionar el contenedor de inversión de control (IoC) de OSGi, Aries admite marcos estándar de Java como la API de persistencia de Java (JPA).

En OSGi, los paquetes pueden exponer servicios que utilizan otros paquetes. Un paquete también puede declarar una versión y puede definir de qué otros paquetes depende. El tiempo de ejecución cargará automáticamente todos sus paquetes en orden de dependencia. En OSGi, pueden existir varias versiones del mismo paquete en paralelo, si así lo requieren las dependencias del paquete.

OSGi en Eclipse IDE y Equinox

OSGi ha existido de alguna forma durante un par de décadas. Se utiliza para muchas aplicaciones conocidas, desde dispositivos móviles integrados hasta servidores de aplicaciones e IDE.

El popular Eclipse IDE está construido sobre OSGi. La implementación de Eclipse del contenedor OSGi se llama Equinox. Es un gran ejemplo para entender OSGi. Estar basado en OSGi significa que Equinox es una plataforma modular. Alberga una variedad de servicios que los desarrolladores pueden agregar a voluntad. Cada uno de estos ofrece una capacidad que un desarrollador podría necesitar en su IDE. Puede agregar editores para Java y JavaScript, un servidor de aplicaciones y un conector de base de datos. Cada uno de estos se implementa como un paquete OSGi que se agrega al contenedor y puede interactuar con otros servicios en el contenedor.

Recientemente, ha habido un aumento del interés en el uso de OSGi para Internet de las cosas (IoT). OSGi se adapta naturalmente a este tipo de desarrollo, que tiene una variedad de componentes de software que se ejecutan uno al lado del otro en los dispositivos, sin que necesariamente se conozcan entre sí. Un contenedor OSGi proporciona una forma simple y estandarizada de alojar estos componentes de software dinámicos.

Uso de OSGi en un proyecto Java: Knoplerfish OSGi

Trabajaremos a través de una aplicación de ejemplo que hará que los conceptos de OSGi sean más concretos. Nuestro ejemplo se basa en el tiempo de ejecución Knoplerfish OSGi, que se utiliza en muchas implementaciones de producción. Knoplerfish incluye una GUI y una interfaz de línea de comandos (CLI) para administrar el contenedor OSGi y sus paquetes.

Lo primero que harás es descargar Knoplerfish. La versión actual en el momento de escribir este artículo es Knoplerfish OSGi 6.1.3. Puede reemplazar esa versión con la más actual cuando lea este artículo.

Una vez que haya descargado e instalado Knoplerfish, utilizar la CLI para caer en el directorio donde descargó el archivo JAR, y escriba: java -jar framework.jar. Eso ejecutará el JAR ejecutable y debería ser recibido con una ventana GUI.

La GUI de Knoplerfish OSGi

La GUI de Knoplerfish OSGi puede parecer abrumadora al principio, pero los conceptos básicos son simples:

  • En la parte superior de la pantalla está el menú.
  • A la izquierda está el conjunto de paquetes que se han cargado en el tiempo de ejecución.
  • A la derecha hay una ventana de información.
  • En la parte inferior hay una consola de salida de texto.
  • En la parte inferior hay una consola de entrada.
Matthew Tyson

Escriba helpen la consola de entrada si desea ver las opciones de ayuda.

Antes de pasar al ejemplo, observe el conjunto de paquetes en ejecución. Verá un paquete llamado HTTP Server, lo que significa que un paquete que ejecuta un servidor HTTP está activo. Vaya a su navegador y consulte // localhost: 8080. Efectivamente, verá una página web de Knoplerfish.

El paquete 'Hello JavaWorld'

Usemos el tiempo de ejecución de OSGi para construir un paquete simple, al que llamaré Hello JavaWorld. Este paquete envía un mensaje a la consola.

En el Listado 1, usamos Maven para construir el paquete. Tiene solo una dependencia, que es proporcionada por la alianza OSGi.

Listado 1. Dependencia de OSGi en Maven POM

   org.osgi org.osgi.core   

Ahora, también usaremos un complemento, cortesía del proyecto Apache Felix. Este complemento se encarga de empaquetar la aplicación como un paquete OSGi para su uso. El Listado 2 muestra la configuración que usaremos.

Listado 2. Complemento OSGi Felix en Maven POM

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

Ahora podemos echar un vistazo a la clase simple que generará un "Hola".

Listado 3. Hola paquete 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) { } } 

Cree el paquete yendo a la línea de comandos y escribiendo mvn clean install. Esto generará un archivo JAR que contiene el paquete. Ahora, vaya al Filemenú en la GUI de Knoplerfish y seleccione Add Bundle. Esto proporcionará un explorador de archivos. Busque el JAR que acabamos de crear y selecciónelo.

Administrar paquetes OSGi en el contenedor

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.

Por último, tenga en cuenta que el artículo anterior de la serie "Qué es: Java" presentó el Java Platform Module System, que ofrece un enfoque diferente al mismo desafío de la modularidad de Java.

Esta historia, "¿Qué es OSGi? Un enfoque diferente a la modularidad de Java" fue publicada originalmente por JavaWorld.