Dependency Injection and Using Guice

Dependency Injection

Simply put… A client receives service instances that it dependents on.

YourService service = new YourServiceImpl(new ServiceA(new ServiceB(new ServiceC())));

[For Details: https://en.wikipedia.org/wiki/Dependency_injection]

Using Guice for Dependency Injection

Guice, a simple DI framework for Java.

As its Wiki writes, you can think of Guice as a map to bind each dependency to a provider.

Main purpose

Instead of letting clients to look up dependencies as the above pattern, an injector interface will handle that. (@Inject is the new A = new B();). This interface should be defined in a module class, binding interfaces or class with the implementation or provider you want, and using injectors to create new instances according to your bindings.

An Example

We have this old-fashioned BugGeneratorService:

interface BugGeneratorService {
    public Bug generateBug();
}

class RandomBugGeneratorService implements BugGeneratorService {
    BugDao bugDao; // Data accessor to get a bug instance
    
    // Constructor
    RandomBugGeneratorService(BugDao bugDao) {
        this.bugDao = bugDao;
    }

    // method to generate a random bug
    @Override
    public Bug generateBug() {
        Bug bug = bugDao.getRandomBug();
        return bug;
    }
}

A client will do something like this, or put it inside a factory class.

BugDao bugDao = new NetworkBugDao();
BugGeneratorService service = new RandomBugGeneratorService(bugDao);
service.generateBug();

Moving to Guice

Define Module to declare dependencies. Here, BugGeneratorService is bound to RandomBugGeneratorService and BugDao is bound to a provider for NetworkBugDao.

class MyModule extends AbstractModule {
    @Override
    protected void configure() {
        bind(BugGeneratorService.class).to(RandomBugGeneratorService.class);
        
        // Assume if we already have a NetworkBugDaoProvider class
        bind(BugDao.class).toProvider(NetworkBugDaoProvider.class);
        // if we need a new provider class
        @Provides
        BugDao provideBugDao() {
            return NetworkBugDao();
        }
    }
}

Use Constructor Injection in service implementation. The @Inject annotation will let Guice look for instance for BugDao, in this case it will trigger NetworkBugDaoProvider when creating service with Guice.

class RandomBugGeneratorService implements BugGeneratorService {
    BugDao bugDao;
    
    // Constructor
    @Inject
    RandomBugGeneratorService(BugDao bugDao) {
        this.bugDao = bugDao;
    }

    @Override
    public Bug generateBug() {
        Bug bug = bugDao.getRandomBug();
        return bug;
    }
}

Then when you want to use BugGeneratorService

Injector injector = Guice.createInjector(new MyModule());
BugGeneratorService bugGeneratorService = injector.getInstance(BugGeneratorService.class);
service.generateBug();

Write unit tests as usual using constructor to create instance

class RandomBugGeneratorServiceUnitTest {
    @Mock
    BugDao dao;

    @Before
    void Setup() {
        RandomBugGeneratorService service = new RandomBugGeneratorService(dao);
    }
    // unit tests...
}

Integration tests with injector. The @Inject annotation will get RandomBugGeneratorService instance for BugGeneratorService, avoiding redundant code for creating RandomBugGeneratorService with BugDao.

class BugGeneratorServiceIntegrationTest {
    @Inject
    BugGeneratorService service;

    // tests...
}

More Examples

Besides what we have talked about, there are more things you can make use of.

// Applying Scopes to reuse the instance created at the first time 
bind(BugGeneratorService.class).to(RandomBugGeneratorService.class).asEagerSingleton();
bind(BugGeneratorService.class).to(RandomBugGeneratorService.class).in(Singleton.class);
// with @Singleton when doing @Provides
@Provides @Singleton
BugDao provideBugDao() {
    return NetworkBugDao();
}

Common Question

injecting constructor or fields?

Injecting constructor is the recommended way when you’re using final objects. Plus it is easy to write tests using this design.

Injecting fields is neither testable or safe to use for immutable field.

want a constructor with parameters from the caller

class RandomBugGeneratorService implements BugGeneratorService {
    // Constructor
    RandomBugGeneratorService(BugDao bugDao, Date date, int bugNumber) {
        // ...
    }
}

Solution #0: add a factory to create instance only with a method to create new instance with parameters from caller, while injecting from Guice when creating the factory.

interface BugGeneratorServiceFactory {
    create(Date date, int bugNumber);
}

class RandomBugGeneratorServiceFactory implements BugGeneratorServiceFactory {
    BugDaoProvider bugDaoProvider;

    @Inject
    RandomBugGeneratorServiceFactory(BugDaoProvider bugDaoProvider) {
        this.bugDaoProvider = bugDaoProvider;
    }

    create(Date date, int bugNumber) {
        return RandomBugGeneratorService(bugDaoProvider.get(), date, bugNumber);
    }
}

and bind BugGeneratorServiceFactory to RandomBugGeneratorServiceFactory.

Solution #1: AssistedInject

we still need factories to handle parameters from Guice and caller separately, but use @Assisted to avoid manually writing RandomBugGeneratorServiceFactory class in solution#1. It will link parameters with @Assisted to the create() method in factory, and bind the base factory(BugGeneratorServiceFactory) to the provider generated by Guice.

class RandomBugGeneratorService implements BugGeneratorService {
    @Inject
    RandomBugGeneratorService(BugDao bugDao, @Assisted Date date, @Assisted int bugNumber) {
        // ...
    }
}
install(new FactoryModuleBuilder()
     .implement(BugGeneratorService.class, RandomBugGeneratorService.class)
     .build(BugGeneratorServiceFactory.class));