Google Guice is a popular dependency injection framework for Java that makes it easy to manage the relationships between objects in your application. Guice allows you to declare the dependencies between objects, which eliminates the need for manual object creation and management. This helps to make your code more readable, maintainable and testable. In this blog, we will focus on one of its key features: Binding.
A binding is an object that corresponds to an entry in the Guice map. To create bindings, you need to extend AbstractModule
and override its configure
method. Once you’ve created your modules, pass these modules as argument to Guice.createInjector()
to build an injector, which can be used to bootstrap your application later. For example:
1
2
Injector injector = Guice.createInjector(new ApplicationModule());
Application application = injector.getInstance(Application.class);
I will walk you through all popular binding methods in this blog. Now let’s go!
Linked Binding
Linked binding maps an interface to its implementation, or a superclass to a subclass. Let’s take an example. At first, we create an interface Shape
and then create an implementation Circle
using this interface.
1
2
3
public interface Shape {
void print();
}
1
2
3
4
5
6
public class Circle implements Shape {
@Override
public void print() {
System.out.println("This is a circle!");
}
}
You can bind Shape
interface to Circle
implementation by adding following code in the Guice Module class:
1
2
3
4
5
6
public class ApplicationModule extends AbstractModule {
@Override
protected void configure() {
bind(Shape.class).to(Circle.class);
}
}
Then when you call injector.getInstance(Shape.class)
, or when the injector encounters a dependency on Shape
, it will use a Circle
. Linked bindings can also be chained.
Binding Annotation
Sometimes you have multiple bindings for the same type. To enable this, Guice supports an optional binding annotation. The annotation and type together uniquely identify a binding. This pair is called a key.
Let’s say, you have 2 implementations of Shape
interface: Circle
and Square
, and you want to create bindings for both. Then,binding annotations can help. There are 2 ways of using binding annotations.
Create a Java annotation
Defining a binding annotation requires several imports.
1
2
3
4
@Qualifier
@Target({ FIELD, PARAMETER, METHOD })
@Retention(RUNTIME)
public @interface CircleShape {}
1
2
3
4
@Qualifier
@Target({ FIELD, PARAMETER, METHOD })
@Retention(RUNTIME)
public @interface SquareShape {}
In the example above, we create 2 different java annotations: CircleShape
and SquareShape
. When you want to bind Circle
implementation, annotate the binding with CircleShape
. Similarly, annotate the binding with SquareShape
to bind Square
implementation.
1
2
3
4
5
6
7
public class ApplicationModule extends AbstractModule {
@Override
protected void configure() {
bind(Shape.class).annotatedWith(CircleShape.class).to(Circle.class);
bind(Shape.class).annotatedWith(SquareShape.class).to(Square.class);
}
}
To depend on the annotated binding, you can either apply the annotation in injector.getInstance
function:
1
2
Shape circle = injector.getInstance(Key.get(Shape.class, CircleShape.class));
Shape square = injector.getInstance(Key.get(Shape.class, SquareShape.class));
or add it to the injected parameter:
1
2
3
4
@Inject
public Service(@CircleShape Shape shape) {
...
}
@Named
Another way to distinguish bindings is @Named
that takes a string:
1
2
3
4
5
6
7
public class ApplicationModule extends AbstractModule {
@Override
protected void configure() {
bind(Shape.class).annotatedWith(Names.named("Circle")).to(Circle.class);
bind(Shape.class).annotatedWith(Names.named("Square")).to(Square.class);
}
}
Since java compiler can’t check the string, using Named
is error-prone. Please use Named
carefully.
Instance Binding
You can bind a type to a specific instance of the type. This is usually only useful for objects that don’t have dependencies of their own, such as value objects.
1
2
3
4
5
6
7
public class ApplicationModule extends AbstractModule {
@Override
protected void configure() {
bind(String.class).annotatedWith(Names.named("Name")).toInstance("Kenny");
bind(Integer.class).annotatedWith(Names.named("ID")).toInstance(123);
}
}
In the example above, when you instantiate a String
object, it will ‘Kenny’. And 123 will be assigned to Integer
in the Guice injector.
Please note that you should avoid using .toInstance
with objects that are complicated to create, since it can slow down application startup. Instead, you can use an @Provides
method, which I will cover later.
Provider Binding
Guice allows you to specify a provider class that creates instances of a particular types. This can be useful when you need to create instances of a class that require additional configuration or setup.
The Provider class needs to implement implements the javax.inject.Provider
interface. The get()
method is responsible for creating and returning the instance.
1
2
3
4
5
6
7
8
public class CircleProvider implements Provider<Shape> {
public Shape get() {
Circle circle = new Circle();
// Perform additional set up for circle instance
...
return circle;
}
}
Then, you can create a binding using this Provider class in the Guice module class:
1
2
3
4
5
6
public class ApplicationModule extends AbstractModule {
@Override
protected void configure() {
bind(Shape.class).toProvider(CircleProvider.class);
}
}
@Provides
@Provides
is powerful to create bindings, because it can almost satisfy all use cases mentioned in previous sections. You gain more control over the instantiation process of a class by @Provides
.
Instead of using bind
by overriding configure()
method, you can create functions annotated by @Provides
. The method’s return type is the bound type. Whenever the injector needs an instance of that type, it will invoke the method. Binding annotation can also be applied to distinguish implementations of the same type.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class ApplicationModule extends AbstractModule {
@Override
protected void configure() {
...
}
@Provides
@CircleShape
Shape providesCircle() {
return new Circle();
}
@Provides
@SquareShape
Shape providesSquare() {
return new Square();
}
}
This is my preferred option to create bindings, because you do not need to remember which bind
should be used in configure()
. All you need is to define every instantiation details inside the function annotated with @Provides
.
Just-in-time Binding
In addition to explicitly creating bindings in the Guice module class, bindings can also be created implicitly. If a type is needed but there isn’t an explicit binding, the injector will attempt to create a Just-In-Time binding.
@Inject Constructors
This is commonly seen to create JIT(Just-In-Time) bindings. You simply need to annotate the constructor of the class that you want to bind with the @Inject
annotation. For example:
1
2
3
4
5
6
7
8
9
public class Application {
private Shape circle;
@Inject
public Application(@CircleShape Shape circle) {
this.circle = circle;
}
...
Besides using @Inject
constructors, a special case exists when constructors take zero arguments, and is non-private and defined in a non-private class. Although a class does not have an annotated constructor, it can also be injected if a zero-argument constructor is created.
@ImplementedBy and @ProvidedBy
Implicit bindings are created when a class is annotated by either @ImplementedBy
or @ProvidedBy
. For example,
1
2
3
4
@ImplementedBy(Circle.class)
public interface Shape {
void print();
}
or
1
2
3
4
@ProvidedBy(CircleProvider.class)
public interface Shape {
void print();
}
@ImplementedBy
annotation is equivalent to the following bind()
statement:
1
bind(Shape.class).to(Circle.class);
@ProvidedBy
annotation is equivalent to the following bind()
statement:
1
bind(Shape.class).toProvider(CircleProvider.class);
Enforce Explicit Bindings
To disable implicit bindings, you can use the requireExplicitBindings
API:
1
2
3
4
5
6
public class ApplicationModule extends AbstractModule {
@Override
protected void configure() {
binder().requireExplicitBindings();
}
}
After enabling this, all bindings must be listed in a Module class in order to be injected.
Comments powered by Disqus.