Una sesión práctica con Google Guice

Hace unos meses, escribí un artículo explicando la inyección de dependencia. Había mencionado un artículo de seguimiento con una sesión práctica de Google Guice. Si bien estoy decepcionado por haber escrito esto tan tarde, una parte de mí está feliz de haber podido escribir un segundo artículo.

Este artículo asume que está familiarizado con lo que es la inyección de dependencia. Le recomendaría que lea mi artículo anterior, ya que nos basaremos en los ejemplos que usamos allí. Si está escuchando el término por primera vez, valdrá la pena. Si está familiarizado con él, leerlo no le llevará mucho tiempo :)

Si no ha trabajado mucho con Guice, compruébelo en GitHub aquí.

Tendremos que configurar algunas cosas antes de comenzar

  1. JDK : Usaremos Java para esta tarea. Por lo tanto, necesitará tener un JDK que funcione para poder ejecutar código Java en su computadora. Para comprobar si ya está instalado, ejecute 'java -version' en la línea de comando. Si la versión es 1.6 o superior, estamos bien. Solo una nota: no creo que tenga mucho sentido intentar esto si no tienes experiencia con Java .
  2. Maven : Usaremos maven como herramienta de construcción. Para instalar maven, siga las instrucciones aquí //maven.apache.org/install.html (bastante fácil). Para comprobar si ya tiene maven, ejecute 'mvn -v' en la línea de comando.
  3. git (opcional): //www.linode.com/docs/development/version-control/how-to-install-git-on-linux-mac-and-windows/
  4. clonar las manos en el repositorio (FreshGuice) : ejecutar los comandos mencionados a continuación
cd folder/to/clone-into/ git clone //github.com/sankalpbhatia/FreshGuice.git

Enlaces y anotaciones de enlace

Estamos listos ahora. Permítanme comenzar presentando dos términos cruciales en el marco de Guice: enlaces y anotaciones de enlace.

Vinculaciones: siendo el concepto central de Guice, en términos literales, significa un acuerdo o promesa que implica una obligación que no se puede romper. Ahora mapeémoslo en el contexto de la inyección de dependencia. Cuando hacemos que Guice vincule una instancia con una clase, hacemos un acuerdo con Guice en el sentido de que "Cuando solicite una instancia de X.java, dame esta instancia". Y este acuerdo no se puede romper.

Anotaciones de enlace: en ocasiones, querrá varios enlaces para el mismo tipo. La anotación y el tipo (clase) juntos identifican de forma única un enlace. Por ejemplo, en algunos casos, es posible que necesite dos instancias separadas de la misma clase / implementación de la misma interfaz. Para identificarlos, utilizamos anotaciones vinculantes. Veremos algunos ejemplos cuando expliquemos los enlaces.

Cómo crear enlaces

La sección de guía del usuario de Guice lo explica a la perfección. Así que lo copiaré aquí:

Para crear enlaces, extienda AbstractModuley anule su configuremétodo. En el cuerpo del método, llame bind()para especificar cada enlace. Estos métodos se verifican por tipos para que el compilador pueda informar errores si utiliza los tipos incorrectos. Una vez que haya creado sus módulos, páselos como argumentos Guice.createInjector()para construir un inyector.

Hay varios tipos de enlaces: enlazado, instancia, anotación @Provides, enlaces de proveedor, enlaces de constructor y enlaces no segmentados.

Para este artículo, solo cubriré enlaces enlazados, enlaces de instancia, anotación @Provides y una anotación especial @Inject. Rara vez utilizo otros medios para vincular, pero se puede encontrar más información en //github.com/google/guice/wiki/Bindings.

  1. Enlace vinculado: un enlace vinculado asigna un tipo / interfaz a su implementación. Este ejemplo asigna la interfaz MessageService a su implementación EmailService.

En términos sencillos: cuando le pido a Guice que me dé una instancia de MessageService, me dará una instancia de EmailService.

Pero, ¿cómo sabrá crear una instancia de EmailService ? Eso lo veremos más tarde.

public class MessagingModule extends AbstractModule { @Override protected void configure() { bind(MessageService.class).to(EmailService.class); }}

Quizás queremos más de una instancia de MessageService en nuestro proyecto. En algunos lugares, querríamos que un SMSService se asocie con un MessageService, en lugar de un EmailService. En tales casos, usamos una anotación vinculante. Para crear una anotación vinculante, tendrá que crear dos anotaciones así:

@BindingAnnotation @Target({ FIELD, PARAMETER, METHOD }) @Retention(RUNTIME)public @interface Email {}
@BindingAnnotation @Target({ FIELD, PARAMETER, METHOD }) @Retention(RUNTIME)public @interface SMS {}

No es necesario que conozca las anotaciones de metadatos (@Target, @ Retention). Si está interesado, lea esto: //github.com/google/guice/wiki/BindingAnnotations

Una vez que tengamos las anotaciones con nosotros, podemos crear dos enlaces separados que indiquen a Guice que cree diferentes instancias de MessageService en función de BindingAnnotation (lo considero un calificador).

public class MessagingModule extends AbstractModule { @Override protected void configure() { bind(MessageService.class).annotatedWith(Email.class) .to(EmailService.class);
 bind(MessageService.class).annotatedWith(SMS.class) .to(SMSService.class); }}

2. Enlace de instancia: enlaza un tipo a una instancia específica

 bind(Integer.class) .annotatedWith(Names.named(“login timeout seconds”)) .toInstance(10);

Uno debe evitar usar .toInstance con objetos que son complicados de crear, ya que puede ralentizar el inicio de la aplicación. En su lugar, puede utilizar un método @Provides. De hecho, incluso puede olvidar que mencionamos algo sobre el enlace de instancias en este momento.

3. @ Proporciona anotación :

Esto es directamente de la wiki de Guice, ya que es bastante simple:

Cuando necesite código para crear un objeto, use un @Providesmétodo. El método debe estar definido dentro de un módulo y debe tener una @Providesanotación. El tipo de retorno del método es el tipo vinculado. Siempre que el inyector necesite una instancia de ese tipo, invocará el método.
bind(MessageService.class)
.annotatedWith(Email.class)
.to(EmailService.class);

es lo mismo que

@[email protected] MessageService provideMessageService() { return new EmailService();}

donde Email.java es una anotación vinculante.

Las dependencias se pueden pasar a un método con esta anotación, lo que lo hace extremadamente útil en proyectos de la vida real. Por ejemplo, para el código mencionado a continuación, el inyector ejercerá el enlace para el parámetro de cadena apiKey antes de invocar el método.

@Provides @PayPalCreditCardProcessor providePayPalCreditCardProcessor( @Named("PayPal API key") String apiKey) { PayPalCCProcessor processor = new PaypalCCProcessor(); processor.setApiKey(apiKey); return processor; }

4. @ Inject annotation (Just in Time binding): Whatever we covered up until now are called explicit bindings. If Guice, when trying to create an instance, does not find an explicit binding, it tries to create one using a Just-in-time binding.

Guice can create these bindings by using the class’s injectable constructor. This is either a non-private, no-arguments constructor or a constructor with the @Injectannotation.

Task

Now let’s move to the project we cloned from Github.

Like the examples in the previous article, this maven project implements a BillingService which charges a PizzaOrder using a credit card and generates a Receipt.

The project structure is as follows:

Interfaces

  • BillingService — charges an order using a credit card
  • CreditCardProcessor — debits some amount from a credit card
  • TransactionLog — logs results

Classes

src

  • CreditCard — entity representing a Credit Card
  • PizzaOrder — entity representing a Pizza order
  • Receipt — entity representing a receipt
  • RealBillingService implements BillingService
  • PaypalCreditCardProcessor implements CreditCardProcessor
  • BankCreditCardProcessor implements CreditCardProcessor
  • InMemoryTransactionLog implements TransactionLog
  • GuiceTest — Main class which uses BillingService
  • BillingModule — All Guice bindings go here
  • GuiceInjectionTest : Unit tests to check binding constraints

The task here is to create Guice Bindings in the BillingModule such that the following constraints are satisfied:

  1. All implementations of BillingService should be bound to RealBillingService.
  2. CreditCardProcessor interface annotated with @Paypal should be bound to PaypalCreditCardProcessor class.
  3. CreditCardProcessor interface named with string “Bank” should be bound to BankCreditCardProcessor class.
  4. BillingService instances returned by injector should have an instance of BankCreditCardProcessor as their dependency.
  5. All implementations of TransactionLog should be bound to InMemoryTransactionLog.

All five unit tests in GuiceInjectionTests should pass if the above conditions are satisfied. You should also be able to run the main method in GuiceTest.

To test correctness:

  1. run unit tests
mvn test

This should run the test file GuiceInjectionTests.java.

2. run the main file

mvn exec:java -Dexec.mainClass="GuiceTest"

This should execute the main class of the project, which does the end to end work of creating an order, processes payment using a credit card and generates a receipt.

You can comment if you have any questions and I will try answering them. Please note that there is no single correct answer for this exercise. DM me your solutions and I will add the answers to the repository. Or better still, send me a pull request :)