Choosing a framework wisely is important when implementing Dependency Injection as each framework has its own advantages and disadvantages. There are various Java-based dependency injection frameworks available in the open source community, such as Dagger, Google Guice, Spring DI, JAVA EE 8 DI, and PicoContainer.
In this article we will learn about Google Guice (pronounced juice), a lightweight DI framework that helps developers to modularize applications. Guice encapsulates annotation and generics features introduced by Java 5 to make code type-safe. It enables objects to wire together and tests with fewer efforts. Annotations help you to write error-prone and reusable code.
This tutorial is an excerpt taken from the book 'Java 9 Dependency Injection', written by Krunal Patel, Nilang Patel.
In Guice, the new keyword is replaced with @inject for injecting dependency. It allows constructors, fields, and methods (any method with multiple numbers of arguments) level injections. Using Guice, we can define custom scopes and circular dependency. It also has features to integrate with Spring and AOP interception.
Moreover, Guice also implements Java Specification Request (JSR) 330, and uses the standard annotation provided by JSR-330. The first version of Guice was introduced by Google in 2007 and the latest version is Guice 4.1.
Before we see how dependency injection gets implemented in Guice, let's first setup Guice.
To make our coding simple, throughout this tutorial, we are going to use a Maven project to understand Guice DI. Let’s create a simple Maven project using the following parameters: groupid:, com.packt.guice.id, artifactId : chapter4, and version : 0.0.1-SNAPSHOT. By adding Guice 4.1.0 dependency on the pom.xml file, our final pom.xml will look like this:
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.packt.guice.di</groupId> <artifactId>chapter4</artifactId>
<packaging>jar</packaging>
<version>0.0.1-SNAPSHOT</version>
<name>chapter4</name>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.google.inject</groupId>
<artifactId>guice</artifactId>
<version>4.1.0</version>
</dependency>
</dependencies>
<build>
<finalName>chapter2</finalName>
</build>
</project>
We have set up Guice, now it is time to understand how injection works in Guice. Let's rewrite the example of a notification system using Guice, and along with that, we will see several indispensable interfaces and classes in Guice. We have a base interface called NotificationService, which is expecting a message and recipient details as arguments:
public interface NotificationService { boolean sendNotification(String message, String recipient); }
The SMSService concrete class is an implementation of the NotificationService interface. Here, we will apply the @Singleton annotation to the implementation class. When you consider that service objects will be made through injector classes, this annotation is furnished to allow them to understand that the service class ought to be a singleton object. Because of JSR-330 support in Guice, annotation, either from javax.inject or the com.google.inject package, can be used:
import javax.inject.Singleton;
import com.packt.guice.di.service.NotificationService;
@Singleton
public class SMSService implements NotificationService {
public boolean sendNotification(String message, String recipient) {
// Write code for sending SMS
System.out.println("SMS has been sent to " + recipient);
return true;
}
}
In the same way, we can also implement another service, such as sending notifications to a social media platform, by implementing the NotificationService interface.
It's time to define the consumer class, where we can initialize the service class for the application. In Guice, the @Inject annotation will be used to define setter-based as well as constructor-based dependency injection. An instance of this class is utilized to send notifications by means of the accessible correspondence services. Our AppConsumer class defines setter-based injection as follows:
import javax.inject.Inject;
import com.packt.guice.di.service.NotificationService;
public class AppConsumer {
private NotificationService notificationService;
//Setter based DI
@Inject
public void setService(NotificationService service) {
this.notificationService = service;
}
public boolean sendNotification(String message, String recipient){
//Business logic
return notificationService.sendNotification(message, recipient);
}
}
Guice needs to recognize which service implementation to apply, so we should configure it with the aid of extending the AbstractModule class, and offer an implementation for the configure() method. Here is an example of an injector configuration:
import com.google.inject.AbstractModule;
import com.packt.guice.di.impl.SMSService;
import com.packt.guice.di.service.NotificationService;
public class ApplicationModule extends AbstractModule{
@Override
protected void configure() {
//bind service to implementation class
bind(NotificationService.class).to(SMSService.class);
}
}
In the previous class, the module implementation determines that an instance of SMSService is to be injected into any place a NotificationService variable is determined. In the same way, we just need to define a binding for the new service implementation, if required. Binding in Guice is similar to wiring in Spring:
import com.google.inject.Guice; import com.google.inject.Injector; import com.packt.guice.di.consumer.AppConsumer; import com.packt.guice.di.injector.ApplicationModule;
public class NotificationClient {
public static void main(String[] args) {
Injector injector = Guice.createInjector(new ApplicationModule());
AppConsumer app = injector.getInstance(AppConsumer.class);
app.sendNotification("Hello", "9999999999");
}
}
In the previous program, the Injector object is created using the Guice class's createInjector() method, by passing the ApplicationModule class's implementation object. By using the injector's getInstance() method, we can initialize the AppConsumer class. At the same time as creating the AppConsumer's objects, Guice injects the needy service class implementation (SMSService, in our case). The following is the yield of running the previous code:
SMS has been sent to Recipient :: 9999999999 with Message :: Hello
So, this is how Guice dependency injection works compared to other DI. Guice has embraced a code-first technique for dependency injection, and management of numerous XML is not required.
Let's test our client application by writing a JUnit test case. We can simply mock the service implementation of SMSService, so there is no need to implement the actual service. The MockSMSService class looks like this:
import com.packt.guice.di.service.NotificationService;
public class MockSMSService implements NotificationService {
public boolean sendNotification(String message, String recipient) {
System.out.println("In Test Service :: " + message + "Recipient :: " + recipient);
return true;
}
}
The following is the JUnit 4 test case for the client application:
import org.junit.After; import org.junit.Assert; import org.junit.Before; import org.junit.Test;
import com.google.inject.AbstractModule;
import com.google.inject.Guice;
import com.google.inject.Injector;
import com.packt.guice.di.consumer.AppConsumer;
import com.packt.guice.di.impl.MockSMSService;
import com.packt.guice.di.service.NotificationService;
public class NotificationClientTest {
private Injector injector;
@Before
public void setUp() throws Exception {
injector = Guice.createInjector(new AbstractModule() {
@Override
protected void configure() {
bind(NotificationService.class).to(MockSMSService.class);
}
});
}
@After
public void tearDown() throws Exception {
injector = null;
}
@Test
public void test() {
AppConsumer appTest = injector.getInstance(AppConsumer.class);
Assert.assertEquals(true, appTest.sendNotification("Hello There", "9898989898"));;
}
}
Take note that we are binding the MockSMSService class to NotificationService by having an anonymous class implementation of AbstractModule. This is done in the setUp() method, which runs for some time before the test methods run.
As we know what dependency injection is, let us explore how Google Guice provides injection.
We have seen that the injector helps to resolve dependencies by reading configurations from modules, which are called bindings. Injector is preparing charts for the requested objects.
Dependency injection is managed by injectors using various types of injection:
Constructor injection can be achieved by using the @Inject annotation at the constructor level. This constructor ought to acknowledge class dependencies as arguments. Multiple constructors will, at that point, assign the arguments to their final fields:
public class AppConsumer {
private NotificationService notificationService;
//Constructor level Injection
@Inject
public AppConsumer(NotificationService service){
this.notificationService=service;
}
public boolean sendNotification(String message, String recipient){
//Business logic
return notificationService.sendNotification(message, recipient);
}
}
If our class does not have a constructor with @Inject, then it will be considered a default constructor with no arguments. When we have a single constructor and the class accepts its dependency, at that time the constructor injection works perfectly and is helpful for unit testing. It is also easy because Java is maintaining the constructor invocation, so you don't have to stress about objects arriving in an uninitialized state.
Guice allows us to define injection at the method level by annotating methods with the @Inject annotation. This is similar to the setter injection available in Spring. In this approach, dependencies are passed as parameters, and are resolved by the injector before invocation of the method. The name of the method and the number of parameters does not affect the method injection:
private NotificationService notificationService; //Setter Injection @Inject public void setService(NotificationService service) { this.notificationService = service; }
This could be valuable when we don't want to control instantiation of classes. We can, moreover, utilize it in case you have a super class that needs a few dependencies. (This is difficult to achieve in a constructor injection.)
Fields can be injected by the @Inject annotation in Guice. This is a simple and short injection, but makes the field untestable if used with the private access modifier. It is advisable to avoid the following:
@Inject private NotificationService notificationService;
Guice provides a way to declare an injection as optional. The method and field might be optional, which causes Guice to quietly overlook them when the dependencies aren't accessible. Optional injection can be used by mentioning the @Inject(optional=true) annotation:
public class AppConsumer { private static final String DEFAULT_MSG = "Hello"; private string message = DEFAULT_MSG;
@Inject(optional=true)
public void setDefaultMessage(@Named("SMS") String message) {
this.message = message;
}
}
Static injection is helpful when we have to migrate a static factory implementation into Guice. It makes it feasible for objects to mostly take part in dependency injection by picking up access to injected types without being injected themselves. In a module, to indicate classes to be injected on injector creation, use requestStaticInjection(). For example, NotificationUtil is a utility class that provides a static method, timeZoneFormat, to a string in a given format, and returns the date and timezone. The TimeZoneFormat string is hardcoded in NotificationUtil, and we will attempt to inject this utility class statically.
Consider that we have one private static string variable, timeZonFmt, with setter and getter methods. We will use @Inject for the setter injection, using the @Named parameter.
NotificationUtil will look like this:
@Inject static String timezonFmt = "yyyy-MM-dd'T'HH:mm:ss";
@Inject
public static void setTimeZoneFmt(@Named("timeZoneFmt")String timeZoneFmt){
NotificationUtil.timeZoneFormat = timeZoneFmt;
}
Now, SMSUtilModule should look like this:
class SMSUtilModule extends AbstractModule{ @Override protected void configure() { bindConstant().annotatedWith(Names.named(timeZoneFmt)).to(yyyy-MM-dd'T'HH:mm:ss); requestStaticInjection(NotificationUtil.class); } }
This API is not suggested for common utilization since it faces many of the same issues as static factories. It is also difficult to test and it makes dependencies uncertain.
To sum up, what we learned in this tutorial, we began with basic dependency injection then we learned how basic Dependency Injection works in Guice, with examples.
If you found this post useful, be sure to check out the book 'Java 9 Dependency Injection' to learn more about Google Guice and other concepts in dependency injection.
Learning Dependency Injection (DI)
Angular 2 Dependency Injection: A powerful design pattern