Guice (pronounced ‘juice’) is a lightweight dependency injection framework for Java 8 and above, brought to you by Google.

Dependency Injection Using Guice in Java https://github.com/google/guice/wiki/GettingStarted

Dependency injection is a design pattern wherein classes declare their dependencies as arguments instead of creating those dependencies

Without Dependency Injection, Bad code

// GreetingService.java
public interface GreetingService {
    String greet();
}

// GreetingServiceImpl.java
public class GreetingServiceImpl implements GreetingService {
    public String greet() {
        return "Hello, Normal without Guice!";
    }
}
// MyClass.java
public class MyClass {
    private final GreetingService greetingService = new GreetingServiceImpl();

    public void performGreeting() {
        String message = greetingService.greet();
        System.out.println(message);
    }

    public static void main(String[] args) {
        MyClass myClass = new MyClass();
        myClass.performGreeting();
    }
}

However, this approach has some disadvantages:

With Dependency Injection, Without Guice

Without using Guice, this is how you will need to do things in java:

image

// MyClass.java
public class MyClass {
    private final GreetingService greetingService;

    public MyClass(GreetingService greetingService) {
        this.greetingService = greetingService;
    }

    public void performGreeting() {
        String message = greetingService.greet();
        System.out.println(message);
    }

    public static void main(String[] args) {
        GreetingService greetingService = new GreetingServiceImpl();
        MyClass myClass = new MyClass(greetingService);
        myClass.performGreeting();
    }
}

By using dependency injection, the benefits include:

Loose coupling: MyClass doesn’t need to know about the specific implementation of GreetingService. It only depends on the abstraction defined by the interface GreetingService.

Flexibility: The implementation of GreetingService can be easily changed or swapped out by providing a different implementation while creating an instance of MyClass.

Testability: With dependency injection, it becomes easier to write unit tests for MyClass. You can provide a mock or stub implementation of GreetingService during testing, enabling isolated testing of MyClass behavior.

Using Factory

Refer: https://github.com/google/guice/wiki/Motivation#factories

A factory class decouples the client and implementing class. A simple factory uses static methods to get and set mock implementations for interfaces. A factory is implemented with some boilerplate code:

public interface GreetingService {
    String greet();
}

public class DefaultGreetingService implements GreetingService {
    private final String name;

    public DefaultGreetingService(String name) {
        this.name = name;
    }

    public String greet() {
        return "Hello, " + name + "!";
    }
}

public class GreetingServiceFactory {
    private static GreetingService instance;

    public static void setInstance(GreetingService service) {
        instance = service;
    }

    public static GreetingService getInstance() {
        if (instance == null) {
            return new DefaultGreetingService("John Doe");
        }

        return instance;
    }
}

public class MyClass {
    private final GreetingService greetingService;

    public MyClass() {
        this.greetingService = GreetingServiceFactory.getInstance();
    }

    public void performGreeting() {
        String message = greetingService.greet();
        System.out.println(message);
    }

    public static void main(String[] args) {
        MyClass myClass = new MyClass();
        myClass.performGreeting();
    }
}

So we can now test it like the following

public class MyClassTest {
    @Test
    public void testPerformGreetingWithFakeService() {
        // Create a fake implementation of GreetingService
        GreetingService fakeService = new FakeGreetingService();

        // Set the fake implementation in the factory
        GreetingServiceFactory.setInstance(fakeService);

        // Create an instance of MyClass
        MyClass myClass = new MyClass();

        // Perform the greeting
        myClass.performGreeting();

        // Verify the output
        assertEquals("Hello, Fake User!", getConsoleOutput());
    }

    // Helper method to capture the console output
    private String getConsoleOutput() {
        // Replace this with your own implementation to capture the console output during the test
        return ""; // Placeholder implementation
    }

    // Fake implementation of GreetingService
    private static class FakeGreetingService implements GreetingService {
        public String greet() {
            return "Hello, Fake User!";
        }
    }
}

With Guice

By using a dependency injection framework like Guice, you can further automate and streamline the injection process, manage the lifecycle of dependencies, and handle complex dependency graphs. It simplifies the management of dependencies and promotes modular design.

When we use Guice, We will need to modify the main to use Injector to get the instance of MyClass but we don’t need to set up GreetingService explicitly in the main

import com.google.inject.Guice;
import com.google.inject.Inject;
import com.google.inject.Injector;

public class MyClass {
    private final GreetingService greetingService;

    @Inject
    public MyClass(GreetingService greetingService) {
        this.greetingService = greetingService;
    }

    public void performGreeting() {
        String message = greetingService.greet();
        System.out.println(message);
    }

    public static void main(String[] args) {
        Injector injector = Guice.createInjector(new GuiceModule());
        MyClass myClass = injector.getInstance(MyClass.class);
        myClass.performGreeting();
    }
}

Now we need module which extends inject.AbstractModule as follows, this tell that whenever we need GreetingService, pass GreetingServiceImpl, that way, we need not explicitly instantiate GreetingServiceImpl inside main class, so this can solve our problem when we need to recursively inject many classes

import com.google.inject.AbstractModule;

public class GuiceModule extends AbstractModule {
    @Override
    protected void configure() {
        bind(GreetingService.class).to(GreetingServiceImpl.class);
    }
}

Without using Constructor

In the previous example, the dependency GreetingService was passed through the constructor of MyClass. If you want to inject the dependency without passing it through the constructor, you can use the @Inject annotation on a field instead. Here’s an updated version:

import com.google.inject.Guice;
import com.google.inject.Inject;
import com.google.inject.Injector;

public class MyClass {
    @Inject
    private GreetingService greetingService;

    public void performGreeting() {
        String message = greetingService.greet();
        System.out.println(message);
    }

    public static void main(String[] args) {
        Injector injector = Guice.createInjector(new GuiceModule());
        MyClass myClass = injector.getInstance(MyClass.class);
        myClass.performGreeting();
    }
}

In this updated code, the GreetingService dependency is injected using the @Inject annotation on the field greetingService directly. The field is automatically populated by Guice when an instance of MyClass is created.

By using the @Inject annotation on the field, you don’t need to pass the dependency through the constructor explicitly. Guice will take care of injecting the appropriate instance of GreetingService when creating an instance of MyClass.