guice: @Inject Injector does not work with child injectors

The testcase below works, if the injector is created by:

Guice.createInjector(new ChildModule());

it fails if the injector is created by:

Guice.createInjector().createChildInjector(new ChildModule());

The error is:

com.google.inject.ConfigurationException: Guice configuration errors:

  1. Unable to create binding for guice.GuiceTest$IOne. It was already configured on one or more child injectors or private modules bound at guice.GuiceTest$ChildModule.configure(GuiceTest.java:39) If it was in a PrivateModule, did you forget to expose the binding? while locating guice.GuiceTest$IOne
package guice;

import static org.junit.Assert.assertNotNull;

import org.junit.Test;

import com.google.inject.AbstractModule;
import com.google.inject.Guice;
import com.google.inject.Inject;
import com.google.inject.Injector;
import com.google.inject.Provider;

public class GuiceTest {

  public interface IOne {
  }

  public static class One implements IOne {
  }

  public interface ITwo {
    IOne getOne();
  }

  public static class Two implements ITwo {
    @Inject Provider<Injector> provider;

    @Override
    public IOne getOne() {
      return provider.get().getInstance(IOne.class);
    }
  }

  public static class ChildModule extends AbstractModule {

    @Override
    protected void configure() {
      bind(IOne.class).to(One.class);
      bind(ITwo.class).to(Two.class);
    }

  }

  @Test
  public void thisTestWorks() {
    Injector injector = Guice.createInjector(new ChildModule());
    ITwo two = injector.getInstance(ITwo.class);
    assertNotNull(two.getOne());

  }

  @Test
  public void thisTestFails() {
    Injector injector = Guice.createInjector().createChildInjector(new ChildModule());
    ITwo two = injector.getInstance(ITwo.class);
    assertNotNull(two.getOne());
  }
}

About this issue

  • Original URL
  • State: closed
  • Created 8 years ago
  • Reactions: 6
  • Comments: 18 (6 by maintainers)

Commits related to this issue

Most upvoted comments

This may be a problem with the way injectors are injected into things from child injectors. Looks like it’s always giving you the parent injector.

@sameb, that does, indeed, seem to be the bug - instances obtained through child injectors are handed the parent injector in some (but not all) cases:

package com.example.guice;

import com.google.inject.AbstractModule;
import com.google.inject.Guice;
import com.google.inject.Inject;
import com.google.inject.Injector;
import com.google.inject.Key;
import com.google.inject.Provides;
import com.google.inject.name.Named;

import static com.google.inject.name.Names.named;

public class GuiceBug {
    interface IntContainer {
        int value();
    }

    static final class HashCodeContainer implements IntContainer {
        final int value;

        @Inject
        HashCodeContainer(final Injector injector) {
            this.value = injector.hashCode();
        }

        @Override
        public int value() {
            return value;
        }
    }

    static final class Foo {
        final int number;

        @Inject
        Foo(final Injector injector) {
            this.number = injector.hashCode();
        }
    }

    static final class M extends AbstractModule {
        @Override
        protected void configure() {
            //#1 gets the parent injector, bugged:
            bind(IntContainer.class).annotatedWith(named("1")).to(HashCodeContainer.class);

            //#2 gets the parent injector, bugged:
            bind(HashCodeContainer.class).annotatedWith(named("2")).to(HashCodeContainer.class);

            //#3 gets the child injector, which is correct...
            bind(Foo.class);
        }

        //#4 gets the child injector, which is correct...
        @Provides @Named("4") IntContainer provide(final Injector injector) {
            return new HashCodeContainer(injector);
        }
    }

    public static void main(String[] args) {
        final Injector parent = Guice.createInjector();
        final Injector child  = parent.createChildInjector(new M());

        final IntContainer four = child.getInstance(Key.get(IntContainer.class, named("1")));
        check(four.value() == child.hashCode(), "1");
        final HashCodeContainer five = child.getInstance(Key.get(HashCodeContainer.class, named("2")));
        check(five.value() == child.hashCode(), "2");
        final Foo foo = child.getInstance(Foo.class);
        check(foo.number == child.hashCode(), "3");
        final IntContainer six = child.getInstance(Key.get(IntContainer.class, named("4")));
        check(six.value() == child.hashCode(), "4");
    }

    static void check(final boolean pass, final String n) {
        if (pass) {
            System.out.printf("Scenario #%s: PASSED%n", n);
        } else {
            System.err.printf("Scenario #%s: FAILED%n", n);
        }
    }
}

Any chance this bug will get addressed?

So, the weird workaround that someone at my company seems to have figured out is that if you inject an injector along with something exclusively from the parent injector scope, the right injector gets injected.

heres an SSCCE:

static class MyNestingType {

    Injector child = null;

    @Inject public MyNestingType(Injector injector){
        child = injector.createChildInjector();
    }
}

@Test
public void when_calling_createChildInjector_with_deps_that_resolve_child_injectors_should_get_child_not_sibling() {

    var granpaInjector = Guice.createInjector();
    var parentInjector = granpaInjector.createChildInjector();
    var instance = parentInjector.getInstance(MyNestingType.class);
    var childInjector = instance.child;

    assertThat(childInjector.getParent()).isEqualTo(parentInjector);
}