Guice – Google
1. Introduction
Guice is a framework that makes it easier for your application to use the dependency injection (DI) pattern. Dependency injection is a design pattern wherein classes declare their dependencies as arguments instead of creating those dependencies directly. For example, a class ‘A’ needs class ‘B’ to perform its job, so class ‘A’ does not need to worry about how it needs to build a class ‘B’ object – this should be done externally. Let us take an example where we are building an application to handle user data. We have a service class called UserService
which talks to the database.
public class UserService { private Datastore datastore; public UserService() { this.datastore = new Datastore("/org/my-datastore"); } }
In the example above the UserService
class is instantiating the Datastore
. This makes the Datastore
class tightly coupled with the UserService
class. If in the future we want to change the Datastore, UserService class will need to make the change as well. This makes testing this class very hard. Instead of writing untestable or inflexible code, you can use a dependency injection pattern to address all these issues. Below is the same example but using dependency injection:
public class UserService { private Datastore datastore; public UserService(Datastore datastore) { this.datastore = datastore; } }
The above UserService
class can be used with any Datastore
as UserService
class has no knowledge of how the Datastore is created. For testing purposes, you can even use an in-memory database.
2. Binding
Guice uses an embedded domain-specific language, or EDSL, to help you create bindings simply and readably. This approach is great for overall usability, but it does come with a small cost: it is difficult to learn how to use the Binding EDSL by reading method-level javadocs. To bind a simple class:
bind(DefaultImpl.class);
This statement does essentially nothing; it “binds the ServiceImpl class to itself” and does not change Guice’s default behavior. You may still want to use this if you prefer your Module class to serve as an explicit manifest for the services it provides. We can bind the Service with a specific implementation:
bind(Service.class).to(ServiceImpl.class);
This specifies that a request for a Service
instance with no binding annotations should be treated as if it were a request for a ServiceImpl
instance. This overrides the function of any @ImplementedBy
or @ProvidedBy
annotations found on Service
, since Guice will have already moved on to ServiceImpl
before it reaches the point when it starts looking for these annotations.
bind(Service.class).toProvider(ServiceProvider.class);
In this example, ServiceProvider
must extend or implement Provider
. This binding specifies that Guice should resolve an unannotated injection request for Service
by first resolving an instance of ServiceProvider
in a regular way, then calling get()
on the resulting Provider
instance to obtain the Service
instance.
bind(Service.class).annotatedWith(MyBinder.class).to(ServiceImpl.class);
Like the previous example, but only applies to injection requests that use the binding annotation @MyBinder
3. Example
In this section, we will see a working example. Let us create a maven project and define the dependency for google guice like below:
com.google.inject guice 5.1.0
pom.xml
4.0.0 org.example JavaCodeGeeks 1.0-SNAPSHOT 16 16 com.google.inject guice 5.1.0
This will let us use the required classes for this simple example. Let us first define a very simple domain class User
User.java
package org.javacodegeeks; public record User (String username, String address) {}
JDK 14 introduces records
, which are a new kind of type declaration. Like an enum
, a record is a restricted form of a class. It’s ideal for “plain data carriers,” classes that contain data not meant to be altered and only the most fundamental methods such as constructors and accessors.
Now let us define a controller class that will handle requests related to the users. For simplicity, we will only define one method in this class to register the user. The method will take three parameters – first name, surname, and address and will call the service method. The service is injected into the controller using guice @Inject
annotation.
UserController.java
package org.javacodegeeks; import com.google.inject.Inject; public class UserController { @Inject private UserService userService; public void registerUser(String firstname, String surname, String address) { userService.registerUser(new User(firstname + "_" + surname, address)); } }
Let us now define our simple service interface:
UserService.java
package org.javacodegeeks; public interface UserService { void registerUser(User user); }
Below is the default implementation of the service:
DefaultUserService.java
package org.javacodegeeks; public class DefaultUserService implements UserService { @Override public void registerUser(User user) { // TODO - implement System.out.println(String.format("User %s registered successfully", user.username())); } }
Now let us bind this default implementation to the service:
BasicModule.java
package org.javacodegeeks; import com.google.inject.AbstractModule; public class BasicModule extends AbstractModule { @Override protected void configure() { bind(UserService.class).to(DefaultUserService.class); } }
Now let us create our main class:
Application.java
package org.javacodegeeks; import com.google.inject.Guice; import com.google.inject.Injector; public class Application { public static void main(String[] args) { Injector injector = Guice.createInjector(new BasicModule()); UserController userController = injector.getInstance(UserController.class); userController.registerUser("Tom", "Cruise", "NYC"); } }
Guice is the entry point to the Guice framework. It creates Injectors from Modules. Guice supports a model of development that draws clear boundaries between APIs, Implementations of these APIs, Modules which configure these implementations, and finally Applications that consist of a collection of Modules. It is the Application, which typically defines your main()
method, that bootstraps the Guice Injector using the Guice
class.
When you run the Application
class, you will see an output as below:
User Tom_Cruise registered successfully
4. Conclusion
In this article, we looked at how to use Google Guice for dependency injection. This is a very simple example – there are a lot more advanced features which is out of the scope of this article. For example, you can use Named bindings when you annotate the class which you are binding with the @Named annotation, and in your module, you can do something like the below:
bind(MyInterface.class) .annotatedWith(Names.named("NamedBinding")) .to(DefaultImpl.class);
We also looked at different ways of binding the classes. In the end, we looked at a very simple working example
5. Download
This was an example of Google Guice.
You can download the full source code of this example here: Google Guice