Search code examples
dependency-injectionguice

Use Guice nested injects


I'm relatively new to Guice, and some things still give me a pretty hard time. My particular question is, how do you handle nested injections in Guice. Example:

Class A uses Class B via @Inject, and Class B uses Class C.

Explicitly:

My Module where I bind to Providers.

public class ModuleBinder extends AbstractModule {

    @Override
    protected void configure() {
        bind(DatabaseControllerInterface.class)
        .toProvider(DatabaseControllerProvider.class).asEagerSingleton();

        bind(AnalyzerInterface.class)
        .toProvider(AnalyzerProvider.class).asEagerSingleton();

        bind(SystemAdministrationInterface.class)
        .toProvider(SystemAdministrationProvider.class).asEagerSingleton();

        bind(LogInServiceInterface.class)
        .toProvider(LogInServiceProvider.class);
    }
}

The DatabaseControllerProvider:

public class DatabaseControllerProvider implements Provider<DatabaseControllerInterface> {

    @Override
    public DatabaseControllerInterface get() {
        return new DatabaseControllerImpl();
    }
}

The LogInServiceProvider:

public class LogInServiceProvider implements Provider<LogInServiceInterface> {

    @Override
    public LogInServiceInterface get() {
        return new LogInServiceImpl();
    }
}

And finally, the LogInService uses:

public class LogInServiceImpl implements LogInServiceInterface{

    @Inject
    private DatabaseControllerProvider databaseControllerProvider;

    private final DatabaseControllerInterface databaseController;


    public LogInServiceImpl() {
         this.databaseController = databaseControllerProvider.get();
    }

    @Override
    public User register(final String mail, final String userName, final String password) {
        databaseController.registerUser(userName, mail, password, UserRole.ADMIN);
    }

}

The call is then:

public class Test() {
     public static test() {
         final Injector injector = Guice.createInjector(new ModuleBinder());
         logInService = injector.getInstance(LogInServiceInterface.class);
         logInService.registerUser("test", "test", "test");
      }
}

I know most of you guys will get sick with that code, but hey, I'm a beginner with Guice, so please be gentle with me.

I want to use Constructor injection, I already realized that field injection is considered "evil". Do you have any idea how to get that working by keeping the providers (I need them)?

Using the injections in the example does nothing on the "second" level, the DatabaseControllerImpl in LogInServiceImpl is null.

Did I configure something wrong? Did I misunderstand the usage of Provides and/or Modules?

I hope somebody can and wants to help me. If you need more informations, post a comment.

With best regards,

JosefRucksack


Solution

  • Your direct answer: You're calling new T(); in your Providers, which doesn't support field injection.

    First, a real timesaver: Don't keep your explicit Providers. If you have bound a T, Guice allows you to inject a Provider or call Injector.getProvider for that T, even if you haven't explicitly created a Provider yourself. See the Built-In Bindings page on the wiki, or the Injector docs (emphasis mine):

    Contains several default bindings:

    • This Injector instance itself
    • A Provider<T> for each binding of type T
    • The Logger for the class being injected
    • The Stage in which the Injector was created

    Instead, do it this way:

    public class ModuleBinder extends AbstractModule {
    
        @Override
        protected void configure() {
            bind(DatabaseControllerInterface.class)
                .to(DatabaseControllerImpl.class).asEagerSingleton();
            bind(AnalyzerInterface.class)
                .to(AnalyzerImpl.class).asEagerSingleton();
            bind(SystemAdministrationInterface.class)
                .to(SystemAdministrationImpl.class).asEagerSingleton();
            bind(LogInServiceInterface.class)
                .to(LogInServiceImpl.class);
        }
    }
    

    You then have the same choice you do now, to inject T or Provider<T> and call getInstance or getProvider as needed.


    If your Providers are absolutely necessary, especially if they actually receive an instance from some other system or service locator, one other option is to add your @Inject fields into them as in the Provider bindings wiki page and pass them into your constructor, or to just inject a MembersInjector<T>:

    public class LogInServiceProvider implements Provider<LogInServiceInterface> {
        @Inject MembersInjector<LogInServiceImpl> logInServiceImplInjector;
    
        @Override
        public LogInServiceInterface get() {
            LogInServiceImpl logInServiceImpl = YourExternalDep.getLogInService();
            logInServiceImplInjector.injectMembers(logInServiceImpl);
            return logInServiceImpl;
        }
    }
    

    However, this explicit-Provider solution is not idiomatic Guice, and should only be used with external or legacy code. Guice's whole reason for existence is to automate away boilerplate and let your systems come together clearly and flexibly. Providers are an implementation detail; let Guice create them for you.