Skip to content

Best Practices

elandau edited this page May 11, 2015 · 9 revisions

This page provides examples and best practices guidelines to follow when writing DI friendly code

Best Practices

Modules should be side effect free

Don’t allocate data or have any conditional logic in a Guice module.

Don’t inject the Injector

Unless you are writing a generic framework most situations in which you want to Inject the injector can be solved by writing a Provider.

Be careful with AutoBindSingleton

When writing libraries keep in mind that classpath scanning of AutoBindSingleton classes will result in those classes being created eagerly when the injector is created. Too broad a set of a packages to scan can result in unwanted singletons being created.

@Singleton Provider is not @Singleton T

Annotating a Provider with @Singleton ensures that the Provider is a singleton and not the type T itself. Every injection or call to .get() will return a new instance of T. To make T a singleton either do bind(T.class).toProvider(TProvider.class).in(Scopes.Singleton) or implement SingletonProvider instead of Provider.

Examples

Configuration driven binding

The following example shows how to use multi bindings to create an extensible mechanism for constructing a polymorphic type. Note that new implementation types can be added simply by adding another binding to the map binder. This can be done in any Guice module

public class FooProvider implements Provider<Foo> {
   @Configuration(value="foo.type")
   private String type;

   private final Map<String, Provider<Foo>> providers;
  
   @Inject
   FooProvider(Map<String, Provider<Foo>> providers) {
       this.providers = providers;
   }

   public Foo get() {
      return providers.get(type).get();
   }
}

public class MyMainModule extends AbstractModule {
   protected void configure() {
       bind(Foo.class).toProvider(FooProvider.class).in(Scopes.SINGLETON);
   }

   // - or - 
   @Provides
   @Singleton
   public Foo getFoo(Config config, Map<String, Provider<Foo>> impls) {
       return impls.get(config.getString("foo.type")).get();
   }
}


public class FooModule1 extends AbstractModule { 
   public void configure() { 
      MapBinder.newMapBinder(binder(), String.class, Foo.class)
               .addBinding("foo1")
               .to(FooImpl1.class).in(Scopes.SINGLETON);
   }
}

public class FooModule2 extends AbstractModule { 
   public void configure() { 
      MapBinder.newMapBinder(binder(), String.class, Foo.class)
               .addBinding("foo2")
               .to(FooImpl2.class).in(Scopes.SINGLETON);
   }
}

Plugin architecture using multi-bindings

Coming soon..

Interfaces first

For injectable classes first create an interface and specify a default implementation using @ImplementedBy. This will make it easier to override the implementation in a binding. Note that binding in a guice module overrides @ImplementedBy without complaining about existing bindings.