Guice: @Inject Injector no funciona con inyectores infantiles

Creado en 4 ene. 2016  ·  17Comentarios  ·  Fuente: google/guice

El caso de prueba a continuación funciona, si el inyector es creado por:

Guice.createInjector(new ChildModule());

falla si el inyector es creado por:

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

el error es:

com.google.inject.ConfigurationException: errores de configuración de Guice:

1) No se puede crear un enlace para guice.GuiceTest$IOne. Ya estaba configurado en uno o más inyectores niño o módulos privados
enlazado en guice.GuiceTest$ChildModule.configure(GuiceTest.java:39)
Si estaba en un PrivateModule, ¿olvidó exponer el enlace?
mientras localiza 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 {
    <strong i="17">@Inject</strong> Provider<Injector> provider;

    <strong i="18">@Override</strong>
    public IOne getOne() {
      return provider.get().getInstance(IOne.class);
    }
  }

  public static class ChildModule extends AbstractModule {

    <strong i="19">@Override</strong>
    protected void configure() {
      bind(IOne.class).to(One.class);
      bind(ITwo.class).to(Two.class);
    }

  }

  <strong i="20">@Test</strong>
  public void thisTestWorks() {
    Injector injector = Guice.createInjector(new ChildModule());
    ITwo two = injector.getInstance(ITwo.class);
    assertNotNull(two.getOne());

  }

  <strong i="21">@Test</strong>
  public void thisTestFails() {
    Injector injector = Guice.createInjector().createChildInjector(new ChildModule());
    ITwo two = injector.getInstance(ITwo.class);
    assertNotNull(two.getOne());
  }
}

Comentario más útil

Esto puede ser un problema con la forma en que los inyectores se inyectan en las cosas de los niños que se inyectan. Parece que siempre te da el inyector principal.

@sameb , de hecho, ese parece ser el error: las instancias obtenidas a través de inyectores secundarios se entregan al inyector principal en algunos casos (pero no en todos):

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;

        <strong i="10">@Inject</strong>
        HashCodeContainer(final Injector injector) {
            this.value = injector.hashCode();
        }

        <strong i="11">@Override</strong>
        public int value() {
            return value;
        }
    }

    static final class Foo {
        final int number;

        <strong i="12">@Inject</strong>
        Foo(final Injector injector) {
            this.number = injector.hashCode();
        }
    }

    static final class M extends AbstractModule {
        <strong i="13">@Override</strong>
        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...
        <strong i="14">@Provides</strong> @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);
        }
    }
}

¿Hay alguna posibilidad de que este error se resuelva?

Todos 17 comentarios

'Dos' se vincula en el inyector principal porque ChildModule no tiene una instrucción bind(Two.class) separada. 'Dos' también está inyectando el inyector (una idea generalmente mala), por lo que termina inyectando el inyector principal. Más tarde, al llamar a two.getOne, está tratando de recuperar 'IOne' del inyector principal, pero estaba vinculado en el inyector secundario, por lo que obtiene ese error.

Tienes algunas formas de solucionar el problema:

1) Agregue binder().requireExplicitBindings() para obligar a los enlaces a vivir en el inyector que los creó.
2) Agregue declaraciones explícitas bind(One.class) y bind(Two.class) para obligar a los enlaces a vivir en el inyector secundario.
3) Deje de inyectar el inyector y en su lugar inyecte el Proveedordirectamente, lo que haría que fallara más rápido (en createChildInjector) en lugar de más tarde durante two.getOne().

¡Gracias por tu respuesta superrápida! Cambié el ChildModule a

  public static class ChildModule extends AbstractModule {

    <strong i="6">@Override</strong>
    protected void configure() {
      binder().requireExplicitBindings();
      bind(One.class);
      bind(Two.class);
      bind(IOne.class).to(One.class);
      bind(ITwo.class).to(Two.class);
    }

  }

pero el error es el mismo.

Por cierto, no puedo inyectar un proveedor en mi código porque es demasiado genérico. Se parece a esto

Object createInstance(java.lang.Class<?> type) {
  Object instance = createObjectFromEclipseModelingFrameWork(type);
  injector.injectMembers(instance);
  return instance;
}

¿Puedes pegar el seguimiento completo de la pila del error?

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:41)
  If it was in a PrivateModule, did you forget to expose the binding?
  while locating guice.GuiceTest$IOne

1 error
    at com.google.inject.internal.InjectorImpl.getProvider(InjectorImpl.java:1042)
    at com.google.inject.internal.InjectorImpl.getProvider(InjectorImpl.java:1001)
    at com.google.inject.internal.InjectorImpl.getInstance(InjectorImpl.java:1051)
    at guice.GuiceTest$Two.getOne(GuiceTest.java:30)
    at guice.GuiceTest.thisTestFails(GuiceTest.java:50)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:606)
    at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50)
    at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
    at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)
    at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
    at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325)
    at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:78)
    at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:57)
    at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
    at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
    at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
    at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
    at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
    at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
    at org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:86)
    at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:38)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:459)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:675)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:382)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:192)

Esto puede ser un problema con la forma en que los inyectores se inyectan en las cosas de los niños que se inyectan. Parece que siempre te da el inyector principal.

Si inyecta Injector directamente en lugar de indirectamente a través de Provider , entonces funciona.

En otras palabras cambiar:

  public static class Two implements ITwo {
    <strong i="9">@Inject</strong> Provider<Injector> provider;

    <strong i="10">@Override</strong>
    public IOne getOne() {
      return provider.get().getInstance(IOne.class);
    }
  }

para:

  public static class Two implements ITwo {
    <strong i="14">@Inject</strong> Injector injector;

    <strong i="15">@Override</strong>
    public IOne getOne() {
      return injector.getInstance(IOne.class);
    }
  }

No hay ninguna ventaja en usar Provider<Injector> aquí, ya que Injector ya existe en el momento de la inyección; por lo general, usa Provider cuando necesita diferir la creación o romper un ciclo de dependencia.

Cuando uso el Inyector sin Proveedor, entonces, en nuestro código de producción, recibo la advertencia:

AssistedInject factory ... será lento porque la clase ... ha asistido a las dependencias del proveedor o inyecta el inyector.

Cambiar el código de Injector a Provider<Injector> realmente aceleró las cosas aquí.

Ohhh, si no recibe la advertencia de un proveedor, entonces es probable que sea un error malo. Debería estar buscando eso y advertir también.

Bueno, técnicamente supongo que debido al error que señaló mculls, no hay problema de que no estemos advirtiendo al proveedor.. Sin embargo, si el proveedorestá arreglado para inyectar el inyector secundario, entonces tendríamos que agregar la advertencia.

Esto puede ser un problema con la forma en que los inyectores se inyectan en las cosas de los niños que se inyectan. Parece que siempre te da el inyector principal.

@sameb , de hecho, ese parece ser el error: las instancias obtenidas a través de inyectores secundarios se entregan al inyector principal en algunos casos (pero no en todos):

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;

        <strong i="10">@Inject</strong>
        HashCodeContainer(final Injector injector) {
            this.value = injector.hashCode();
        }

        <strong i="11">@Override</strong>
        public int value() {
            return value;
        }
    }

    static final class Foo {
        final int number;

        <strong i="12">@Inject</strong>
        Foo(final Injector injector) {
            this.number = injector.hashCode();
        }
    }

    static final class M extends AbstractModule {
        <strong i="13">@Override</strong>
        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...
        <strong i="14">@Provides</strong> @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);
        }
    }
}

¿Hay alguna posibilidad de que este error se resuelva?

Entonces, la solución extraña que alguien en mi compañía parece haber descubierto es que si inyecta un inyector junto con algo exclusivamente del osciloscopio del inyector principal, se inyecta el inyector correcto.

No estoy seguro de si este problema está relacionado con InjectiongTheInjector
En la Guía del usuario, dijo que no inyecte el inyector, porque inyectar el inyector hace imposible que Guice sepa con anticipación que su Gráfico de dependencia está completo,

En el caso de prueba anterior de @avoss , Class Two usa un proveedor de inyector, que casi equivale a inyectar el inyector

Esto puede ser un problema con la forma en que los inyectores se inyectan en las cosas de los niños que se inyectan. Parece que siempre te da el inyector principal.

@sameb , de hecho, ese parece ser el error: las instancias obtenidas a través de inyectores secundarios se entregan al inyector principal en _algunos_ (pero no en todos) los casos:

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;

        <strong i="10">@Inject</strong>
        HashCodeContainer(final Injector injector) {
            this.value = injector.hashCode();
        }

        <strong i="11">@Override</strong>
        public int value() {
            return value;
        }
    }

    static final class Foo {
        final int number;

        <strong i="12">@Inject</strong>
        Foo(final Injector injector) {
            this.number = injector.hashCode();
        }
    }

    static final class M extends AbstractModule {
        <strong i="13">@Override</strong>
        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...
        <strong i="14">@Provides</strong> @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);
        }
    }
}

¿Hay alguna posibilidad de que este error se resuelva?

   ` final Injector parent = Guice.createInjector(new M());`

Usar el inyector principal sin el inyector secundario funcionó para mí. ¡Pruébalo!

En el escenario de casos de prueba, guice crea recursivamente el enlace justo a tiempo usando parent.createJustInTimeBindingRecursive al principio, esta puede ser la razón por la cual el inyector inyectado es el inyector principal.

Simplemente verifique si el inyector secundario tiene un enlace de tecla vinculado correcto antes de usar el inyector principal, no estoy seguro de si esta es la forma correcta de resolver este error.

Tampoco sé por qué el travis CI no pasó porque no se encontró http 404.

Acabo de pasar varias horas rastreando esto.

editar: en realidad ya me he encontrado con este problema, por lo que ha preguntado y respondido:
https://stackoverflow.com/questions/19461065/run-time-injection-how-do-i-get-the-most-childish-injector-with-guice

Aquí está mi árbol de dependencia y caso de uso:

CommandLineReader [Concrete]: 
  [no deps]

ApplicationRoot [Concrete]:
  Dep1 [Concrete]:
    SharedServiceInterface [Interface via Impl1]
    Common [Concrete]
  Dep2:
    SharedServiceInterface [Interface via Impl2]
    Common [Concrete]

atado como:

class ApplicationRoot{
  ApplicationRoot(Dep1 dep1, Dep2 dep2) { ... }
}
class Dep1{
  private final SharedServiceInterface svc;

  Dep1(Injector injector){
    var child = injector.createChildInjector(binder -> binder.bind(SharedServiceInterface.class).to(Impl1.class))

    svc = child.getInstance(SharedServiceInterface.class)
  }
}
class Dep2{
  private final SharedServiceInterface svc;

  Dep2(Injector injector){
    var child = injector.createChildInjector(binder -> binder.bind(SharedServiceInterface.class).to(Impl2.class))

    svc = child.getInstance(SharedServiceInterface.class)
  }
}

public static void main(string[] args){
  var rootsParent = Guice.createInjector(...);

  rootsParent.getInstance(CommandLineReader.class).handle(args)

  var actualRoot = rootsParent.createChildInjector(binder -> binder.bind(Common.class).asAppropriate())

  actualRoot.getInstance(ApplicationRoot.class) //ERROR: "Common" not bound.
  // stack trace points here,
  // but adding some catch blocks has the stack-trace point to the createChildInjector lines
  // the cause: the 'parentInjector' instance passed into Dep1's ctor is the same as "rootsParent" here.
}

Si hay una mejor manera de hacer esto con "solo usar proveedores", hágamelo saber. Esto está en guice 4.2.2

aquí hay un SSCCE:

static class MyNestingType {

    Injector child = null;

    <strong i="6">@Inject</strong> public MyNestingType(Injector injector){
        child = injector.createChildInjector();
    }
}

<strong i="7">@Test</strong>
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);
}
¿Fue útil esta página
0 / 5 - 0 calificaciones

Temas relacionados

dxiao picture dxiao  ·  4Comentarios

felixblaschke picture felixblaschke  ·  51Comentarios

nathanmerrill picture nathanmerrill  ·  5Comentarios

afghl picture afghl  ·  5Comentarios

gissuebot picture gissuebot  ·  3Comentarios