introduction to google guice

26
INTRODUCTION TO GOOGLE GUICE Jyotsna Karan Software Consultant Knoldus Software LLP

Upload: mycellwasstolencom

Post on 17-Aug-2015

437 views

Category:

Education


10 download

TRANSCRIPT

INTRODUCTION TO GOOGLE GUICE

Jyotsna Karan Software Consultant

Knoldus Software LLP

AGENDAl Introduction to Dependency Injection

l What is Google Guice

l Exploring the Guice API

l Injector

l Module

l Guice

l Binder

l The ability to supply (inject) an external dependency into a software component.

l Types of Dependency Injection: l Constructor injectionl Setter injectionl Cake patternl Google-guice

What is Dependency Injection

Benefits of Dependency Injection

l Some of the benefits of using Dependency Injection are:

l Separation of Concerns

l Boilerplate Code reduction in application classes because all work to initialize dependencies is handled by the injector component

l Configurable components makes application easily extendable

l Unit testing is easy with mock objects

Disadvantages of Dependency Injection

l If overused, it can lead to maintenance issues because effect of changes are known at runtime.

l Dependency injection hides the service class dependencies that can lead to runtime errors that would have been caught at compile time.

Exploring Google Guice

l Google 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}

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());}

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 }}

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()))}

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

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 }

}

l Guice

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)

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])

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.

l Linked 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])

l Linked 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]) }}

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

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 {

/..../}

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])

l Instance 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)

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])

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:

Constructor Bindings

class BillingModule extends Module {

def configure(binder: Binder) = { try { binder.bind(classOf[BillingService]).toConstructor( classOf[RealBillingService].getConstructor(classOf[Connection])); } catch { case ex : Exception =>{ println("Exception Occured") } } }

}

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.

http://malsup.com/jquery/media/guice.pdf

http://www.journaldev.com/2394/dependency-injection-design-pattern-in-java-example-tutorial

https://books.google.co.in/books?id=s9Yr6gnhE90C&printsec=frontcover&source=gbs_ge_summary_r&cad=0#v=onepage&q&f=false

https://github.com/google/guice/wiki/ExternalDocumentation

https://github.com/google/guice/wiki/Motivation

References

Thank you