May 2007: Dependency Injection with Guice

Dependency Injection with Guice

By Paul Jensen, OCI Partner

May 2007


Introduction

In the past few years, the Java community has embraced the principles of Dependency Injection (DI). DI frameworks such as Spring are in widespread use. Recently, a new DI framework called Guice (pronounced “juice”) has drawn attention. As opposed to more comprehensive frameworks like Spring, Guice functionality is limited almost exclusively to Dependency Injection (some AOP support representing the “almost”). Guice also differs from Spring, defining wiring with Java as opposed to XML.

Guice Basics

Guice utilizes a combination of annotations and Java code to define where injection occurs and what is injected. Guice is therefore limited to use with Java 5 or later.

In order to demonstrate Guice, we will create a simple example of an order execution system comprised of several services dependent upon one another in various ways. These dependencies will be resolved by Guice (effectively "wiring" the services together). The core service is the OrderService, with an implementation below which has been "Guiced".

  1. import com.google.inject.Inject;
  2. import com.ociweb.jnb.guice.infra.LoggingService;
  3.  
  4. public interface OrderService {
  5. void submitOrder(Order order);
  6. }
  7.  
  8. public class DefaultOrderService implements OrderService {
  9. private final OrderDAO dao;
  10. private final LoggingService logger;
  11.  
  12. @Inject
  13. public DefaultOrderService(OrderDAO dao, LoggingService logger)
  14. {
  15. this.dao = dao;
  16. this.logger = logger;
  17. }
  18.  
  19. public void submitOrder(Order order) {
  20. // ...
  21. }
  22. }

Note that the only Guice-specific code in the above is the @Inject annotation. This annotation marks an injection point. Guice will attempt to reconcile the dependencies implied by the annotated constructor, method or field. Constructor-based injection is generally preferred, as it provides for one-step initialization and final fields. For method-based injection, Guice places no restriction on the name of @Inject-annotated methods (although convention dictates a setter of the form setX).

In this example, the appropriate implementations for the OrderDAO and LoggingService interfaces must be defined. Many frameworks utilize XML to express such information. In Guice, it is defined by an implementation of a Guice Module. Guice provides an AbstractModule, used here, which provides some syntactic convenience.

  1. public class ProductionServiceModule extends AbstractModule {
  2. public void configure() {
  3. bind(OrderService.class).to(DefaultOrderService.class);
  4. bind(OrderDAO.class).to(JdbcOrderDAO.class);
  5. bind(LoggingService.class).to(StandardLoggingService.class);
  6. }
  7. }

This Module simply defines the mapping of interfaces to implementations, e.g. binding injections requiring an OrderService to the DefaultOrderService implementation. Between the module and @Inject annotations, all the structure is in place for DI.

By default, a new instance is created for each injection. For this application, singleton implementations are desired. This is accomplished by adding an in(Scopes.SINGLETON) call to the Module bindings or adding an @Singleton annotation to the implementation class. Singletons are normally not created until needed. The method asEagerSingleton() forces object creation at Guice initialization. One or more eagerly initialized singletons can be used to control application initialization.

bind(LoggingService.class).to(StandardLoggingService.class).asEagerSingleton();

Various other scopes are defined by Guice, including ServletScopes.SESSION and ServletScopes.REQUEST. Custom scopes may also be created.

All that remains is to bootstrap the process:

  1. public static void main(String[] args) {
  2. Injector injector = Guice.createInjector(new ProductionModule());
  3. OrderService orderService = injector.getInstance(OrderService.class);
  4. Order order = new Order(...);
  5. orderService.submitOrder(order);
  6. }

The Injector performs the dependency injection as defined in one or more modules (createInjector is a varargs method). It also acts as a repository for all injected objects. However looking up services in the Injector should be used sparingly, as this usage pattern (the Service Locator pattern) is precisely a usage that Dependency Injection attempts to avoid.

Qualifying Injection with Annotations

The bindings so far have been strictly one interface to one implementation. If more implementations exist, they must be differentiated for injection.

As one potential solution, Guice offers a static annotation solution, parameterizing each potential injection site. Users may define custom annotations to differentiate bindings to the same type. Related bindings include an annotatedWith([AnnotationClass].class). Injections may be annotated as in the @Default annotation below:

public DefaultOrderService(@Default OrderDAO dao, LoggingService logger)

Annotations may be differentiated by attributes. Guice offers the @Named annotation which can define string identifiers. While not as typesafe as an unparameterized annotation, this approach is somewhat simpler to implement, not requiring definition of a new annotation type.

Annotations are necessary to support injection of primitive values. In the example below, the connection string for a DAO is configured in its constructor. Presumably, identical syntax would be used for the remaining DAOs.

  1. @Inject
  2. public JdbcOrderDAO(
  3. @Named("connectionString") String connectionString)
  4. {
  5. System.out.println("ConnectionString = " + connectionString);
  6. }

The associated binding in the Module uses the Names class to create an instance of the Named annotation and associates the String value:

bindConstant().annotatedWith(Names.named("connectionString"))
 .to("jdbc:msql://localhost:1114/contact_mgr");

Providers

Often an application requires instances of multiple implementations of a given interface. For example, the OrderService may have need to submit orders to a different intermediary based on its final destination. More specifically, a Counterparty can represent a set of unique exchanges to which the order is destined. The OrderService will need to route the order request to appropriate Counterparty based on the destination exchange.

These requirements are not amenable to the injection approaches shown so far. One approach would be to utilize the Guice Provider<> interface, which acts as a factory for a particular type. Typically a Provider will be parameterized for a single class and return an instance from its get() method. Providers can be used for lazy initialization or as a factory for multiple objects. Providers are also useful for integrating with third-party components (Guice uses them for JNDI and Spring integration) or to perform additional activities beyond initial object creation (e.g. registration, post construction initialization).

For this particular case, the Provider will return a list. Note that Providers are "in the club" (to use the Guice creator's terminology) and can participate in Dependency Injection.

bind(new TypeLiteral<List<Counterparty>>(){})
 .toProvider(CounterpartyProvider.class);

Note the use of Guice's TypeLiteral class to preserve the type parameters at runtime (see Super Type Tokens).

  1. @Singleton
  2. public class CounterpartyProvider implements Provider<List<Counterparty>> {
  3. private final List<Counterparty> list = new ArrayList<Counterparty>();
  4.  
  5. @Inject
  6. public CounterpartyProvider(FixCounterparty fixCounterParty, CustomCounterparty customCounterParty)
  7. {
  8. list.add(fixCounterParty);
  9. list.add(customCounterParty);
  10. }
  11.  
  12. public List<Counterparty> get() {
  13. return list;
  14. }
  15. }

Which is injected into the OrderService here:

  1. @Inject
  2. @Log
  3. public void setCounterparties(Provider<List<Counterparty>> counterparties) {
  4. this.counterparties.addAll(counterparties.get());
  5. logger.write("Added " + this.counterparties.size() + " counterparties.");
  6. }

Interception

In the previous section, the setCounterparties() method was annotated with @Log. This is a custom annotation created to demonstrate the AOP capabilities of Juice. The Module may define Interceptors and their join points.

  1. import org.aopalliance.intercept.MethodInterceptor;
  2.  
  3. public class LoggingInterceptor implements MethodInterceptor {
  4. ...
  5. public Object invoke(MethodInvocation arg0) throws Throwable {
  6. loggingService.write("Invoking " + arg0.getMethod());
  7. return arg0.proceed();
  8. }
  9. }

The bindInterceptor() method in the Module defines where to apply intercepters:

  1. void bindInterceptor(Matcher<? super Class<?>> classMatcher,
  2. Matcher<? super Method> methodMatcher,
  3. MethodInterceptor... interceptors)

in this case applying the LoggingInterceptor to methods with the Log annotation in all classes:

bindInterceptor(Matchers.any(), Matchers.annotatedWith(Log.class), new LoggingInterceptor());

Matches can be based on a wide variety of criteria including annotations, class type, method return type, and containing package.

Impressions

When initially considering Guice as a topic of this article, I approached investigating "Yet another Dependency Injection framework" with some trepidation. I am a proponent of DI and I have enjoyed using Spring on several projects. Spring configuration was manageable for me, particularly using autowiring to reduce some of their complexity.

The URL references below contain opinions from a variety of individuals on Guice, typically comparing it to Spring's DI capabilities. As comparisons are unavoidable...

Summary

Guice offers a new approach for Dependency Injection while fully taking advantage of Java 5 annotations and generics. Some aspects of Guice have been incorporated in other frameworks (e.g. Guice is utilized in Struts 2 and Tapestry - see link below). Guice has attracted much notice in the Java community and presents an interesting alternative to other DI frameworks.

References

 

The Software Engineering Tech Trends is a monthly publication featuring emerging trends in software engineering.

Subscribe

© Copyright Object Computing, Inc. 1993, 2018. All rights reserved

secret