Dependency Injection

Goal

Concepts

Library

Dependencies

Lesson

To say that a project has a dependency simply means that it depends on something else in order to work. So far we've mostly been talking about library dependencies; in fact this very lesson indicates a dependency on a third-party library that needs to be included in your Maven POM. But we can also talk about the dependencies of a particular class implementation. To implement one interface, a class may need a reference to the implementation of another interface to delegate some of its responsibilities.

Think back to the FarmService interface from the lesson on business logic. In addition to feeding the animals, the farm needs to collect eggs from the hens.

Adding a service for collecting eggs to farm services.
public interface FarmService {

  void feedAnimals() throws IOException;

  void collectEggs();

}
Egg-gathering machine.
Collecting eggs using a complicated contraption.

In order to collect eggs, the implementation class FarmServiceImpl will need access to an implementation of the EggCollector interface.

Farm service implementation dependency on an EggCollector.
public class FarmServiceImpl implements FarmService {

  private final EggCollector eggCollector = new ByHandEggCollector();

  …

  @Override
  public void collectEggs() {
    eggCollector.collect();
  }

}

FarmServiceImpl thus has a dependency on EggCollector, and here the decision has been made to use a ByHandEggCollector, which goes around to the hen houses, lifts up the chickens, and collects the eggs in a pail. But there also exists a ContraptionEggCollector, which uses some combination of cranes, ropes, and pulleys to gather the eggs each day—perhaps something like the figure on the side.

On day we may want one day to change egg collection approach, but this is made difficult by having the ByHandEggCollector implementation hard-coded into FarmServiceImpl. If someone were to use our library without access to the source code, they would lose the flexibility to change the egg-gathering implementation. Perhaps most importantly, having this dependency directly referenced in the code makes it hard for us to test FarmServiceImpl as a single unit, especially if its dependencies in turn have more dependencies.

Inversion of Control

One way to solve this problem is to add another layer of indirection using a technique called inversion of control (IoC). This is a general term that could apply to everything from event handling, callbacks, or simply delegating to another entity to provide a result. At its most fundamental level, IoC is an approach in which you hand over the normal sequence of control of your program logic to something else.

The normal sequence of control for creating an object in Java is to create it directly using the new operator, as is done with ByHandEggCollector above. This immediately calls the object's constructor and returns a new instance of the EggCollector implementation, the one we specified by name. With inversion of control, we offload the responsibility of deciding which type of EggCollector to create to some other object or framework.

Factory

One way to delegate the responsibilities of creating the EggCollector is to use the factory pattern, which you've been familiar with for some time now. It could be as simple as single method in a separate class.

Using a factory to create an EggCollector instance.
public class FarmDependenciesFactory {

  public static EggCollector createEggCollector() {
    return new ByHandEggCollector();
  }

}
public class FarmServiceImpl implements FarmService {

  private final EggCollector eggCollector = FarmDependenciesFactory.createEggCollector();

  …

}

Service Locator

A variation of the factory design pattern is the service locator pattern. Because many services do things and don't maintain state information specific to one caller, one can make a service be a singleton so that only one service exists for all components of the program to use. (See Singletons below for more information.) A service locator provides a common location from which all parts of the program can come look up a desired service.

Using a service locator to retrieve an instance of EggCollector.class.
public class FarmServiceLocator {

  private final EggCollector eggCollector = new ByHandEggCollector();

  public static <T> T getService(final Class<T> serviceType) {
    if(EggCollector.class.isInstance(serviceType) {
      return serviceType.cast(eggCollector);
    }
    //TODO look up other types of services
  }

}
public class FarmServiceImpl implements FarmService {

  private final EggCollector eggCollector = FarmServiceLocator.getService(EggCollector.class);

  …

}

Like a factory, a service locator pattern can be swapped out to provide other implementations. But most commonly a service locator is configured with the service singletons it will make available. It may create a map of interfaces to implementations, for instance, which can be configured with specific implementations before the service locator is used.

Dependency Injection

Dependency injection (DI) is a form of inversion of control taken to the extreme. In many ways DI could be considered a variation of a service locator, but crucially the DI framework will actively provide or inject the dependencies needed by a class.

JSR-330

For the DI framework to inject dependencies, your class must provide the DI framework some avenue of injecting the correct value. While DI frameworks have historically used proprietary approaches for specifying how your code will be injected, JSR 330 and the javax.inject package now provide a set of standard annotations which most DI frameworks have adopted.

The javax.inject.Inject annotation is used to indicate the point at which a dependency should be injected. Use of @Inject will indicate to the DI framework that it should supply some needed implementation. (Exactly how this works is explained later in the lesson.)

Injection Types

There are several ways a dependency can be injected, and there is no requirement that you stick to one only (although consistency promotes understadability). Here we'll discuss the three most important approaches: field injection, setter injection, and constructor injection.

Field injection

Instance variables can be points of injection. This approach is most similar to the first FarmServiceImpl variation above.

Injecting an implementation into a field.
public class FarmServiceImpl implements FarmService {

  @Inject
  private EggCollector eggCollector;

  …

}

Note that a general interface EggCollector is indicated as the field type. Simply by using the @Inject annotation will cause the DI framework to magically provide the correct implementation of EggCollector—provided that the DI framework has been configured, as explained later in this lesson.

Setter Injection

Setter injection uses a JavaBean-type setter method to provide an entry point for injection. Rather than magically setting the instance variable, the DI framework will magically call the correct setter at the appropriate time.

Injecting a dependency using a setter method.
public class FarmServiceImpl implements FarmService {

  private EggCollector eggCollector;

  @Inject
  public void setEggCollector(@Nonnull final EggCollector eggCollector) {
    this.eggCollector = checkNotNull(eggCollector);
  }

  …

}
Constructor Injection

With constructor injection, the dependencies are provided up-front as arguments to the constructor.

Injecting a dependency via the constructor.
public class FarmServiceImpl implements FarmService {

  private final EggCollector eggCollector;

  @Inject
  public FarmServiceImpl(@Nonnull final EggCollector eggCollector) {
    this.eggCollector = checkNotNull(eggCollector);
  }

  …

}

Singletons

If you would like the DI container to ensure that only one instance of your class is instantiated in the container, you can use the javax.inject.Singleton annotation. In fact many services and managers are meant to serve various callers, and should be used as singletons. If you mark an implementation with @Singleton, rather than creating a new instance of the implementation for each injection, the DI framework will create a single instance for injecting at all the appropriate injection points.

Marking that an implementation should be a singleton.
@Singleton
public class FarmServiceImpl implements FarmService {

  …

}

DI Containers

Until now we have ignored how to configure which implementations should be injected, as well as what exactly is doing the injection. Both of these matters are controlled by the DI container. One of the first libraries that made DI popular is the Spring Framework, which has now provides a plethora of interrelated modules and frameworks to handle everything from security to social networking. Google provides its own dependency injection framework named Guice.

Guice

Guice (which is pronounced like juice) is the original dependency injection framework from Google. The Guice DI container will be used in these lessons, but most of the concepts appear in other DI containers as well.

Modules

Configuration of the Google Guice DI container takes place inside a com.google.inject.Module instance. The com.google.inject.AbstractModule class makes it easy to specify which concrete implementation should be used for a requested type, by passing the type (usually an interface) to AbstractModule.bind(Class<T> clazz). The bind() method returns a LinkedBindingBuilder instance which acts as a fluent interface for specifying which implementation should be used.

The most common binding uses LinkedBindingBuilder.to(Class<? extends T> implementation), specifying the type of the desired concrete implementation. Here is how we could configure a module to use a ContraptionEggCollector for collecting eggs:

Configuring a Guice module with concrete bindings for interfaces.
public class FarmModule extends AbstractModule {

  @Override
  protected void configure() {
    bind(EggCollector.class).to(ContraptionEggCollector.class);
    bind(FarmService.class).to(FarmServiceImpl.class);
  }

}

Injector

The actual DI container for Guice is the com.google.inject.Injector interface. You create an injector using the main com.google.inject.Guice class. Specify which module has the bindings you want and call the static factory method Guice.createInjector(Module... modules).

Once you have an implementation of a Guice Injector, you can ask it for an implementation of a particular type. The magic happens behind the scenes: the injector will determine which concrete type is bound to the abstract type or interface requested and create it for you, automatically injecting dependencies at @Inject points. As is often the case, the concrete type itself may have dependencies (specified as interfaces if following best practices). The injector will in term look up the concrete implementation for each of these dependencies, creating and injecting them in turn, and continuing to create bound dependencies as needed. The returned instance is therefore likely connected to an entire graph of dependency instances—bound by the module; and created and injected by the injector.

Asking a Guice injector for an instance of an interface.
final Injector injector = Guice.createInjector(new FarmModule());

final FarmService farmService = injector.getInstance(FarmService.class);

Scope

TODO https://github.com/google/guice/wiki/Scopes

Spring

TODO

TODO talk about named injection and @Named

Benefits and Drawbacks

Dependency injection, like indirection in general, provides flexibility to your code. Its benefits include the following:

But as you learned at the beginning of this course, additional layers of indirection bring complexity and lower the transparency of the system. Dependency injection has several drawbacks:

Review

Summary

TODO

Gotchas

In the Real World

Think About It

Self Evaluation

Task

Retrofit your main business logic classes in Booker so that they are configurable using dependency injection via Google Guice.

See Also

References

Resources

Acknowledgments