Servicios y Proveedores de Servicios como Módulos

Avanzado

En la programación, a menudo es útil separar lo que se debe hacer de cómo se hace. Como aprendió anteriormente, una forma de lograr esto en Java es mediante el uso de interfaces. La interfaz especifica qué, y la clase de implementación especifica cómo. Este concepto se puede ampliar para que la clase de implementación sea provista por código que está fuera de su programa, mediante el uso de un plug-in.

Utilizando dicho enfoque, las capacidades de una aplicación pueden mejorarse, actualizarse o modificarse simplemente cambiando el plug-in. El núcleo de la aplicación en sí permanece sin cambios. Una forma en que Java admite una arquitectura de aplicación conectable es mediante el uso de servicios y proveedores de servicios. Debido a su importancia, especialmente en grandes aplicaciones comerciales, el sistema de módulos de Java les brinda soporte.

Antes de comenzar, es necesario indicar que las aplicaciones que usan servicios y proveedores de servicios suelen ser bastante sofisticadas. Por lo tanto, puede descubrir que a menudo no necesita las características de módulos basado en servicios. Sin embargo, dado que el soporte para los servicios constituye una parte bastante importante del sistema de módulos, es importante que tenga una comprensión general de cómo funcionan estas características. Además, se presenta un ejemplo simple que ilustra las técnicas básicas necesarias para usarlas.

1. Fundamentos de Servicios y Proveedores de Servicios

En Java, un servicio es una unidad de programa cuya funcionalidad está definida por una interfaz o una clase abstracta. Por lo tanto, un servicio especifica de forma general alguna forma de actividad del programa. Una implementación concreta de un servicio es suministrada por un proveedor de servicios. En otras palabras, un servicio define la forma de alguna acción, y el proveedor de servicios suministra esa acción.

Como se mencionó, los servicios se usan a menudo para admitir una arquitectura conectable. Por ejemplo, un servicio puede ser usado para soportar la traducción de un idioma a otro. En este caso, el servicio admite la traducción en general. El proveedor de servicios proporciona una traducción específica, como alemán a inglés o francés a chino. Debido a que todos los proveedores de servicios implementan la misma interfaz, se pueden usar diferentes traductores para traducir diferentes idiomas sin tener que cambiar el núcleo de la aplicación. Simplemente puede cambiar el proveedor de servicios.

Los proveedores de servicios son compatibles con la clase ServiceLoader. ServiceLoader es una clase genérica empaquetada en java.util. Se declara así:

class ServiceLoader<S>

Aquí, S especifica el tipo de servicio. Los proveedores de servicios son cargados por el método load(). Tiene varias formas; el que usaremos se muestra aquí:

public static <S> ServiceLoader<S> load(Class <S> tipoServicio)

Aquí, tipoServicio especifica el objeto Class para el tipo de servicio deseado. Recuerda que Class es una clase que encapsula información sobre una clase. Hay una variedad de formas de obtener una instancia de Clase. La forma en que usaremos aquí se llama literal de clase. Un literal de clase tiene esta forma general:

nombreClase.class

Aquí, nombreClase especifica el nombre de la clase.

Cuando se invoca load(), devuelve una instancia de ServiceLoader para la aplicación. Este objeto admite la iteración y se puede pasar cíclicamente mediante el uso de un bucle for-each. Por lo tanto, para encontrar un proveedor específico, simplemente búscalo utilizando un bucle.

2. Palabras clave basadas en servicio

Los módulos soportan servicios a través del uso de las palabras clave provides, uses, y with. Esencialmente, un módulo especifica que proporciona un servicio con una declaración provides. Un módulo indica que requiere un servicio con una declaración uses. El tipo específico de proveedor de servicios es declarado por with. Cuando se usan juntos, le permiten especificar un módulo que proporciona un servicio, un módulo que necesita ese servicio y la implementación específica de ese servicio. Además, el sistema de módulos garantiza que los proveedores de servicios y servicios estén disponibles y se encuentren.

  • Aquí está la forma general de provides:
provides tipoServicio with tiposImplementacion;

Aquí, tipoServicio especifica el tipo de servicio, que a menudo es una interfaz, aunque también se utilizan clases abstractas. Los tiposImplementacion especifican una lista de tipos de implementación separada por comas. Por lo tanto, para proporcionar un servicio, el módulo indica tanto el nombre del servicio como su implementación.

  • Aquí está la forma general de uses:
uses tipoServicio

Aquí, tipoServicio especifica el tipo de servicio requerido.

3. Ejemplo de servicio basado en módulos

Para demostrar el uso de los servicios, agregaremos un servicio al ejemplo de aplicación modular que hemos estado usando (Leer sobre Módulos en Java). Para simplificar, comenzaremos con la primera versión de la aplicación. A ello agregaremos dos nuevos módulos. El primero se llama funciones. Definirá interfaces que soportan funciones que realizan operaciones binarias en las cuales cada argumento es un int y el resultado es un int. El segundo módulo se llama funcionesimp y contiene implementaciones concretas de las interfaces.

Comience por crear los directorios de origen necesarios.

  1. En el directorio appsrc, agregue directorios llamados funciones y funcionesimp.
  2. En funciones, agregue el subdirectorio también llamado funciones. Luego, en ese directorio, agregue el subdirectorio funcionesbin. Por lo tanto, comenzando con appsrc, habrá creado este árbol: appsrc\funciones\funciones\funcionesbin
  3. Por último, en funcionesimp, agregue el subdirectorio también llamado funcionesimp. En ese directorio, agregue el subdirectorio funcionesimpbin. Por lo tanto, comenzando con appsrc, habrá creado este árbol: appsrc\funcionesimp\funcionesimp\funcionesimpbin

3.1. Las interfaces de servicio

Se necesitan dos interfaces relacionadas con el servicio. Uno especifica la forma de una acción, y el otro especifica la forma del proveedor de esa acción. Ambos van en el directorio funcionesbin, y ambos están en el paquete funciones.funcionesbin. El primero, llamado FuncBinarias, declara la forma de una función binaria. Se muestra aquí:

// Esta interfaz define una función que toma dos argumentos int y devuelve un resultado int.
// Por lo tanto, puede describir cualquier operación binaria en dos entradas que devuelve un int.

package funciones.funcionesbin;
public interface FuncBinarias{
    // Obtiene el nombre de la función.
    public String getNombre();

    // Esta es la función a realizar. 
    // Será provista por implementaciones específicas.
    public int func(int a, int b);
}

FuncBinarias declara la forma de un objeto que puede implementar una función entera binaria. Esto se especifica mediante el método func(). El nombre de la función se puede obtener de getNombre(). El nombre se usará para determinar qué tipo de función se implementa. Esta interfaz es implementada por una clase que proporciona una función binaria.

La segunda interfaz declara la forma del proveedor del servicio. Se llama FuncBinProveedor y se muestra aquí:

// Esta interfaz define la forma de un proveedor de servicios
// que obtiene instancias de FuncBinarias.

package funciones.funcionesbin;

import funciones.funcionesbin.FuncBinarias;

public interface FuncBinProveedor {

    // Obtiene un FuncBinarias
    public FuncBinarias get();
}

FuncBinProveedor declara solo un método, get(), que se usa para obtener una instancia de FuncBinarias. Esta interfaz debe ser implementada por una clase que quiera proporcionar instancias de FuncBinarias.

3.2. Las clases de implementación

En este ejemplo, se admiten dos implementaciones concretas de FuncBinarias. El primero es SumAbs, que devuelve la suma de los valores absolutos de sus argumentos. El segundo es ResAbs, que devuelve el resultado de restar el valor absoluto del segundo argumento del valor absoluto del primer argumento. Estos son proporcionados por las clases SumAbsProveedorResAbsProveedor. El código fuente de estas clases debe almacenarse en el directorio funcionesimpbin, y todos son parte del paquete funcionesimp.funcionesimpbin.

El código para SumAbs se muestra aquí:

// SumAbs proporciona una implementación concreta de FuncBinarias.
// Devuelve el resultado de abs (a) + abs (b).

package funcionesimp.funcionesimpbin;

import funciones.funcionesbin.FuncBinarias;

public class SumAbs implements FuncBinarias {
    // Devuelve el nombre de esta función.
    public String getNombre(){
        return "sumarAbsolutos";
    }

    //Implementa la función SumAbs.
    public int func(int a, int b){
        return Math.abs(a)+Math.abs(b);
    }
}

SumAbs implementa func() tal que devuelve el resultado de sumar los valores absolutos de a y b. Observe que getNombre() devuelve la cadena “sumarAbsolutos”. Identifica esta función.

La clase ResAbs se muestra a continuación:

// ResAbs proporciona una implementación concreta de FuncBinarias.
// Devuelve el resultado de abs (a) - abs (b).

package funcionesimp.funcionesimpbin;
import funciones.funcionesbin.FuncBinarias;

public class ResAbs implements FuncBinarias{

    // Devuelve el nombre de esta función.
    public String getNombre(){
        return "restarAbsolutos";
    }

    //Implementa la función ResAbs.
    public int func(int a, int b){
        return Math.abs(a)- Math.abs(b);
    }
}

Aquí, func() se implementa para devolver la diferencia entre los valores absolutos de a y b, y getNombre() devuelve la cadena “restarAbsolutos”.

Para obtener una instancia de SumAbs, se utiliza SumAbsProveedor. Implementa BinFuncProvider y se muestra aquí:

// Este es un proveedor para la función SumAbs.

package funcionesimp.funcionesimpbin;

import funciones.funcionesbin.*;

public class SumAbsProveedor implements FuncBinProveedor {

    // Proporciona un objeto SumAbs.
    public FuncBinarias get(){
        // Devuelve un objeto SumAbs.
        return new SumAbs();
    }
}

El método get() simplemente devuelve un nuevo objeto SumAbs(). Aunque este proveedor es muy simple, es importante señalar que algunos proveedores de servicios serán mucho más complejos.

El proveedor de ResAbs se llama ResAbsProveedor y se muestra a continuación:

// Este es un proveedor para la función ResAbs.

package funcionesimp.funcionesimpbin;

import funciones.funcionesbin.*;

public class ResAbsProveedor implements FuncBinProveedor {

    // Proporciona un objeto ResAbs.
    public FuncBinarias get(){
        return new ResAbs();
    }
}

Su método get() devuelve un objeto de ResAbs.

3.3. Los archivos de definición del módulo

A continuación, se necesitan dos archivos de definición de módulo. El primero es para el módulo userfuncs. Se muestra aquí:

Este código debe estar contenido en un archivo module-info.java que se encuentra en el directorio del módulo funciones. Tenga en cuenta que exporta el paquete funciones.funcionesbin. Este es el paquete que define las interfaces FuncBinarias y FuncBinProveedor.

module funciones{
    exports funciones.funcionesbin;
}

El segundo archivo module-info.java se muestra a continuación. Define el módulo que contiene las implementaciones. Va en el directorio del módulo funcionesimp.

module funcionesimp{
    requires funciones;

    provides funciones.funcionesbin.FuncBinProveedor with
            funcionesimp.funcionesimpbin.SumAbsProveedor,
            funcionesimp.funcionesimpbin.ResAbsProveedor;
}

Este módulo requiere funciones porque ahí es donde están contenidos FuncBinarias y FuncBinProveedor, y esas interfaces son necesarias para las implementaciones. El módulo proporciona implementaciones de FuncBinProveedor con las clases SumAbsProveedor y ResAbsProveedor.

3.4. Demostración de proveedores de servicios

Para demostrar el uso de los servicios, el método main() de MiAppModDemo se expande para usar SumAbs y ResAbs. Lo hace al cargarlos en tiempo de ejecución mediante el uso de ServiceLoader.load(). Aquí está el código actualizado:

// Una aplicación basada en módulos
// que demuestra servicios y proveedores de servicios.

package appinicio.midemoappmod;

import java.util.ServiceLoader;
import appfuncs.funcsimples.FuncsMateSimples;
import funciones.funcionesbin.*;

public class MiAppModDemo {
    public static void main(String[] args) {

        // Primero, use las funciones incorporadas como antes.
        if(FuncsMateSimples.esDivisor(2,10));
        System.out.println("2 es divisor de 10");

        System.out.println("El divisor positivo más pequeño entre 33 y 99 es: "
                            +FuncsMateSimples.divPeq(33,99));
        System.out.println("El divisor positivo más grande entre 33 y 99 es: "
                +FuncsMateSimples.divGra(33,99));

        // Ahora, use operaciones definidas por el usuario basadas en el servicio.

        // Establezca un service loader para funciones binarias.
        ServiceLoader<FuncBinProveedor> loadserv=
                ServiceLoader.load(FuncBinProveedor.class);

        FuncBinarias binarias=null;

        // Encuentre el proveedor de sumAbs y obtenga la función.
        for (FuncBinProveedor fbp:loadserv){
            if (fbp.get().getNombre().equals("sumarAbsolutos")){
                binarias=fbp.get();
                break;
            }
        }

        if (binarias !=null)
            System.out.println("El resultado de la función sumarAbsolutos es: "+binarias.func(12,-6));
        else
            System.out.println("Función sumarAbsolutos no encontrada.");

        // Ahora, encuentre el proveedor para ResAbs y obtenga la función.
        for (FuncBinProveedor fbp:loadserv){
            if (fbp.get().getNombre().equals("restarAbsolutos")){
                binarias=fbp.get();
                break;
            }
        }

        if (binarias !=null)
            System.out.println("El resultado de la función restarAbsolutos es: "+binarias.func(12,-6));
        else
            System.out.println("Función restarAbsolutos no encontrada.");


    }
}

3.5. Explicación del código

Echemos un vistazo de cerca a cómo un servicio se carga y ejecuta con el código anterior. En primer lugar, se crea un cargador de servicios (service loader) para servicios del tipo FuncBinProveedor con esta declaración:

ServiceLoader<FuncBinProveedor> loadserv=
ServiceLoader.load(FuncBinProveedor.class);

Tenga en cuenta que el parámetro de tipo para ServiceLoader es FuncBinProveedor. Este es también el tipo usado en la llamada a load(). Esto significa que se encontrarán los proveedores que implementan esta interfaz. Por lo tanto, después de que se ejecute esta instrucción, las clases FuncBinProveedor en el módulo estarán disponibles a través de loadserv. En este caso, tanto SumAbsProveedor como ResAbsProveedor estarán disponibles.

A continuación, una declaración de tipo FuncBinarias llamada binarias, se declara e inicializa en null. Se usará para referirse a una implementación que suministra un tipo específico de función binaria. A continuación, el siguiente bucle busca loadserv para uno que tenga el nombre “sumarAbsolutos”.

for (FuncBinProveedor fbp:loadserv){ 
if (fbp.get().getNombre().equals("sumarAbsolutos")){ 
binarias=fbp.get(); break; } }

Aquí, un bucle for-each itera a través de loadserv. Dentro del bucle, se verifica el nombre de la función suministrada por el proveedor. Si coincide con “sumarAbsolutos”, esa función se asigna a binarias llamando al método get() del proveedor.

Finalmente, si se encuentra la función, como será en este ejemplo, esta sentencia se ejecuta:

if (binarias !=null)
System.out.println("El resultado de la función sumarAbsolutos es: "
+binarias.func(12,-6));

En este caso, debido a que binarias se refiere a una instancia de SumAbs, la llamada a func() realiza una suma de valor absoluto. Una secuencia similar se usa para encontrar y ejecutar ResAbs.

Como MiAppModDemo ahora usa FuncBinProveedor, su archivo de definición de módulo debe incluir una declaración de usos que especifique este hecho. Recuerde que MiAppModDemo está en el módulo appinicio. Por lo tanto, debe cambiar el archivo module-info.java para appinicio como se muestra aquí:

// Definición del módulo para el módulo de aplicación principal (main).
// Ahora usa FuncBinProveedor.
module appinicio{
    // Requiere el módulo appfuncs.
    requires appfuncs;
    requires funciones;

    uses funciones.funcionesbin.FuncBinProveedor;
}

3.6. Compilar y ejecutar ejemplo de servicio basado en módulos

Una vez que haya realizado todos los pasos anteriores, puede compilar y ejecutar el ejemplo ejecutando los siguientes comandos:

javac -d appmodules --module-source-path 
appsrc appsrc\funcionesimp\module-info.java 
appsrc\appinicio\appinicio\midemoappmod\MiAppModDemo.java
java --module-path appmodules -m appinicio/appinicio.midemoappmod.MiAppModDemo
Compilar y ejecutar ejemplo de servicio basado en módulos Java
Compilación y ejecución de servicio basado en módulos (ejemplo Java)

Aquí está el resultado:

2 es divisor de 10
El divisor positivo más pequeño entre 33 y 99 es: 3
El divisor positivo más grande entre 33 y 99 es: 11
El resultado de la función sumarAbsolutos es: 18
El resultado de la función restarAbsolutos es: 6

Como muestra el resultado, las funciones binarias fueron ubicadas y ejecutadas. Es importante enfatizar que, si falta la declaración de los recursos en el módulo userfuncsimp o la declaración de usos en el módulo appstart, la aplicación fallaría.

Es importante enfatizar que si faltara la declaración provides en el módulo funcionesimp o la declaración uses en el módulo appinicio, la aplicación fallaría.

4. Características adicionales de módulo

Antes de concluir el tema sobre los módulos, hay tres características más que requieren una breve introducción. Estos son el módulo open, la declaración opens y el uso de requires static. Cada una de estas funciones está diseñada para manejar una situación especializada, y cada una constituye un aspecto bastante avanzado del sistema modular. Dicho esto, es importante que tenga una comprensión general de su propósito. A medida que adquiere más experiencia con Java, puede encontrar situaciones para las que ofrecen soluciones elegantes.

4.1. Modulo abierto

Como aprendió anteriormente en este capítulo, de manera predeterminada, los tipos en los paquetes de un módulo son accesibles solo si se exportan explícitamente a través de una declaración exports. Si bien esto es lo que generalmente deseará, puede haber circunstancias en las que resulte útil habilitar el acceso en tiempo de ejecución a todos los paquetes del módulo, independientemente de si se exporta un paquete o no.

Para permitir esto, puede crear un módulo abierto. Un módulo abierto se declara precediendo la palabra clave de module con el modificador open, como se muestra aquí:

open module nombreModulo {
// definición del módulo
}

En un módulo abierto, los tipos en todos los paquetes son accesibles en tiempo de ejecución. Comprenda, sin embargo, que solo aquellos paquetes que se exportan explícitamente están disponibles en tiempo de compilación. Por lo tanto, el modificador open afecta solo a la accesibilidad en tiempo de ejecución.

La razón principal para un módulo abierto es permitir que se acceda a los paquetes en el módulo a través de la reflexión. Reflection es la característica que permite que un programa analice el código en tiempo de ejecución.

4.2. La declaración opens

Es posible que un módulo abra un paquete específico para el acceso en tiempo de ejecución y para el acceso reflexivo de otros módulos en lugar de abrir un módulo completo. Para ello, utilice la instrucción de apertura que se muestra aquí:

opens nombrePaquete;

Aquí, nombrePaquete especifica el paquete para abrir. También es posible incluir una cláusula to, que nombra aquellos módulos para los cuales se abre el paquete.

Es importante comprender que opens no otorga acceso en tiempo de compilación. Se usa solo para abrir un paquete para el acceso en tiempo de ejecución y reflexivo. Otro punto: una declaración opens no se puede usar en un módulo abierto. Recuerde, todos los paquetes en un módulo abierto ya están abiertos.

4.3. requires static

Como ya sabe, require especifica una dependencia que, de forma predeterminada, se impone tanto durante la compilación como en el tiempo de ejecución. Sin embargo, es posible atenuar este requisito de tal forma que no se requiera un módulo en tiempo de ejecución. Esto se logra mediante el uso del modificador static en una declaración require. Por ejemplo, esto especifica que mymod es necesario para la compilación, pero no en tiempo de ejecución:

requiere mimodulo static;

En este caso, la adición de static hace que mimodulo sea opcional en tiempo de ejecución. Esto puede ser útil en una situación en la que un programa puede utilizar la funcionalidad si está presente, pero no la requiere.

Módulos en Java
  • Servicios y Proveedores de Servicios
Sending
User Review
5 (2 votes)

Sobre el Autor:

Hey hola! Yo soy Alex Walton y tengo el placer de compartir conocimientos hacía ti sobre el tema de Programación en Java, desde cero, Online y Gratis.

Deja una Respuesta

*

Este sitio usa Akismet para reducir el spam. Aprende cómo se procesan los datos de tus comentarios.