Search code examples
javadependency-injectionguice

Guice: Inject a class bound by another module?


I have the following classes:

public class CacheModule extends AbstractModule {
    @Override
    protected void configure() {
        bindConstant().annotatedWith(Names.named(TIMEOUT")).to(60);
        // ...etc.
    }
}

public class DefaultCacheAdaptor implements CacheAdaptor {
    private CacheModule bootstrapper = new CacheModule();

    @Named("TIMEOUT") private int timeout;

    // other fields omitted for brevity

    public DefaultCacheAdaptor() {
        super();

        Injector injector = Guice.createInjector(bootstrapper);

        @Named("TIMEOUT") int t = injector.getInstance(Integer.class);
        setTimeout(t);
    }
}

public class QueueModule extennds AbstractModule {
    @Override
    public void configure() {
        bind(CacheAdaptor.class).to(DefaultCacheAdaptor.class);
    }
}

public class DefaultQueueAdaptor implements QueueAdaptor {
    private QueueModule bootstrapper = new QueueModule();

    private CacheAdaptor cacheAdaptor;

    public DefaultQueueAdaptor() {
        super();

        Injector injector = Guice.createInjector(bootstrapper);

        setCacheAdaptor(injector.getInstance(CacheAdaptor.class));
    }
}

The CacheModule/CacheAdaptor/DefaultCacheAdaptor is located in a different JAR than QueueModule/QueueAdaptor/DefaultQueueAdaptor, and so the latter JAR depends on the former JAR at runtime (obviously).

The purpose of coding things this way is to allow the CacheModule to boostrap/inject the entire object graph under DefaultCacheAdaptor when the user writes:

CacheAdaptor cacheAdaptor = new DefaultCacheAdaptor();

Ditto for the QueueAdaptor.

It just so happens to be that the QueueAdaptor gets injected with a CacheAdaptor.

However, DefaultCacheAdaptor is the "root" of its own object tree, and should always be injected by the CacheModule.

So I ask: how can I bind DefaultCacheAdaptor to CacheAdaptor from inside QueueModule, but ensure that the DefaultCacheAdaptor is itself initialized/bootstrapped by the CacheModule?


Solution

  • To be honest it sounds like this problem isn't actually one of Guice, but instead the standard problem across software engineering: ensuring your dependencies do what they claim to do. QueueModule shouldn't concern itself about whether DefaultCacheAdaptor holds up to its general contract. Instead, write a unit test for DefaultCacheAdaptor that guarantees that it bootstraps itself, then use it in QueueModule without a second thought.

    This is especially true because DefaultCacheAdaptor has a completely unrelated injector tree. You should be able to use DefaultCacheAdaptor opaquely, and stop concerning QueueAdaptor with its implementation details. Its bootstrapping is part of its implementation, not its API.

    Even if you were to merge the two Injector graphs into one (by injecting the Injector and calling createChildInjector, for instance), there's very little way to guarantee at compile time that bindings you need in other modules will exist, because Modules work at runtime. Your best bet is to write a unit test. You can fail a little faster by calling requireBinding, which will fail at Injector creation if that particular dependency doesn't end up satisfied externally.