Injecting a composite with Guice

243 views Asked by At

Suppose I have an interface Foo. It is implemented by concrete implementations CompositeFoo, FooA, FooB, and FooC. Moreover, CompositeFoo is as follows:

public class CompositeFoo implements Foo {
    @Inject public CompositeFoo(List<? extends Foo> elements);
}

I wish to, in a Guice PrivateModule, bind Foo to a CompositeFoo with the list being a FooA followed by something that is either a FooB or a FooC. (This must be a list, as order matters; this rules out multibindings as a solution.)

The problem is that I am seeing some circularity involved. Suppose the provider of the CompositeFoo is as follows:

public class CompositeFooProvider implements Provider<Foo> {
    @Inject private FooA first;
    @Inject @Named("Inner") private Foo second;

    @Override public Foo get() { return new CompositeFoo(asList(first, second)); }
}

The module providing the second Foo (either FooB or FooC) is as follows:

public class InnerModule extends PrivateModule {
    private final Key<? super Foo> bindingKey;  // key will be exposed, bound to the Foo below

    // configure() deals with deps of FooB and FooC

    @Provides
    public Foo getInnerFoo(...) {
        // Assume that the args are such that if they are "valid", we should return a FooB, else FooC
        if (...) return new FooB(...);
        else return new FooC(...);
    }
}

The circularity arises when I try to construct the outer module: I need to install the InnerModule (passing in Key.get(Foo.class, Names.named("Inner")) as the binding key) in order to get the second Foo, but Foo is already bound in the outer module due to its binding to the CompositeFooProvider. How do I resolve this circularity? Will converting the @Provides method to its own Provider suffice?

1

There are 1 answers

0
Tavian Barnes On BEST ANSWER

The @Provides Foo method is providing a binding for Foo which conflicts with the Foo binding in the outer module. So bind it as something else:

public class InnerModule extends PrivateModule {
    private final Key<Foo> bindingKey;  // key will be exposed, bound to the @Inner Foo below

    @BindingAnnotation
    @Target({ FIELD, PARAMETER, METHOD })
    @Retention(RUNTIME)
    @interface Inner {
    }

    @Override
    protected void configure() {
        bind(bindingKey).to(Key.get(Foo.class, Inner.class));
        expose(bindingKey);
    }

    @Provides
    @Inner Foo getInnerFoo(...) {
        // Assume that the args are such that if they are "valid", we should return a FooB, else FooC
        if (...) return new FooB(...);
        else return new FooC(...);
    }
}

Or perhaps you can just do

    @Provides
    @Exposed
    @Named("Inner") Foo getInnerFoo(...) {
        // Assume that the args are such that if they are "valid", we should return a FooB, else FooC
        if (...) return new FooB(...);
        else return new FooC(...);
    }

directly and not bother passing in the binding key.