§Dependency Injection
Dependency injection is a widely used design pattern that helps to separate your components’ behaviour from dependency resolution. Components declare their dependencies, usually as constructor parameters, and a dependency injection framework helps you wire together those components so you don’t have to do so manually.
Out of the box, Play provides dependency injection support based on JSR 330. The default JSR 330 implementation that comes with Play is Guice, but other JSR 330 implementations can be plugged in. To enable the Play-provided Guice module, make sure you have guice
in your library dependencies in build.sbt, e.g.:
libraryDependencies += guice
The Guice wiki is a great resource for learning more about the features of Guice and DI design patterns in general.
§Motivation
Dependency injection achieves several goals:
1. It allows you to easily bind different implementations for the same component. This is useful especially for testing, where you can manually instantiate components using mock dependencies or inject an alternate implementation.
2. It allows you to avoid global static state. While static factories can achieve the first goal, you have to be careful to make sure your state is set up properly. In particular Play’s (now deprecated) static APIs require a running application, which makes testing less flexible. And having more than one instance available at a time makes it possible to run tests in parallel.
The Guice wiki has some good examples explaining this in more detail.
§How it works
Play provides a number of built-in components and declares them in modules such as its BuiltinModule. These bindings describe everything that’s needed to create an instance of Application
, including, by default, a router generated by the routes compiler that has your controllers injected into the constructor. These bindings can then be translated to work in Guice and other runtime DI frameworks.
The Play team maintains the Guice module, which provides a GuiceApplicationLoader. That does the binding conversion for Guice, creates the Guice injector with those bindings, and requests an Application
instance from the injector.
There are also third-party loaders that do this for other frameworks, including Spring.
We explain how to customize the default bindings and application loader in more detail below.
§Declaring dependencies
If you have a component, such as a controller, and it requires some other components as dependencies, then this can be declared using the @Inject annotation. The @Inject
annotation can be used on fields or on constructors. For example, to use field injection:
import javax.inject.*;
import play.libs.ws.*;
public class MyComponent {
@Inject WSClient ws;
// ...
}
Note that those are instance fields. It generally doesn’t make sense to inject a static field, since it would break encapsulation.
To use constructor injection:
import javax.inject.*;
import play.libs.ws.*;
public class MyComponent {
private final WSClient ws;
@Inject
public MyComponent(WSClient ws) {
this.ws = ws;
}
// ...
}
Field injection is shorter, but we recommend using constructor injection in your application. It is the most testable, since in a unit test you need to pass all the constructor arguments to create an instance of your class, and the compiler makes sure the dependencies are all there. It is also easy to understand what is going on, since there is no “magic” setting of fields going on. The DI framework is just automating the same constructor call you could write manually.
Guice also has several other types of injections which may be useful in some cases. If you are migrating an application that uses statics, you may find its static injection support useful.
Guice is able to automatically instantiate any class with an @Inject
on its constructor without having to explicitly bind it. This feature is called just in time bindings is described in more detail in the Guice documentation. If you need to do something more sophisticated you can declare a custom binding as described below.
§Dependency injecting controllers
Play’s routes compiler generates a router class that declares your controllers as dependencies in the constructor. This allows your controllers to be injected into the router.
To enable the injected routes generator specifically, add the following to your build settings in build.sbt
:
routesGenerator := InjectedRoutesGenerator
Prefixing the controller name with an @
symbol takes on a special meaning: instead of the controller being injected directly, a Provider
of the controller will be injected. This allows, for example, prototype controllers, as well as an option for breaking cyclic dependencies.
§Component lifecycle
The dependency injection system manages the lifecycle of injected components, creating them as needed and injecting them into other components. Here’s how component lifecycle works:
- New instances are created every time a component is needed. If a component is used more than once, then, by default, multiple instances of the component will be created. If you only want a single instance of a component then you need to mark it as a singleton.
- Instances are created lazily when they are needed. If a component is never used by another component, then it won’t be created at all. This is usually what you want. For most components there’s no point creating them until they’re needed. However, in some cases you want components to be started up straight away or even if they’re not used by another component. For example, you might want to send a message to a remote system or warm up a cache when the application starts. You can force a component to be created eagerly by using an eager binding.
- Instances are not automatically cleaned up, beyond normal garbage collection. Components will be garbage collected when they’re no longer referenced, but the framework won’t do anything special to shut down the component, like calling a
close
method. However, Play provides a special type of component, called theApplicationLifecycle
which lets you register components to shut down when the application stops.
§Singletons
Sometimes you may have a component that holds some state, such as a cache, or a connection to an external resource, or a component might be expensive to create. In these cases it may be important that there is only one instance of that component. This can be achieved using the @Singleton annotation:
import javax.inject.*;
@Singleton
public class CurrentSharePrice {
private volatile int price;
public void set(int p) {
price = p;
}
public int get() {
return price;
}
}
§Stopping/cleaning up
Some components may need to be cleaned up when Play shuts down, for example, to stop thread pools. Play provides an ApplicationLifecycle component that can be used to register hooks to stop your component when Play shuts down:
import java.util.concurrent.CompletableFuture;
import javax.inject.*;
import play.inject.ApplicationLifecycle;
@Singleton
public class MessageQueueConnection {
private final MessageQueue connection;
@Inject
public MessageQueueConnection(ApplicationLifecycle lifecycle) {
connection = MessageQueue.connect();
lifecycle.addStopHook(
() -> {
connection.stop();
return CompletableFuture.completedFuture(null);
});
}
// ...
}
The ApplicationLifecycle
will stop all components in reverse order from when they were created. This means any components that you depend on can still safely be used in your components stop hook, since because you depend on them, they must have been created before your component was, and therefore won’t be stopped until after your component is stopped.
Note: It’s very important to ensure that all components that register a stop hook are singletons. Any non singleton components that register stop hooks could potentially be a source of memory leaks, since a new stop hook will be registered each time the component is created.
You can can also implement the cleanup logic using Coordinated Shutdown. Play uses Akka’s Coordinated Shutdown internally but it is also available for userland code. ApplicationLifecycle#stop
is implemented as a Coordinated Shutdown task. The main difference is that ApplicationLifecycle#stop
runs all stop hooks sequentially in a predictable order where Coordinated Shutdown runs all tasks in the same phase in parallel which may be faster but unpredictable.
§Providing custom bindings
It is considered good practice to define an interface for a component, and have other classes depend on that interface, rather than the implementation of the component. By doing that, you can inject different implementations, for example you inject a mock implementation when testing your application.
In this case, the DI system needs to know which implementation should be bound to that interface. The way we recommend that you declare this depends on whether you are writing a Play application as an end user of Play, or if you are writing library that other Play applications will consume.
§Play applications
We recommend that Play applications use whatever mechanism is provided by the DI framework that the application is using. Although Play does provide a binding API, this API is somewhat limited, and will not allow you to take full advantage of the power of the framework you’re using.
Since Play provides support for Guice out of the box, the examples below show how to provide bindings for Guice.
§Binding annotations
The simplest way to bind an implementation to an interface is to use the Guice @ImplementedBy annotation. For example:
import com.google.inject.ImplementedBy;
@ImplementedBy(EnglishHello.class)
public interface Hello {
String sayHello(String name);
}
public class EnglishHello implements Hello {
public String sayHello(String name) {
return "Hello " + name;
}
}
§Programmatic bindings
In some more complex situations, you may want to provide more complex bindings, such as when you have multiple implementations of the one interface, which are qualified by @Named annotations. In these cases, you can implement a custom Guice Module:
import com.google.inject.AbstractModule;
import com.google.inject.name.Names;
public class Module extends AbstractModule {
protected void configure() {
bind(Hello.class).annotatedWith(Names.named("en")).to(EnglishHello.class);
bind(Hello.class).annotatedWith(Names.named("de")).to(GermanHello.class);
}
}
If you call this module Module
and place it in the root package, it will automatically be registered with Play. Alternatively, if you want to give it a different name or put it in a different package, you can register it with Play by appending its fully qualified class name to the play.modules.enabled
list in application.conf
:
play.modules.enabled += "modules.HelloModule"
You can also disable the automatic registration of a module named Module
in the root package by adding it to the disabled modules:
play.modules.disabled += "Module"
§Configurable bindings
Sometimes you might want to read the Config
or use a ClassLoader
when you configure Guice bindings. You can get access to these objects by adding them to your module’s constructor.
In the example below, the Hello
binding for each language is read from a configuration file. This allows new Hello
bindings to be added by adding new settings in your application.conf
file.
import com.google.inject.AbstractModule;
import com.google.inject.name.Names;
import com.typesafe.config.Config;
import play.Environment;
public class Module extends AbstractModule {
private final Environment environment;
private final Config config;
public Module(Environment environment, Config config) {
this.environment = environment;
this.config = config;
}
protected void configure() {
// Expect configuration like:
// hello.en = "myapp.EnglishHello"
// hello.de = "myapp.GermanHello"
final Config helloConf = config.getConfig("hello");
// Iterate through all the languages and bind the
// class associated with that language. Use Play's
// ClassLoader to load the classes.
helloConf
.entrySet()
.forEach(
entry -> {
try {
String name = entry.getKey();
Class<? extends Hello> bindingClass =
environment
.classLoader()
.loadClass(entry.getValue().toString())
.asSubclass(Hello.class);
bind(Hello.class).annotatedWith(Names.named(name)).to(bindingClass);
} catch (ClassNotFoundException ex) {
throw new RuntimeException(ex);
}
});
}
}
Note: In most cases, if you need to access
Config
when you create a component, you should inject theConfig
object into the component itself or into the component’sProvider
. Then you can read theConfig
when you create the component. You usually don’t need to readConfig
when you create the bindings for the component.
§Eager bindings
In the code above, new EnglishHello
and GermanHello
objects will be created each time they are used. If you only want to create these objects once, perhaps because they’re expensive to create, then you should use the @Singleton
annotation. If you want to create them once and also create them eagerly when the application starts up, rather than lazily when they are needed, then you can use Guice’s eager singleton binding.
import com.google.inject.AbstractModule;
import com.google.inject.name.Names;
// A Module is needed to register bindings
public class Module extends AbstractModule {
protected void configure() {
// Bind the `Hello` interface to the `EnglishHello` implementation as eager singleton.
bind(Hello.class).annotatedWith(Names.named("en")).to(EnglishHello.class).asEagerSingleton();
bind(Hello.class).annotatedWith(Names.named("de")).to(GermanHello.class).asEagerSingleton();
}
}
Eager singletons can be used to start up a service when an application starts. They are often combined with a shutdown hook so that the service can clean up its resources when the application stops.
import javax.inject.*;
import play.inject.ApplicationLifecycle;
import play.Environment;
import java.util.concurrent.CompletableFuture;
// This creates an `ApplicationStart` object once at start-up.
@Singleton
public class ApplicationStart {
// Inject the application's Environment upon start-up and register hook(s) for shut-down.
@Inject
public ApplicationStart(ApplicationLifecycle lifecycle, Environment environment) {
// Shut-down hook
lifecycle.addStopHook(
() -> {
return CompletableFuture.completedFuture(null);
});
// ...
}
}
import com.google.inject.AbstractModule;
public class StartModule extends AbstractModule {
protected void configure() {
bind(ApplicationStart.class).asEagerSingleton();
}
}
§Play libraries
If you’re implementing a library for Play, then you probably want it to be DI framework agnostic, so that your library will work out of the box regardless of which DI framework is being used in an application. For this reason, Play provides a lightweight binding API for providing bindings in a DI framework agnostic way.
To provide bindings, implement a Module to return a sequence of the bindings that you want to provide. The Module
trait also provides a DSL for building bindings:
import com.typesafe.config.Config;
import java.util.Arrays;
import java.util.List;
import play.Environment;
import play.inject.Binding;
import play.inject.Module;
public class HelloModule extends Module {
@Override
public List<Binding<?>> bindings(Environment environment, Config config) {
return Arrays.asList(
bindClass(Hello.class).qualifiedWith("en").to(EnglishHello.class),
bindClass(Hello.class).qualifiedWith("de").to(GermanHello.class));
}
}
This module can be registered with Play automatically by appending it to the play.modules.enabled
list in reference.conf
:
play.modules.enabled += "com.example.HelloModule"
- The
Module
bindings
method takes a PlayEnvironment
andConfiguration
. You can access these if you want to configure the bindings dynamically. - Module bindings support eager bindings. To declare an eager binding, add
.eagerly()
at the end of yourBinding
.
In order to maximize cross framework compatibility, keep in mind the following things:
- Not all DI frameworks support just in time bindings. Make sure all components that your library provides are explicitly bound.
- Try to keep binding keys simple - different runtime DI frameworks have very different views on what a key is and how it should be unique or not.
§Excluding modules
If there is a module that you don’t want to be loaded, you can exclude it by appending it to the play.modules.disabled
property in application.conf
:
play.modules.disabled += "play.api.db.evolutions.EvolutionsModule"
§Managing circular dependencies
Circular dependencies happen when one of your components depends on another component that depends on the original component (either directly or indirectly). For example:
public class Foo {
@Inject
public Foo(Bar bar) {
// ...
}
}
public class Bar {
@Inject
public Bar(Baz baz) {
// ...
}
}
public class Baz {
@Inject
public Baz(Foo foo) {
// ...
}
}
In this case, Foo
depends on Bar
, which depends on Baz
, which depends on Foo
. So you won’t be able to instantiate any of these classes. You can work around this problem by using a Provider
:
public class Foo {
@Inject
public Foo(Bar bar) {
// ...
}
}
public class Bar {
@Inject
public Bar(Baz baz) {
// ...
}
}
public class Baz {
@Inject
public Baz(Provider<Foo> fooProvider) {
// ...
}
}
Note that if you’re using constructor injection it will be much more clear when you have a circular dependency, since it will be impossible to instantiate the component manually.
Generally, circular dependencies can be resolved by breaking up your components in a more atomic way, or finding a more specific component to depend on. A common problem is a dependency on Application
. When your component depends on Application
it’s saying that it needs a complete application to do its job; typically that’s not the case. Your dependencies should be on more specific components (e.g. Environment
) that have the specific functionality you need. As a last resort you can work around the problem by injecting a Provider<Application>
.
§Advanced: Extending the GuiceApplicationLoader
Play’s runtime dependency injection is bootstrapped by the GuiceApplicationLoader
class. This class loads all the modules, feeds the modules into Guice, then uses Guice to create the application. If you want to control how Guice initializes the application then you can extend the GuiceApplicationLoader
class.
There are several methods you can override, but you’ll usually want to override the builder
method. This method reads the ApplicationLoader.Context
and creates a GuiceApplicationBuilder
. Below you can see the standard implementation for builder
, which you can change in any way you like. You can find out how to use the GuiceApplicationBuilder
in the section about testing with Guice.
import com.typesafe.config.Config;
import com.typesafe.config.ConfigFactory;
import play.ApplicationLoader;
import play.inject.guice.GuiceApplicationBuilder;
import play.inject.guice.GuiceApplicationLoader;
public class CustomApplicationLoader extends GuiceApplicationLoader {
@Override
public GuiceApplicationBuilder builder(ApplicationLoader.Context context) {
Config extra = ConfigFactory.parseString("a = 1");
return initialBuilder
.in(context.environment())
.loadConfig(extra.withFallback(context.initialConfig()))
.overrides(overrides(context));
}
}
When you override the ApplicationLoader
you need to tell Play. Add the following setting to your application.conf
:
play.application.loader = "modules.CustomApplicationLoader"
You’re not limited to using Guice for dependency injection. By overriding the ApplicationLoader
you can take control of how the application is initialized.
§Adding dependency to a class without touching subclasses
Sometimes you may want to add new dependency to some base class which may have many subclasses.
To avoid providing the dependency directly to each of them you may add it as an injectable field.
This approach can reduce the testability of the class so use it with care.
import com.google.inject.ImplementedBy;
@ImplementedBy(LiveCounter.class)
interface Counter {
public void inc(String label);
}
public class NoopCounter implements Counter {
public void inc(String label) {}
}
import javax.inject.Singleton;
@Singleton
public class LiveCounter implements Counter {
public void inc(String label) {
System.out.println("inc " + label);
}
}
import javax.inject.Inject;
import play.mvc.Controller;
import play.mvc.Result;
public class BaseController extends Controller {
// LiveCounter will be injected
@Inject protected volatile Counter counter = new NoopCounter();
public Result someBaseAction(String source) {
counter.inc(source);
return ok(source);
}
}
import javax.inject.Singleton;
import play.mvc.Result;
@Singleton
public class SubclassController extends BaseController {
public Result index() {
return someBaseAction("index");
}
}