2
votes

I'm trying to learn Guice, but the following scenario seems hard. Let's say I have classes A,B and C. I should be able to do the following (Note that this example is somewhat simplified to the actual case):

  1. A is global singleton
  2. A has dependency on ProviderB (i.e. factory)
  3. B has dependency on A and ProviderC (i.e. factory)
  4. C has dependency on A and B

When B creates C, the dependency B of C must be the same instance, i.e. from C's point of view, the B is singleton.

I have tried creating child injectors:

private static class MainModule extends AbstractModule{

    @Override
    protected void configure() {
        bind(A.class).in(Singleton.class);
    }

    @Provides
    B createB(Injector injector){
        return injector.createChildInjector(new SubModule()).getInstance(B.class);
    }
}

private static class SubModule extends AbstractModule{

    @Override
    protected void configure() {
        bind(B.class).in(Singleton.class);
        bind(C.class);
    }
}

public static void main(String[] args) {
    Injector injector = Guice.createInjector(new MainModule());

    A a = injector.getInstance(A.class);

    B b1 = a.getB();
    B b2 = a.getB();

    // all following C's are different instances
    C b1c1 = b1.getC(); //this has b1 and a
    C b1c2 = b1.getC(); //this has b1 and a

    C b2c1 = b2.getC(); //this has b2 and a
    C b2c2 = b2.getC(); //this has b2 and a

}

But then Guice gives errors that B is already bound (The Provides method in MainModule). Therefore I would need to override the B binding of MainModule, but this seems to be impossible using child injectors.

The problem is solvable by using multiple injectors, e.g. create new one for createB method and inject A as parameter, but it seems that using multiple injectors is not best practice.

EDIT: Added comments to C instances to clarify which instance of B they should have

1
Is there a reason that MainModule and SubModule can't be installed into the initial injector at the same time, or that you can't annotate @Provides createB(...) with @Singleton? - Jeff Bowman
Hmm maybe I was a bit unclear, is it more clear if I say that for every dependency of B, the B is considered to be singleton, but elsewhere there can be multiple B's, i.e. everytime A.getB is called, it returns a new instance. If I annotate createB with @Singleton I guess then B would be considered singleton everywhere. - Bjarne Boström
I could also note that the parent injector so far has only singleton bindings (A here, more in actual application), so I could create the SubModule with the parent injector, inject every singleton, bind those again as singletons and create completely new top-level injector before making B with it, but I would like to avoid writing those again. - Bjarne Boström
You might want to look into assisted injection. So instead of B having a Provider<C>, it would have a CFactory and could call it like factory.create(this). - Tavian Barnes
Might your question be related to "Dependency injection: Scoping by region"? - Jeff Bowman

1 Answers

1
votes

I'll post this as an answer, it's more like workaround than full solution, but probably good enough for my application anyway:

private static class MainModule extends AbstractModule{

    @Override
    protected void configure() {
        bind(A.class).in(Singleton.class);
        bind(SubModule.class).in(Singleton.class);
    }

    @Provides
    B createB(Injector injector){
        SubModule m = injector.getInstance(SubModule.class);
        return Guice.createInjector(m).getInstance(B.class);
    }
}

private static class SubModule extends AbstractModule{

    private final A a;

    @Inject
    public SubModule(A a) {
        this.a = a;
    }

    @Override
    protected void configure() {
        bind(A.class).toInstance(a);
        bind(B.class).in(Singleton.class);
        bind(C.class);
    }
}

First I tought that most of the bindings would be in the MainModule, but I guess they can be moved to the SubModule anyway if they are only used in the context of B (and route others like I did with A). This is mostly similar to the answer of linked question: Dependency injection: Scoping by region (Guice, Spring, Whatever) but I create new top-level injector because it seems I can't bind B in both modules, or I might miss something here..

(First time doing this, not sure if this is correct way to do post a work-around as own answer)