helloopensource

 

guice

Page history last edited by James Staggs 2 yrs ago

Hello Guice

 

 

After stepping through this tutorial you will know how to start using Google Guice (pronounced juice) to handle dependency injection in your Java applications.

 

 

Versions Used:

Guice 1.0

Eclipse 3.3.0

Java Runtime Environment 1.5.0_12

JUnit 3.8.2

 

Assumptions:

JDK is properly installed

The Java perspective for Eclipse is properly installed

 

Setup:

  1. Download guice-1.0.zip from http://code.google.com/p/google-guice/
  2. Create a new Java project in Eclipse named HelloGuice

  3. Extract guice-1.0.jar from the zip file into the HelloGuice project folder

  4. Add Guice to the build path for the HelloGuice project by selecting the project name and pressing Alt+Enter

  5. Use the Add JARs button to select the Guice jar archive

     

  6. Use the Add Library button to add JUnit to the build path

     

     

     

  7. After pressing OK your build path should include Guice and JUnit

     

     

     

 

Benefits:

 

Google describes Guice as “a lightweight dependency injection framework for Java 5 and above”. Dependency injection is a technique for decoupling objects from each other by moving the responsibility for instantiating objects to a 3rd party, which then injects these instances into the objects that depend on them. This can be accomplished in several ways, but the Guice framework was designed to be lightweight so that even in large projects with numerous dependencies, the components can be wired together in a clear way without writing a lot of tedious code.

 

Guice will also make it easier to unit test, which leads to improved maintainability.

 

 

Example:

 

Suppose we want to have a Greeting class that depends on a Translator class because it has a method that will issue a greeting in many languages. The next steps will demonstrate the code for this situation using Guice to handle the dependency injection.

 

First, create a new JUnit test class in Eclipse and write the test cases for the desired functionality as follows: 

 

import junit.framework.TestCase;

/**

 * Job: Ensure that Greeting objects return the correct salutations

 */

public class GreetingTest extends TestCase {

    public void testGreetingReturnsExpectedSalutationManually() throws Exception {

        Greeting greeting = new Greeting(new Translator());

        assertEquals("Hello Chandan", greeting.sayHello("English", "Chandan"));

        assertEquals("Bonjour Diana", greeting.sayHello("French", "Diana"));

        assertEquals("Namaste Jimmy", greeting.sayHello("Hindi", "Jimmy"));

    }

}

 

 

The first test above shows a design decision to use constructor injection by passing a concrete implementation of the ITranslator interface to the constructor of the Greeting class. The second test shows the sayGreeting method being called with two parameters - a name and a language. The method should return a salutation in the requested language for the requested name. Neither of these tests make use of Guice, so they need to know which implementation of Translator to use when Greeting is instantiated.

 

 

Next is the interface for translators, which allows us to use dependency injection since Greeting needs to know only about this interface:

 

/**

 * Job: Define a contract for using translators so that

 *      dependency injection can be used

 */

public interface ITranslator {

    String translate(String from, String to, String word);

}

 

 

We also have one implementation, although a mock could easily be used instead for testing purposes:

 

/**

 * Job: Convert a word from one language to another

 *         - currently only implemented to convert 'Hello' from English

 */

public class Translator implements ITranslator {

    public String translate(String fromLanguage, String toLanguage, String word) {

        String translation = word;

        if (fromLanguage.equals("English") && word.equals("Hello")) {

            if (toLanguage.equals("Hindi"))

                translation = "Namaste";

            if (toLanguage.equals("French"))

                translation = "Bonjour";

            if (toLanguage.equals("Italian"))

                translation = "Ciao";

        }

        return translation;

    }

}

 

 

Instead of giving the code that will call Greeting methods the responsibility of instantiating the translator dependency, we can have Guice handle this in a module: 

 

import com.google.inject.Binder;

import com.google.inject.Module;

import com.google.inject.Scopes;

/**

 * Job: Map concrete implementations to interfaces for dependency injection

 */

public class GreetingModule implements Module {

    public void configure(Binder binder) {

        binder.bind(ITranslator.class).to(Translator.class).in(Scopes.SINGLETON);

    }

}

 

 

Modules are how Guice is configured. This is accomplished by implementing Module, which allows Guice to pass a binder to the configure method that will take care of wiring up the dependencies for the application. Usually this means binding an implementation to an interface just as we have done above.  Finally, in the example above we have specified singleton scope so that Guice will inject only one instance.

 

Now we will look at the Greeting class itself:

 

import org.helloopensource.greeting.ITranslator

import com.google.inject.Inject;

/**

 * Job: Return a multi-language greeting by making use of a translator

 */

public class Greeting {

    private ITranslator translator;

 

    @Inject

    public Greeting(ITranslator translator) {

        this.translator = translator;

    }

 

    public String sayHello(String language, String name) {

        return translator.translate("English", language, "Hello") + " " + name;

    }

}

 

Take note of the @Inject annotation above the constructor. This is how we let Guice know where to inject the dependency.

 

Now we can use Guice in our unit tests as follows:

 

package org.helloopensource.guice;

 

import junit.framework.TestCase;

import com.google.inject.Guice;

import com.google.inject.Injector;

 

/**

 * Job: Ensure that Greeting objects return the correct salutations

 */

public class GreetingTest extends TestCase {

 

      public void testGreetingReturnsExpectedSalutationManually() throws Exception {

        Greeting greeting = new Greeting(new Translator());

        assertEquals("Hello Chandan", greeting.sayHello("English", "Chandan"));

        assertEquals("Bonjour Diana", greeting.sayHello("French", "Diana"));

        assertEquals("Namaste Jimmy", greeting.sayHello("Hindi", "Jimmy"));

    }

 

    public void testGreetingReturnsExpectedSalutationWithGuice() throws Exception {

        Injector injector = Guice.createInjector(new GreetingModule());

        Greeting greeting = injector.getInstance(Greeting.class);

        assertEquals("Hello Chandan", greeting.sayHello("English", "Chandan"));

        assertEquals("Bonjour Diana", greeting.sayHello("French", "Diana"));

        assertEquals("Namaste Jimmy", greeting.sayHello("Hindi", "Jimmy"));

    }

}

 

Now we can test the Greeting class without specifying which implementation of the translator dependency to use.  Just pass an instance of our GreetingModule to Guice's createInjector method to create an injector. This injector will now return an instance of Greeting which we can use just as before.

 

Take note that the main difference between the tests above is how the Greeting object is instantiated. In the first test, we are required to create a Translator in order to create the Greeting object, but in the second test there is no mention of a Translator because Guice is now responsible for instantiating objects with their dependencies.

 

Summary:

 

This simple example is only intended to demonstrate some of the mechanics of using Guice, but you probably shouldn't use it unless you have a more complicated chain of dependencies. Guice can be very useful for separating the logic for instantiating objects from the objects themselves, which leads toward both higher cohesion and looser coupling. Guice will also fail at startup if there is a dependency missing, making it easy to catch your own mistakes quickly.

Comments (0)

You don't have permission to comment on this page.