javaguice

How to use java guice when an injectable has its own dependencies


I'm trying to figure out how Guice works and is struggling to find examples/tutorials for the specific scenarios I have.

Lets say I have the following classes:

The problem I'm having is that ClassA does not have a no-arg constructor and also have its own dependencies. I'm not sure how to inject these type of dependencies using an Injector.

ClassA

public class ClassA {

    private final int number;
    private final ClassB classB;

    @Inject
    public ClassA(int number, ClassB classB) {
        this.number = number;
        this.classB = classB;
    }

    public String message() {
        return "ClassA number: " + number + ", ClassB number: " + classB.getNumber();
    }

}

ClassB

@Getter
public class ClassB {

    private final int number;

    @Inject
    public ClassB(@Assisted int number) {
        this.number = number;
    }

}

ClassC

import com.google.inject.Inject;
import com.google.inject.assistedinject.Assisted;

public class ClassC {

    final private int number;
    final private ClassA classA;

    @Inject
    public ClassC(@Assisted int number, ClassA classA) {
        this.number = number;
        this.classA = classA;
    }

    public String message() {
        String message = classA.messsage();
        return message + ", ClassC number: " + number;
    }

}

I have a factory interfaces for all classes with a create(int number) method. In the module configure method I have the following:

install(new FactoryModuleBuilder().build(ClassAFactory.class));
install(new FactoryModuleBuilder().build(ClassBFactory.class));
install(new FactoryModuleBuilder().build(ClassCFactory.class));

The following is where I'm totally lost, how do I mix this all together?

Injector injector = Guice.createInjector(new AppModule());

ClassBFactory bFactory = injector.getInstance(ClassBFactory.class);
ClassB b = bFactory.create(2);
ClassAFactory aFactory = injector.getInstance(ClassAFactory.class);
ClassA a = aFactory.create(1);
ClassCFactory cFactory = injector.getInstance(ClassCFactory.class);
ClassC c = cFactory.create(3);

System.out.println(c.message());

Output for above:

ClassA number: 3, ClassB number: 3, ClassC number: 3

Bonus for me, if its possible to use providers instead of factories for this scenario please can someone show me an example of how to resolve these dependencies in a provider?


Solution

  • This is actually a feature, believe it or not. It's creating corresponding instances with the same value.

    ClassBFactory bFactory = injector.getInstance(ClassBFactory.class);
    ClassB b = bFactory.create(2);
    

    This creates a new ClassB instance with number of 2.

    ClassAFactory aFactory = injector.getInstance(ClassAFactory.class);
    ClassA a = aFactory.create(1);
    

    The constructor for ClassA takes number and an instance of ClassB. However, the instance of ClassB you've created above is not bound and isn't available to the Injector. Since ClassB can be created using assisted injection, though, it's able to creates one. It does and injects that into the instance of ClassA. In this case, both ClassA and ClassB instances have number of 1.

    ClassCFactory cFactory = injector.getInstance(ClassCFactory.class);
    ClassC c = cFactory.create(3);
    

    As above, this creates new ClassB and ClassA instances for this new ClassC, each with number of 3.

    If you want to address this, you need to change your factories to take the right dependencies and make those dependencies @Assisted.

    ClassBFactory bFactory = injector.getInstance(ClassBFactory.class);
    ClassB b = bFactory.create(2);
    ClassAFactory aFactory = injector.getInstance(ClassAFactory.class);
    ClassA a = aFactory.create(1, b); // <--
    ClassCFactory cFactory = injector.getInstance(ClassCFactory.class);
    ClassC c = cFactory.create(3, a); // <--
    

    If you're only going to have one instance of each of these classes, this is better done in a Module as @Provides methods rather than using assisted injection and factories:

    @Provides
    ClassA provideClassA(ClassB b) {
      return new ClassA(1, b);
    }
    
    @Provides
    ClassB provideClassB() {
      return new ClassB(2);
    }
    
    @Provides
    ClassC provideClassC(ClassA a) {
      return new ClassC(3, a);
    }