Bjarne Boström
Bjarne Boström

Reputation: 227

Guice and "scoped" singletons

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

Upvotes: 2

Views: 1306

Answers (1)

Bjarne Boström
Bjarne Boström

Reputation: 227

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)

Upvotes: 1

Related Questions