An overview of Guice
Google's Guice is a Java-based dependency injection framework, which means approximately nothing to people who aren't familiar with it.
Google's Guice is a way to build a graph of dependencies so you can instantiate complex objects made of simpler parts.
Google's Guice is a replacement for the new
keyword for many of your
objects. Bizzarre, right?
In a more tangible sense, Guice is a tool that's used in a lot of Java development. Beyond that, it positively impacts your code resulting in a reduction of boilerplate and an increase in testability.
What is dependency injection?
Dependency injection (DI) is a way to give an object it's dependencies. There are a few flavors of this, but first, let's just get on the same page.
A dependency of a class is something it needs to operate. It will encapsulate some amount of functionality whose implementation the dependant class can ignore.
A basic example of this would be a BillingProcessor
class might have
a dependency on SalesTaxCalculator
. It doesn't need to know how this
works and the Law of Demeter (aka the priniciple of least knowledge)
says that we shouldn't care how it works.
Dependency injection (the means of giving a class it's dependencies)
can take two main forms: constructor injection and setter
injection. They're pretty much what they sound like. Constructor
injection is passing in the dependencies to the constructor. With
setter injection, you create the object with an empty constructor and
call setFoo
to add the Foo
dependency.
I personally prefer constructor injection, because you don't have an object which is in an intermediate invalid state which eliminates lots of checking to ensure the object is valid.
DIY Injection Examples
- Constructor Injection
public class BillingProcessor { private SalesTaxCalculator taxCalc; public BillingProcessor(SalesTaxCalculator taxCalc) { this.taxCalc = taxCalc; } public bill(...) { /* ... */ } }
- Setter Injection
public class BillingProcessor { private SalesTaxCalculator taxCalc; public BillingProcessor() {} public void setTaxCalculator(SalesTaxCalculator taxCalc) { this.taxCalc = taxCalc; } public bill(...) { /* ... */ } }
Doing it without Guice
You can do DI without Guice, but it eventually gets a bit complicated. For our example, we'll design an event-based notification service.
public interface EventCoordinator { public Boolean schedule(Event ev); } public interface Emailer { public void email(User u, String title, String body); }
The implementation of this is pretty straight forward.
public EventScheduler implements EventCoordinator { private Emailer emailer; public EventScheduler() { this.emailer = new EmailService(); } public Boolean schedule(Event ev) { try { for (User u : ev.recipients()) { this.emailer.email(u, ev.title, ev.body); } } catch (Exception exc) { return false; } return true; } }
This suffers from some problems, in that our email service is hard coded, which means when we write our unit tests, we'll be sending real emails to people, which isn't good. Instead of this, we need to sub out the emailer implementation at test time.
public EventScheduler implements EventCoordinator { private Emailer emailer; public EventScheduler(Emailer em) { this.emailer = em; } public Boolean schedule(Event ev) { /* ... */ } }
This is solid. This is easily testable by creating a fake implementation of the Emailer. As this code grows beyond toy examples and the dependency graph of your dependencies becomes longer, you end up with code that looks more like a yak shave.
new EventScheduler( new RealEmailService( new EmailServiceTokenFetcher(username, password), new RateLimiter(maxEmailsPerHour), new EmailTemplate(new FileLocator("template/dir/path/"), new UserPreferenceSelector(new DatabaseConnection(...), ...)) ) )
This is where Guice comes in. You tell Guice that you want an
implementation of Emailer
and it goes and figures out all the things
it needs to do to make one.
Using Guice
Once setup, Guice is pretty straight forward to use. In each of your
constructors that need to have something injected in them, you just
add an @Inject
annotation and that tells Guice to do it's thing.
public EventScheduler implements EventCoordinator { private Emailer emailer; @Inject public EventScheduler(Emailer em) { this.emailer = em; } public Boolean schedule(Event ev) { /* ... */ } }
Guice figures out how to give you an Emailer
based on the type. If
it's a simple object, it'll instantiate it and pass it in. If it has
dependencies, it will resolve those dependencies, pass them into it's
constructor, then pass the resulting object into your object.
When you want to actually get your instance of EventScheduler
, you
just need to create an injector and ask for one. This usually lives in
your application's main()
method. Guice will fill in this object's
dependencies and any of its dependencies and so on.
Injector injector = Guice.createInjector(new MyModule()); // modules are the next section EventScheduler scheduler = injector.getInstance(EventScheduler.class); scheduler.schedule(...)
But wait.. emailer is an interface, not an instantiatable thing. How does Guice know how to give you one of those?
Configuring Guice
Guice is configured with one or more "modules". These inherit from
AbstractModule
.
public class MyModule extends AbstractModule { @Override protected void configure() { bind(Emailer.class).to(RealEmailService.class); } }
You can also tell Guice how to create specific object with don't have
@Inject
annotations (likely because they're a third party library)
by adding methods to your module annotated with @Provides
. These
methods can also take in arguments which are things Guice knows how to
build for you.
public class MyModule extends AbstractModule { String TEMPLATE_PATH = '/path/to/templates'; @Provides RateLimiter generateRateLimiter(UserPreferenceSelector prefSel) { return new EmailTemplate(new FileLocator(TEMPLATE_PATH), prefSel); } /* ...configure... */ }
So this all works based on types. What happens if you have multiple versions of the same type? This happens pretty often with configuration variables which are inevitably ~String~s.
Dealing with different versions of the same type
Guice solves the problem of having two different String
objects you want to
inject through "named annotations".
public EventScheduler implements EventCoordinator { private Emailer emailer; private String defaultSender; @Inject public EventScheduler(Emailer em, @Named("default_from") String defaultFrom) { this.emailer = em; this.defaultSender = defaultFrom; } /* ... */ }
You can bind these manually in your module, but there are libraries which will pull these from your configuration that make use of Names.bindProperties.
public class MyModule extends AbstractModule { @Provides RateLimiter generateRateLimiter(UserPreferenceSelector prefSel, @Named("template-path") String templatePath) { return new EmailTemplate(new FileLocator(templatePath), prefSel); } public void configure() { bind(String.class).annotatedWith(Names.named("template-path")).toInstance("/path/to/template"); /* ...more things... */ } }
There isn't any static analysis of Guice to see if you actually have bindings for the annotations you said you did. This means that failures in Guice happen at run-time. Given that developers can fat-finger strings not referenced as constants, Guice also allows you to use your own annotations.
package example.pizza; import com.google.inject.BindingAnnotation; import java.lang.annotation.Target; import java.lang.annotation.Retention; import static java.lang.annotation.RetentionPolicy.RUNTIME; import static java.lang.annotation.ElementType.PARAMETER; import static java.lang.annotation.ElementType.FIELD; import static java.lang.annotation.ElementType.METHOD; @BindingAnnotation @Target({ FIELD, PARAMETER, METHOD }) @Retention(RUNTIME) public @interface TemplatePath {}
Then you can use it similar to how you'd use the @Named
annotation.
public class MyModule extends AbstractModule { @Provides RateLimiter generateRateLimiter(UserPreferenceSelector prefSel, @TemplatePath String templatePath) { return new EmailTemplate(new FileLocator(templatePath), prefSel); } public void configure() { bind(String.class).annotatedWith(TemplatePath.class).toInstance("/path/to/template"); /* ...more things... */ } }
How instantiation works
Guice, by default, will instantiate a new version of your object every
time you request it. This is perfectly fine for things with minimal
configuration. Some objects, such as database connections, are limited
resources. To solve this, Guice provides different Scopes
. Two
common ones are @Singleton
and @RequestScoped
.
@Singleton
bindings mean there will only be one instance of the
object, which is passed around to all dependants. It can be applied to
the class, the @Provides
method, or manually in the configure
method.
// class based @Singleton public class InMemoryTransactionLog implements TransactionLog { /* everything here should be threadsafe! */ } // within the module's configure() method bind(TransactionLog.class).to(InMemoryTransactionLog.class).in(Singleton.class); // in your module @Provides @Singleton TransactionLog provideTransactionLog() { ... }
IMPORTANT Objects which are annotated as singletons must be threadsafe. This is a transitive property, meaning that all of the dependencies of that module requires must also be threadsafe.
@RequestScoped
annotations have default servlet extensions which
will generate a new object on each request.
Testing
Depencency injection makes for really great test seams, where you can pass in your own mocked objects. Beyond that, you may want to test your actual Guice configuration or use your Guice configuration for integration tests.
Testing your Guice bindings
Because Guice bindings are resolved at runtime, the best mechanism for testing them is simply writing end-to-end tests. If Guice can't find the required bindings, it will crash early.
If you have multiple Guice modules with inter-relationships (yes,
that's totally a thing), you can test how they work together by using
requireBinding
in the dependant module's configure
method. Just
list the classes you expect to be available and it'll error out if
they're not there.
Testing modules using Guice
Sometimes, you want most of the bindings from a Guice module, but
you want to override just one. A common example is the endpoint of a
production server or credentials with the test version. For that, you
can use Modules.override
. This will take all of the items in
MyProductionModule
, except the ones defined in the inline-created
AbstractModule
we made.
Modules.override(new MyProductionModule()).with(new AbstractModule { @Override public void configure() { // overrides a binding for Credentials.class which was defined in MyProductionModule. bind(Credentials.class).toInstance(new DevelopmentCredentials()); } });
Comparison vs Spring
Spring is a tool which some folks have experience with. It predates Guice by 5 years. It's a primarly XML oriented framework, but it does a lot more than Guice. It does some dependency injection, but also comes with a full MVC web framework.
Guice, while being simpler (by virtue of being focused on a small task and having hindsight), also manages to accomplish more in less code. Because it's just normal Java code, it also has good compiler checking for ensuring your constructors are well formed.
There is support for having both Guice and Spring play nicely together.
AOP - Aspect Oriented Programming
Aspect Oriented Programming (AOP) is a mechanism to wrap the functionality of existing code. It's a reasonable fit for things that have cross-cutting concerns, like authorization or metering.
In short, it allows you to intercept the call of a method and do
something before it, instead of it, or use the return value of the
intercepted method for some purpose. This is similar to defadvice
for elisp hackers or python's decorators.
There are other AOP frameworks (AspectJ being the fastest for how it does bytecode weaving) which Guice interoperates with.
If you're curious about this and how it works, check out Guice's documentation on it.