google / guice

Guice (pronounced 'juice') is a lightweight dependency injection framework for Java 11 and above, brought to you by Google.
https://github.com/google/guice
Apache License 2.0
12.48k stars 1.67k forks source link

Misleading exception message for nested Guice injector failure #1761

Open toddliebenschutz-jones opened 1 year ago

toddliebenschutz-jones commented 1 year ago

When using nested Guice injectors, the exception message makes it look like any error in the inner injector is occurring in the outer injector.

Example 1

In this code, each test attempts to create a Guice injector that cannot be created.

These two failures are from two fundamentally different Guice injectors failing to start up (i.e. the nested injector, or the outer injector), however the exception message does not allow them to be distinguished.

package com.example;

import com.google.inject.AbstractModule;
import com.google.inject.Guice;
import com.google.inject.Inject;
import com.google.inject.Singleton;
import org.junit.jupiter.api.Test;

class DemoTest {

  static final class MyModule extends AbstractModule {

    @Override
    protected void configure() {
      bind(MyClass.class);
    }
  }

  @Singleton
  static final class MyClass {

    @Inject
    MyClass(MyInterface myInterface) {
    }
  }

  interface MyInterface {

  }

  @Test
  void createNonNestedModule() {
    Guice.createInjector(new MyModule());
  }

  @Test
  void createNestedModule() {
    Guice.createInjector(new ModuleWithNestedGuiceInjector());
  }

  static class ModuleWithNestedGuiceInjector extends AbstractModule {

    @Override
    protected void configure() {
      Guice.createInjector(new MyModule());
    }
  }
}
Non-nested exception message ``` Unable to create injector, see the following errors: 1) [Guice/MissingImplementation]: No implementation for DemoTest$MyInterface was bound. Requested by: 1 : DemoTest$MyClass.(DemoTest.java:23) \_ for 1st parameter myInterface at DemoTest$MyModule.configure(DemoTest.java:15) Learn more: https://github.com/google/guice/wiki/MISSING_IMPLEMENTATION 1 error ====================== Full classname legend: ====================== DemoTest$MyClass: "com.example.DemoTest$MyClass" DemoTest$MyInterface: "com.example.DemoTest$MyInterface" DemoTest$MyModule: "com.example.DemoTest$MyModule" ======================== End of classname legend: ======================== com.google.inject.CreationException: Unable to create injector, see the following errors: 1) [Guice/MissingImplementation]: No implementation for DemoTest$MyInterface was bound. Requested by: 1 : DemoTest$MyClass.(DemoTest.java:23) \_ for 1st parameter myInterface at DemoTest$MyModule.configure(DemoTest.java:15) Learn more: https://github.com/google/guice/wiki/MISSING_IMPLEMENTATION 1 error ====================== Full classname legend: ====================== DemoTest$MyClass: "com.example.DemoTest$MyClass" DemoTest$MyInterface: "com.example.DemoTest$MyInterface" DemoTest$MyModule: "com.example.DemoTest$MyModule" ======================== End of classname legend: ======================== at app//com.google.inject.internal.Errors.throwCreationExceptionIfErrorsExist(Errors.java:576) at app//com.google.inject.internal.InternalInjectorCreator.initializeStatically(InternalInjectorCreator.java:163) at app//com.google.inject.internal.InternalInjectorCreator.build(InternalInjectorCreator.java:110) at app//com.google.inject.Guice.createInjector(Guice.java:87) at app//com.google.inject.Guice.createInjector(Guice.java:69) at app//com.google.inject.Guice.createInjector(Guice.java:59) at app//com.example.DemoTest.createNonNestedModule(DemoTest.java:37) at java.base@17.0.8/java.lang.reflect.Method.invoke(Method.java:568) at java.base@17.0.8/java.util.ArrayList.forEach(ArrayList.java:1511) at java.base@17.0.8/java.util.ArrayList.forEach(ArrayList.java:1511) ```
Nested exception message ``` Unable to create injector, see the following errors: 1) [Guice/MissingImplementation]: No implementation for DemoTest$MyInterface was bound. Requested by: 1 : DemoTest$MyClass.(DemoTest.java:23) \_ for 1st parameter myInterface at DemoTest$MyModule.configure(DemoTest.java:15) Learn more: https://github.com/google/guice/wiki/MISSING_IMPLEMENTATION 1 error ====================== Full classname legend: ====================== DemoTest$MyClass: "com.example.DemoTest$MyClass" DemoTest$MyInterface: "com.example.DemoTest$MyInterface" DemoTest$MyModule: "com.example.DemoTest$MyModule" ======================== End of classname legend: ======================== com.google.inject.CreationException: Unable to create injector, see the following errors: 1) [Guice/MissingImplementation]: No implementation for DemoTest$MyInterface was bound. Requested by: 1 : DemoTest$MyClass.(DemoTest.java:23) \_ for 1st parameter myInterface at DemoTest$MyModule.configure(DemoTest.java:15) Learn more: https://github.com/google/guice/wiki/MISSING_IMPLEMENTATION 1 error ====================== Full classname legend: ====================== DemoTest$MyClass: "com.example.DemoTest$MyClass" DemoTest$MyInterface: "com.example.DemoTest$MyInterface" DemoTest$MyModule: "com.example.DemoTest$MyModule" ======================== End of classname legend: ======================== at app//com.google.inject.internal.Errors.throwCreationExceptionIfErrorsExist(Errors.java:576) at app//com.google.inject.internal.InternalInjectorCreator.initializeStatically(InternalInjectorCreator.java:163) at app//com.google.inject.internal.InternalInjectorCreator.build(InternalInjectorCreator.java:110) at app//com.google.inject.Guice.createInjector(Guice.java:87) at app//com.google.inject.Guice.createInjector(Guice.java:69) at app//com.google.inject.Guice.createInjector(Guice.java:59) at app//com.example.DemoTest.createNestedModule(DemoTest.java:42) at java.base@17.0.8/java.lang.reflect.Method.invoke(Method.java:568) at java.base@17.0.8/java.util.ArrayList.forEach(ArrayList.java:1511) at java.base@17.0.8/java.util.ArrayList.forEach(ArrayList.java:1511) ```

Example 2

The second example shows why this is confusing in practice. The exception message does not allow the user to work out which injector has the missing dependency. The exception messages for these two test are the same (apart from the test method in the stack trace).

package com.example;

import com.google.inject.AbstractModule;
import com.google.inject.Guice;
import com.google.inject.Inject;
import com.google.inject.Singleton;
import org.junit.jupiter.api.Test;

class DemoTest {

  static final class MyModule extends AbstractModule {

    private final boolean missingDependency;

    public MyModule(boolean missingDependency) {
      this.missingDependency = missingDependency;
    }

    @Override
    protected void configure() {
      if (missingDependency) {
        bind(MyClass.class);
      }
    }
  }

  @Singleton
  static final class MyClass {

    @Inject
    MyClass(MyInterface myInterface) {
    }
  }

  interface MyInterface {

  }

  @Test
  void createInjectorWithMissingDependencyInNestedInjector() {
    Guice.createInjector(new MyModule(true),
        new ModuleWithNestedGuiceInjector(false));
  }

  @Test
  void createInjectorWithMissingDependency() {
    Guice.createInjector(new ModuleWithNestedGuiceInjector(true),
        new MyModule(false));
  }

  static class ModuleWithNestedGuiceInjector extends AbstractModule {

    private final boolean missingDependency;

    ModuleWithNestedGuiceInjector(boolean missingDependency) {
      this.missingDependency = missingDependency;
    }

    @Override
    protected void configure() {
      Guice.createInjector(new MyModule(missingDependency));
    }
  }
}
Exception message ``` Unable to create injector, see the following errors: 1) [Guice/MissingImplementation]: No implementation for DemoTest$MyInterface was bound. Requested by: 1 : DemoTest$MyClass.(DemoTest.java:31) \_ for 1st parameter myInterface at DemoTest$MyModule.configure(DemoTest.java:22) Learn more: https://github.com/google/guice/wiki/MISSING_IMPLEMENTATION 1 error ====================== Full classname legend: ====================== DemoTest$MyClass: "com.example.DemoTest$MyClass" DemoTest$MyInterface: "com.example.DemoTest$MyInterface" DemoTest$MyModule: "com.example.DemoTest$MyModule" ======================== End of classname legend: ======================== com.google.inject.CreationException: Unable to create injector, see the following errors: 1) [Guice/MissingImplementation]: No implementation for DemoTest$MyInterface was bound. Requested by: 1 : DemoTest$MyClass.(DemoTest.java:31) \_ for 1st parameter myInterface at DemoTest$MyModule.configure(DemoTest.java:22) Learn more: https://github.com/google/guice/wiki/MISSING_IMPLEMENTATION 1 error ====================== Full classname legend: ====================== DemoTest$MyClass: "com.example.DemoTest$MyClass" DemoTest$MyInterface: "com.example.DemoTest$MyInterface" DemoTest$MyModule: "com.example.DemoTest$MyModule" ======================== End of classname legend: ======================== at app//com.google.inject.internal.Errors.throwCreationExceptionIfErrorsExist(Errors.java:576) at app//com.google.inject.internal.InternalInjectorCreator.initializeStatically(InternalInjectorCreator.java:163) at app//com.google.inject.internal.InternalInjectorCreator.build(InternalInjectorCreator.java:110) at app//com.google.inject.Guice.createInjector(Guice.java:87) at app//com.google.inject.Guice.createInjector(Guice.java:69) at app//com.google.inject.Guice.createInjector(Guice.java:59) at app//com.example.DemoTest.createNonNestedModule(DemoTest.java:41) at java.base@17.0.8/java.lang.reflect.Method.invoke(Method.java:568) at java.base@17.0.8/java.util.ArrayList.forEach(ArrayList.java:1511) at java.base@17.0.8/java.util.ArrayList.forEach(ArrayList.java:1511) ```

Other notes

lobaorn commented 4 months ago

Hey @toddliebenschutz-jones , letting you know since you may be targeting Guice 7.0.0, about the new Micronaut Guice project, that depending on your usecase could be a useful replacement: https://github.com/micronaut-projects/micronaut-guice/issues/9