This document introduces Google Guice, a dependency injection framework. It discusses dependency injection, benefits like separation of concerns and easier testing. It also covers disadvantages like potential maintenance issues. The document explores the Guice API including Injector, Module, Binder and different types of bindings like linked, annotated, instance and constructor bindings. It provides a simple example using traits, classes and annotations to demonstrate dependency injection with Guice. References for more information on Guice and dependency injection are also included.
3. lThe ability to supply (inject) an external dependency into a
software component.
lTypes of Dependency Injection:
lConstructor injection
lSetter injection
lCake pattern
lGoogle-guice
What is Dependency Injection
4. Benefits of Dependency Injection
lSome of the benefits of using Dependency Injection are:
lSeparation of Concerns
lBoilerplate Code reduction in application classes because all
work to initialize dependencies is handled by the injector
component
lConfigurable components makes application easily extendable
lUnit testing is easy with mock objects
5. Disadvantages of Dependency Injection
lIf overused, it can lead to maintenance issues because effect of
changes are known at runtime.
lDependency injection hides the service class dependencies that
can lead to runtime errors that would have been caught at
compile time.
6. Exploring Google Guice
lGoogle Guice is a Dependency Injection Framework that can be
used by Applications where Relation-ship/Dependency between
Business Objects have to be maintained manually in the
Application code.
Trait Storage {
def store(data: Data)
def retrieve(): String
}
7. Exploring Google Guice
class FileStorage extends Storage {
def store(data: Data) = {
// Store the object in a file using Java Serialization mechanism.
}
def retrieve(): String = {
// Code to retrieve the object.
""
}
}
class StorageClient extends App {
// Making use of file storage.
val storage = new FileStorage();
storage.store(new Data());
}
8. A Simple Guice Example :
trait CreditCardProcessor {
def charge(): String
}
class CreditCardProcessorImpl extends CreditCardProcessor {
def charge(): String = {
//
}
}
trait BillingService {
def chargeOrder(order: PizzaOrder, creditCard: CreditCard): String
}
class RealBillingService @Inject() (processor: CreditCardProcessor) extends BillingService {
def chargeOrder(order: PizzaOrder, creditCard: CreditCard): String = {
//some code code process order
val result = processor.charge()
result
}
}
9. A Simple Guice Example (contd..):
class BillingModule extends Module {
def configure(binder: Binder) ={
binder.bind(classOf[CreditCardProcessor]).to(classOf[CreditCardProcessorImpl])
binder.bind(classOf[BillingService]).to(classOf[RealBillingService])
}
}
object GuiceApp extends App {
val module = new BillingModule
val injector = Guice.createInjector(module)
val component = injector.getInstance(classOf[BillingService])
println("Order Successful : " +
component.chargeOrder(PizzaOrder("garlicBread", 4), CreditCard()))
}
10. Injector
Injectors take care of creating and maintaining Objects that are used by
the Clients.
Injectors do maintain a set of Default Bindings from where they can
take the Configuration information of creating and maintaining Relation-
ship between Objects.
To get all the Bindings associated with the Injector, simply make a call
to Injector.getBindings() method which will return a Map of Binding
objects.
val injector = Guice.createInjector(new BillingModule)
val component = injector.getInstance(classOf[BillingService])
val bindings = injector.getBindings
11. Module
Module is represented by an interface with a method
called Module.configure() which should be overridden by the Application to
populate the Bindings.
To simplify things, there is a class called AbstractModule which directly
extends the Module interface. So Applications can depend
on AbstractModule rather than Module.
class BillingModule extends Module {
def configure(binder: Binder) = {
//code that binds the information using various flavours of bind
}
}
12. lGuice
Guice is a class which Clients directly depends upon to interact
with other Objects. The Relation-ship between Injector and the
various modules is established through this class.
For example consider the following code snippet,
val module = new BillingModule
val injector = Guice.createInjector(module)
13. Binder
This interface mainly consists of information related to Bindings. A
Binding refers a mapping for an Interface to its corresponding
Implementation.
For example, we refer that the interface BillingService is bound
to RealBillingService implementation.
binder.bind(classOf[BillingService]).to(classOf[RealBillingService])
14. Binder
To specify how dependencies are resolved, configure your injector with
bindings.
Creating Bindings
To create bindings, extend AbstractModule and override
its configure method.
In the method body, call bind() to specify each binding. These methods
are type checked so the compiler can report errors if you use the wrong
types.
Once you've created your modules, pass them as arguments
to Guice.createInjector() to build an injector.
15. lLinked Bindings
Linked bindings map a type to its implementation. This example maps
the interface CreditCardProcessor to the implementation
CreditCardProcessorImpl:
class BillingModule extends Module {
def configure(binder: Binder) ={
binder.bind(classOf[CreditCardProcessor]).to(classOf[CreditCardProcessorImpl])
}
}
binder.bind(classOf[CreditCardProcessorImpl]).to(classOf[RealBillingService])
16. lLinked Bindings
Linked bindings can also be chained:
In this case, when a CreditCardProcessor is requested, the injector will return
a RealBillingService.
class BillingModule extends Module {
def configure(binder: Binder) ={
binder.bind(classOf[CreditCardProcessor]).to(classOf[PaypalCreditCardProcessor])
binder.bind(classOf[PaypalCreditCardProcessor]).to(classOf[RealBillingService])
}
}
17. Binding Annotations
We want multiple bindings for a same type.
To enable this, bindings support an optional binding annotation. The
annotation and type together uniquely identify a binding. This pair is
called a key.
To define that annotation you simply add your annotation with your type
18. Binding Annotations
Named Annotations (built in binding annotation)
Guice comes with a built-in binding annotation @Named that uses a
string:
@ImplementedBy(classOf[RealBillingService])
trait BillingService {
def chargeOrder(order: PizzaOrder, creditCard: CreditCard): String
}
class RealBillingService @Inject() (@Named("real") processor: CreditCardProcessor) extends
BillingService {
/..../
}
19. Binding Annotations
To bind a specific name, use Names.named() to create an instance to
pass to annotatedWith:
Since the compiler can't check the string, we recommend using @Named
sparingly.
binder.bind(classOf[BillingService])
.annotatedWith(Names.named("real"))
.to(classOf[RealBillingService])
20. lInstance Bindings
You can bind a type to a specific instance of that type. This is usually
only useful only for objects that don't have dependencies of their own,
such as value objects:
Avoid using .toInstance with objects that are complicated to create,
since it can slow down application startup. You can use an @Provides
method instead.
binder.bind(classOf[String])
.annotatedWith(Names.named("JDBC URL"))
.toInstance("jdbc:mysql://localhost/pizza")
binder.bind(classOf[Int])
.annotatedWith(Names.named("Time Out"))
.toInstance(10)
21. Untargeted Bindings
You may create bindings without specifying a target.
This is most useful for concrete classes and types annotated by either
@ImplementedBy or @ProvidedBy
An untargetted binding informs the injector about a type, so it may
prepare dependencies eagerly.
Untargetted bindings have no to clause, like so:
binder.bind(classOf[RealBillingService])
binder.bind(classOf[RealBillingService]).in(classOf[Singleton])
22. Constructor Bindings
Occasionally it's necessary to bind a type to an arbitrary constructor.
This comes up when the @Inject annotation cannot be applied to the
target constructor: because multiple constructors participate in
dependency injection.
To address this, Guice has toConstructor() bindings.
They require you to reflectively select your target constructor and
handle the exception if that constructor cannot be found:
24. Just-in-time Bindings
If a type is needed but there isn't an explicit binding, the injector
will attempt to create a Just-In-Time binding.
These are also known as JIT bindings and implicit bindings.