Guía de Google Guice

Introducción

Este artículo examinará los fundamentos de Google Guice. Veremos los enfoques para completar las tareas básicas de Inyección de Dependencia (DI) en Guice.

También compararemos y contrastaremos el enfoque de Guice con los de frameworks DI más establecidos como Spring y Contextos e Inyección de Dependencia (CDI).

Este artículo supone que el lector tiene una comprensión de los fundamentos del patrón de Inyección de Dependencia.

Configuración

Para utilizar Google Guice en tu proyecto Maven, necesitarás añadir la siguiente dependencia a tu pom.xml:

<dependency> <groupId>com.google.inject</groupId> <artifactId>guice</artifactId> <version>4.1.0</version></dependency>

También hay una colección de extensiones de Guice (las cubriremos un poco más adelante) aquí, así como módulos de terceros para extender las capacidades de Guice (principalmente proporcionando integración a frameworks Java más establecidos).

Inyección de Dependencias Básica con Guice

3.1. Nuestra aplicación de ejemplo

Trabajaremos con un escenario en el que diseñamos clases que soportan tres medios de comunicación en un negocio de helpdesk: Correo electrónico, SMS y mensajería instantánea.

Considera la clase:

public class Communication { @Inject private Logger logger; @Inject private Communicator communicator; public Communication(Boolean keepRecords) { if (keepRecords) { System.out.println("Message logging enabled"); } } public boolean sendMessage(String message) { return communicator.sendMessage(message); }}

Esta clase Comunicación es la unidad básica de comunicación. Una instancia de esta clase se utiliza para enviar mensajes a través de los canales de comunicación disponibles. Como se muestra arriba, Communication tiene un Communicator que usamos para hacer la transmisión real de los mensajes.

El punto de entrada básico en Guice es el Injector:

public static void main(String args){ Injector injector = Guice.createInjector(new BasicModule()); Communication comms = injector.getInstance(Communication.class);}

Este método principal recupera una instancia de nuestra clase Communication. También introduce un concepto fundamental de Guice: el Módulo (usando BasicModule en este ejemplo). El Módulo es la unidad básica de definición de bindings (o wiring, como se conoce en Spring).

Guice ha adoptado un enfoque de código primero para la inyección y gestión de dependencias, por lo que no tendrás que lidiar con un montón de XML out-of-the-box.

En el ejemplo anterior, el árbol de dependencias de Communication se inyectará implícitamente utilizando una característica llamada just-in-time binding, siempre que las clases tengan el constructor sin carga por defecto. Esta ha sido una característica de Guice desde su inicio y sólo está disponible en Spring desde la v4.3.

3.2. Guice Bindings

Los bindings son a Guice lo que el wiring es a Spring. Con los bindings, se define cómo Guice va a inyectar dependencias en una clase.

Un binding se define en una implementación de com.google.inject.AbstractModule:

public class BasicModule extends AbstractModule { @Override protected void configure() { bind(Communicator.class).to(DefaultCommunicatorImpl.class); }}

Esta implementación del módulo especifica que una instancia de DefaultCommunicatorImpl debe ser inyectada siempre que se encuentre una variable Communicator.

Otra encarnación de este mecanismo es el named binding. Considera la siguiente declaración de variable:

@Inject @Named("DefaultCommunicator")Communicator communicator;

Para ello, tendremos la siguiente definición de binding:

@Overrideprotected void configure() { bind(Communicator.class) .annotatedWith(Names.named("DefaultCommunicator")) .to(DefaultCommunicatorImpl.class);}

Este binding proporcionará una instancia de Communicator a una variable anotada con la anotación @Named(«DefaultCommunicator»).

Verás que las anotaciones @Inject y @Named parecen ser anotaciones de préstamo del CDI de Jakarta EE, y lo son. Están en el paquete com.google.inject.* – debes tener cuidado de importar desde el paquete correcto cuando uses un IDE.

Consejo: Aunque acabamos de decir que hay que usar las anotaciones @Inject y @Named proporcionadas por Guice, vale la pena señalar que Guice proporciona soporte para javax.inject.Inject y javax.inject.Named, entre otras anotaciones de Jakarta EE.

También puedes inyectar una dependencia que no tenga un constructor sin carga por defecto utilizando la vinculación del constructor:

public class BasicModule extends AbstractModule { @Override protected void configure() { bind(Boolean.class).toInstance(true); bind(Communication.class).toConstructor( Communication.class.getConstructor(Boolean.TYPE));}

El fragmento anterior inyectará una instancia de Communication utilizando el constructor que toma un argumento booleano. Suministramos el argumento verdadero al constructor definiendo una vinculación no dirigida de la clase Boolean.

Esta vinculación no dirigida será suministrada ansiosamente a cualquier constructor de la vinculación que acepte un parámetro booleano. Con este enfoque, se inyectan todas las dependencias de Communication.

Otro enfoque de la vinculación específica del constructor es la vinculación de instancia, en la que proporcionamos una instancia directamente en la vinculación:

public class BasicModule extends AbstractModule { @Override protected void configure() { bind(Communication.class) .toInstance(new Communication(true)); } }

Esta vinculación proporcionará una instancia de la clase Communication siempre que se declare una variable Communication.

En este caso, sin embargo, el árbol de dependencias de la clase no se cableará automáticamente. Deberías limitar el uso de este modo donde no haya ninguna inicialización pesada o inyección de dependencia necesaria.

Tipos de inyección de dependencia

Guice soporta los tipos estándar de inyecciones que habrías llegado a esperar con el patrón DI. En la clase Communicator, necesitamos inyectar diferentes tipos de CommunicationMode.

4.1. Inyección de campos

@Inject @Named("SMSComms")CommunicationMode smsComms;

Utiliza la anotación opcional @Named como calificador para implementar la inyección dirigida basada en el nombre

4.2. Inyección de métodos

Aquí utilizamos un método setter para lograr la inyección:

@Injectpublic void setEmailCommunicator(@Named("EmailComms") CommunicationMode emailComms) { this.emailComms = emailComms;}

4.3. Inyección en el constructor

También puedes inyectar dependencias utilizando un constructor:

@Injectpublic Communication(@Named("IMComms") CommunicationMode imComms) { this.imComms= imComms;}

4.4. Inyecciones implícitas

Guice inyectará implícitamente algunos componentes de propósito general como el Inyector y una instancia de java.util.Logger, entre otros. Verás que utilizamos loggers en todos los ejemplos, pero no encontrarás una vinculación real para ellos.

Scoping en Guice

Guice soporta los ámbitos y mecanismos de scoping a los que nos hemos acostumbrado en otros frameworks de DI. Guice proporciona por defecto una nueva instancia de una dependencia definida.

5.1. Singleton

Inyectemos un singleton en nuestra aplicación:

bind(Communicator.class).annotatedWith(Names.named("AnotherCommunicator")) .to(Communicator.class).in(Scopes.SINGLETON);

El in(Scopes.SINGLETON) especifica que cualquier campo Communicator con el @Named(«AnotherCommunicator») obtendrá un singleton inyectado. Este singleton se inicia de forma perezosa por defecto.

5.2. Singleton ansioso

Ahora, vamos a inyectar un singleton ansioso:

bind(Communicator.class).annotatedWith(Names.named("AnotherCommunicator")) .to(Communicator.class) .asEagerSingleton();

La llamada asEagerSingleton() define el singleton como instanciado ansiosamente.

Además de estos dos ámbitos, Guice soporta ámbitos personalizados, así como las anotaciones @RequestScoped y @SessionScoped, suministradas por Jakarta EE (no hay versiones de estas anotaciones suministradas por Guice).

Programación orientada a aspectos en Guice

Guice cumple con las especificaciones de la AOPAlliance para la programación orientada a aspectos. Podemos implementar el interceptor de registro por excelencia, que usaremos para seguir el envío de mensajes en nuestro ejemplo, en sólo cuatro pasos.

Paso 1 – Implementar el MethodInterceptor de la AOPAlliance:

public class MessageLogger implements MethodInterceptor { @Inject Logger logger; @Override public Object invoke(MethodInvocation invocation) throws Throwable { Object objectArray = invocation.getArguments(); for (Object object : objectArray) { logger.info("Sending message: " + object.toString()); } return invocation.proceed(); }}

Paso 2 – Definir una Anotación Java Simple:

@Retention(RetentionPolicy.RUNTIME)@Target(ElementType.METHOD)public @interface MessageSentLoggable {}

Paso 3 – Definir un Binding para un Matcher:

El Matcher es una clase Guice que utilizamos para especificar los componentes a los que se aplicará nuestra anotación AOP. En este caso, queremos que la anotación se aplique a las implementaciones de CommunicationMode:

public class AOPModule extends AbstractModule { @Override protected void configure() { bindInterceptor( Matchers.any(), Matchers.annotatedWith(MessageSentLoggable.class), new MessageLogger() ); }}

Aquí hemos especificado un Matcher que aplicará nuestro interceptor MessageLogger a cualquier clase, que tenga la anotación MessageSentLoggable aplicada a sus métodos.

Paso 4 – Aplicar nuestra anotación a nuestro Communicationmode y cargar nuestro módulo

@Override@MessageSentLoggablepublic boolean sendMessage(String message) { logger.info("SMS message sent"); return true;}public static void main(String args) { Injector injector = Guice.createInjector(new BasicModule(), new AOPModule()); Communication comms = injector.getInstance(Communication.class);}

Conclusión

Habiendo visto la funcionalidad básica de Guice, podemos ver de dónde vino la inspiración para Guice de Spring.

Junto con su soporte para JSR-330, Guice pretende ser un framework DI centrado en la inyección (mientras que Spring proporciona todo un ecosistema para la comodidad de la programación, no necesariamente sólo DI), dirigido a los desarrolladores que quieren flexibilidad DI.

Guice también es altamente extensible, permitiendo a los programadores escribir plugins portables que dan lugar a usos flexibles y creativos del framework. Esto se suma a la amplia integración que Guice ya proporciona para la mayoría de los marcos y plataformas populares como Servlets, JSF, JPA y OSGi, por nombrar algunos.

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *