Guía de Google Guice

1. Introducción

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

También compararemos y contrastaremos el enfoque de Guice con los de marcos de DI más establecidos como Spring y Contexts and Dependency Injection (CDI).

Este artículo asume que el lector comprende los fundamentos del patrón de inyección de dependencia.

2. Configuración

Para usar Google Guice en su proyecto Maven, deberá agregar la siguiente dependencia a su pom.xml :

 com.google.inject guice 4.1.0  

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 al proporcionar integración a marcos Java más establecidos).

3. Inyección de dependencia básica con Guice

3.1. Nuestra aplicación de muestra

Trabajaremos con un escenario en el que diseñamos clases que admitan tres medios de comunicación en un negocio de helpdesk: correo electrónico, SMS e IM.

Considere 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 de 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 comunicador que usamos para hacer la transmisión real del mensaje.

El punto de entrada básico a Guice es el inyector:

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 de comunicación . 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 enlaces (o cableado, como se lo conoce en Spring).

Guice ha adoptado un enfoque de código primero para la inyección y administración de dependencias, por lo que no tendrá que lidiar con una gran cantidad de XML listo para usar.

En el ejemplo anterior, el árbol de dependencias de Comunicación se inyectará implícitamente utilizando una función llamada enlace justo a tiempo , siempre que las clases tengan el constructor predeterminado sin argumentos. Esta ha sido una característica de Guice desde el inicio y solo está disponible en Spring desde v4.3.

3.2. Fijaciones Guice

La unión es para Guice como el cableado es para Spring. Con los enlaces, define cómo Guice va a inyectar dependencias en una clase.

Un enlace 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); } }

La implementación de este módulo especifica que se inyectará una instancia de Default CommunicatorImpl dondequiera que se encuentre una variable de Communicator .

Otra encarnación de este mecanismo es la unión nombrada . Considere la siguiente declaración de variable:

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

Para ello, tendremos la siguiente definición vinculante:

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

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

Notará 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. * ; Debe tener cuidado de importar desde el paquete correcto cuando utilice un IDE.

Sugerencia: Aunque acabamos de decir que usemos @Inject y @Named proporcionados 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 puede inyectar una dependencia que no tenga un constructor sin argumentos predeterminado mediante el enlace de 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 de arriba inyectará una instancia de Comunicación usando el constructor que toma un argumento booleano . Proporcionamos el verdadero argumento al constructor definiendo un enlace no dirigido de la clase booleana .

Este enlace no dirigido se proporcionará con entusiasmo a cualquier constructor en el enlace que acepte un parámetro booleano . Con este enfoque, se inyectan todas las dependencias de la Comunicación .

Otro enfoque para el enlace específico del constructor es el enlace de instancia , donde proporcionamos una instancia directamente en el enlace:

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

Este enlace proporcionará una instancia de la clase de comunicación siempre que se declare una variable de comunicación .

En este caso, sin embargo, el árbol de dependencias de la clase no se conectará automáticamente. Debe limitar el uso de este modo cuando no sea necesaria una gran inicialización o inyección de dependencia.

4. Tipos de inyección de dependencia

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

4.1. Inyección de campo

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

Utilice la anotación @Named opcional como calificador para implementar la inyección dirigida según el nombre

4.2. Método de inyección

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

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

4.3. Inyección de constructor

También puede inyectar dependencias usando un constructor:

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

4.4. Inyecciones implícitas

Guice will implicitly inject some general purpose components like the Injector and an instance of java.util.Logger, among others. You'll notice we are using loggers all through the samples but you won't find an actual binding for them.

5. Scoping in Guice

Guice supports the scopes and scoping mechanisms we have grown used to in other DI frameworks. Guice defaults to providing a new instance of a defined dependency.

5.1. Singleton

Let's inject a singleton into our application:

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

The in(Scopes.SINGLETON) specifies that any Communicator field with the @Named(“AnotherCommunicator”) will get a singleton injected. This singleton is lazily initiated by default.

5.2. Eager Singleton

Now, let's inject an eager singleton:

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

The asEagerSingleton() call defines the singleton as eagerly instantiated.

In addition to these two scopes, Guice supports custom scopes as well as the web-only @RequestScoped and @SessionScoped annotations, supplied by Jakarta EE (there are no Guice-supplied versions of those annotations).

6. Aspect Oriented Programming in Guice

Guice is compliant with the AOPAlliance's specifications for aspect-oriented programming. We can implement the quintessential logging interceptor, which we will use to track message sending in our example, in only four steps.

Step 1 – Implement the AOPAlliance's MethodInterceptor:

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(); } } 

Step 2 – Define a Plain Java Annotation:

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

Step 3 – Define a Binding for a Matcher:

Matcher is a Guice class that we use do specify the components that our AOP annotation will apply to. In this case, we want the annotation to apply to implementations of CommunicationMode:

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

We have specified a Matcher here that will apply our MessageLogger interceptor to any class, that has the MessageSentLoggable annotation applied to its methods.

Step 4 – Apply Our Annotation to Our Communicationmode and Load Our Module

@Override @MessageSentLoggable public 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); }

7. Conclusion

Having looked at basic Guice functionality, we can see where the inspiration for Guice came from Spring.

Junto con su soporte para JSR-330, Guice apunta a ser un marco DI centrado en la inyección (mientras que Spring proporciona un ecosistema completo para la conveniencia de programación, no necesariamente solo DI), dirigido a desarrolladores que desean flexibilidad DI.

Guice también es altamente extensible, lo que permite a los programadores escribir complementos portátiles que dan como resultado usos flexibles y creativos del marco. Esto se suma a la amplia integración que Guice ya proporciona para los marcos y plataformas más populares como Servlets, JSF, JPA y OSGi, por nombrar algunos.

Puede encontrar todo el código fuente utilizado en este tutorial en nuestro proyecto GitHub.