Yes, we are mostly used to Spring for our dependency injection needs. Truth is, Spring’s IoC components works beautifully! However we have other options besides Pivotal’s beloved framework. Guice quickly comes to mind. Read on to take your first steps with Guice!
Google has been using Guice on their applications for more than a dozen years. Strangely enough I never had the pleasure of working with the framework. So, to prepare for a forthcoming mission, I decided to take it for a test drive and write a post about it.
Bear in mind that I’m just beginning with Guice, so I’m probably not grasping all of the tool’s subtleties. Feel free to contribute in the comments below!
And with that said…
First steps with Guice
Guice provides an Injector, which equates to a Spring IoC container.
In the same way we provide bean configurations to a Spring’s context, in Guice we define bindings inside “modules“. We feed those modules to the injector so that it can serve instances (with their dependencies) as required.
In Spring we could create a context and retrieve beans like this:
ApplicationContext context = new ClassPathXmlApplicationContext("myBeans.xml", "myOtherBeans.xml"); Service service = context.getBean("mySupaService", Service.class);
With Guice, we proceed in a very similar fashion!
Injector injector = Guice.createInjector(new Module()); Service service = injector.getInstance(Service.class);
Of course for that to work we need to define our modules. We do that by writing long and unreadable XML files… Nah, just kiddin’! We can simply extend the AbstractModule class and override a configure() method.
Linked Bindings
The easiest approach, linked bindings, maps a type (usually an interface) to its implementation:
class Module extends AbstractModule { @Override protected void configure() { bind(MyType.class).to(MyTypeImpl.class); } }
An important note though: by default Spring returns singleton-scoped beans. That is not the case with Guice. So if you need the same instance to be returned every time, you need to specify it when declaring your Guice bindings:
class Module extends AbstractModule { @Override protected void configure() { bind(MyType.class).to(MyTypeImpl.class).as(Singleton.class); } }
Once done, we inject our bound objects with the standard Java @Inject annotation:
class MyService { private final MyType myType; @Inject public MyService(MyType myType) { this.myType = myType; } }
Of course we still can invoke the getInstance() method and fetch instances directly from our injector.
Handling multiple implementations of the same type
What happens if a given type must bind to multiple implementations? With Guice we can write binding annotations: we link an implementation to a custom annotation. That way we can provide a hint on what we want to inject:
@BindingAnnotation @Target({ FIELD, PARAMETER, METHOD }) @Retention(RUNTIME) @interface Impl1 {} class MyTypeImpl1 implements MyType { @Override public String name() { return "MyTypeImpl1"; } } class Module extends AbstractModule { @Override protected void configure() { bind(MyType.class).annotatedWith(Impl1.class).to(MyTypeImpl1.class).asEagerSingleton(); bind(MyType.class).annotatedWith(Impl2.class).to(MyTypeImpl2.class).asEagerSingleton(); } } class MyService { @Inject public MyService(@Impl1 MyType impl1, @Impl2 MyType impl2) { ... } }
Another possibility, similar to @Qualifier, uses Names to name each implementation. The @Named annotation disambiguates them at injection:
class MyTypeImpl1 implements MyType { @Override public String name() { return "MyTypeImpl1"; } } class MyService { @Inject public MyService(@Named("impl1") MyType impl1, @Named("impl2") MyType impl2) { ... } } class Module extends AbstractModule { @Override protected void configure() { bind(MyType.class).annotatedWith(Names.named("impl1")).to(MyTypeImpl1.class).asEagerSingleton(); bind(MyType.class).annotatedWith(Names.named("impl2")).to(MyTypeImpl2.class).asEagerSingleton(); } } public class NamedBindingsExample { public static void main(String[] args) { Injector injector = Guice.createInjector(new Module()); injector.getInstance(MyService.class); } }
Provider Bindings
Sometimes you need to “prepare” an object before injecting it. For that purpose, you can leverage the @Provides annotation. You simply annotate the module method that builds the object. Eventually you can combine that with a binding annotation or with @Named:
class Module extends AbstractModule { @Override protected void configure() { } @Provides @Impl1 MyType provideImpl1() { MyTypeImpl impl = new MyTypeImpl(); impl.setName("Impl1 name"); return impl; } @Provides @Named("impl2") MyType provideImpl2() { MyTypeImpl impl = new MyTypeImpl(); impl.setName("Impl2 name"); return impl; } } class MyService { @Inject public MyService(@Impl1 MyType impl1, @Named("impl2") MyType impl2) { ... } }
To keep things clean, you can also move these providers out of the module. Just implement the Provider interface to define a provider class, and bind it to a type with:
class Module extends AbstractModule { @Override protected void configure() { bind(MyType).toProvider(MyTypeImplProvider.class); } }
Instance Bindings
When you need to inject a simple value-object, you can leverage instance bindings:
class Module extends AbstractModule { @Override protected void configure() { bind(Integer.class).annotatedWith(Names.named("someValue")).toInstance(123); } } class One { @Inject One(@Named("someValue") int someValue) { System.out.println(someValue); } }
Implicit Bindings
Now, we can simplify the boilerplate code we write to explicitly bind types in the module. Implicit binding can, under certain circumstances, bind an implementation with little to no additional information.
For starters, implementations do not necessarily need an interface to be bound. If you’d rather avoid defining unneeded interfaces (I know I do!), don’t worry: Guice plays along nicely with that.
Look at the code here below:
class Impl { Impl() { System.out.println("One is eligible without explicit definition."); } } class AService { @Inject public AService(Impl impl) { System.out.println(impl != null ? "Everything is wired as it should!" : "Ouch, something was not wired as expected..."); } } public class AppWithoutModule { public static void main(String[] args) { Injector injector = Guice.createInjector(); injector.getInstance(AService.class); } }
The “Impl” class is implicitly declared because it relies on a non-private, no-arguments constructor. The “AService” class needs dependencies… but doesn’t need to be declared. Its constructor carries the @Inject annotation. Guice picks it up automatically.
Let’s consider the code below:
class One { @Inject One(@Named("someValue") int someValue) { System.out.println(someValue); } } class Two {} @ImplementedBy(ThreeImpl.class) interface Three {} class ThreeImpl implements Three {} @ProvidedBy(FourProvider.class) class Four { Four(String arg1, int arg2) {} } class FourProvider implements Provider<Four> { @Override public Four get() { return new Four("some string", 777); } } class MyService { @Inject public MyService(One one, Two two, Three three, Four four) { if (one != null && two != null && three != null && four != null) { System.out.println("Everything is wired as it should!"); } else { System.err.println("Ouch, something was not wired as expected..."); } } } class Module extends AbstractModule { @Override protected void configure() { bind(Integer.class).annotatedWith(Names.named("someValue")).toInstance(123); } }
We don’t need to explicitly bind “MyService“, as it bears an @Inject. The same rules apply to that “One” class. The “Two” class possesses an implicit no-args constructor, so Guice detects it right away.
The “Three” type binds to its implementation with the help of that @ImplementedBy annotation.
Finally, we use a @ProvidedBy annotation to link that “Four” class to its provider. Here again, we avoid having to define the provider in our Module.
That’s it?
Well, no. There’s a lot more to say about Guice, most of which I am still discovering. But hopefully you should now have a good, first view on what this cool framework can bring to the table. Feel free to have a look at the comprehensive User Guide to learn more.
Of course, I’ve also taken the liberty of committing some code to GitHub, so that you can play around with it.
Until next time,
Cheers!