cs410j: advanced java programmingwhitlock/pdf/di.pdfhow can we implement bookstore? package...

30
CS410J: Advanced Java Programming The Dependency Injection design pattern decouples dependent objects so that they may be configured and tested independently. Google Guice manages dependencies among objects and handles the complexity of wiring together complex object relationships. Dependency Injection The Dependency Injection design pattern Testing with mock objects using Mockito Managing dependencies with Google Guice Copyright c 2010-2018 by David M. Whitlock. Permission to make digital or hard copies of part or all of this work for personal or classroom use is granted without fee provided that copies are not made or distributed for profit or commercial advantage and that copies bear this notice and full citation on the first page. To copy otherwise, to republish, to post on servers, or to redistribute to lists, requires prior specific permission and/or fee. Request permission to publish from [email protected]. Last updated April 28, 2018. 1

Upload: phamnguyet

Post on 13-Jun-2018

217 views

Category:

Documents


0 download

TRANSCRIPT

CS410J: Advanced Java Programming

The Dependency Injection design patterndecouples dependent objects so that they maybe configured and tested independently. GoogleGuice manages dependencies among objectsand handles the complexity of wiring togethercomplex object relationships.

Dependency Injection

• The Dependency Injection design pattern

• Testing with mock objects using Mockito

• Managing dependencies with Google Guice

Copyright c©2010-2018 by David M. Whitlock. Permission to make digital or hardcopies of part or all of this work for personal or classroom use is granted withoutfee provided that copies are not made or distributed for profit or commercialadvantage and that copies bear this notice and full citation on the first page. Tocopy otherwise, to republish, to post on servers, or to redistribute to lists, requiresprior specific permission and/or fee. Request permission to publish [email protected]. Last updated April 28, 2018.

1

Dependencies Among Objects

Consider the below set of dependent objects

REST

purchase(List<Book>, CreditCard) : double

refund(List<Book> CreditCard) : double

BookStore

remove(Book)

add(Book)

inStock(Book) : int

BookInventory

directory : File

fileName : String

BookDatabase

debit(CreditCard, dollars : int, cents : int)

credit(CreditCard, dolars : int, cents : int)

CreditCardService

serverHost : String

serverPort : int

FirstBankOfPSUFirstBankOfPortlandStateServlet

A BookStore requires a BookInventory and aCreditCardService to get its work done

• We’ve already extracted interfaces to allow us toabstract out the behavior that we need

• We want to be able to replace the implementations ofBookInventory and CreditCardService withoutaffecting the code in BookStore

2

How can we implement BookStore?

package edu.pdx.cs410J.di;

import java.io.File;import java.util.List;

public class BookStore{

private final BookInventory inventory;

private final CreditCardService cardService;

public BookStore() {String tmpdir =

System.getProperty( "java.io.tmpdir" );File directory = new File( tmpdir );this.inventory =

new BookDatabase( directory, "books.txt" );this.cardService =

new FirstBankOfPSU( "localhost", 8080 );}

When the BookStore is created, it also creates itsdependencies

• Dependencies are stored in final fields becausethey only need to be created once

3

How can we implement BookStore?

public double purchase( List<Book> books,CreditCard card) {

double total = 0.0d;for (Book book : books) {

inventory.remove(book);total += book.getPrice();

}

CreditTransactionCode code =cardService.debit(card, total);

if (code == CreditTransactionCode.SUCCESS ) {return total;

} else {throw new CreditCardTransactionException(code);

}}

The purchase method uses the dependencies

4

What is wrong with this solution?

Sure, this code works, but..

• You can’t test this code!

– Tests would have to execute against the actualdatabase (slow) and credit card service ($$$)

– Lots of data setup: create database file and creditcard account

• The BookStore has to know how to configure itsdependencies

– Does the BookStore really care what port thecredit card service runs on?

• If another class wants to use the BookDatabase, itwould have to create another instance

– Two BookDatabases can’t work with the same file

• If you want to switch out another implementation ofthe dependencies, you have to change theBookStore

– Again, does the BookStore really care what kindof BookInventory is uses?

5

“Don’t call us, we’ll call you”

The “Hollywood Principle” states that dependent codeshould require that its dependencies are provided to it

• Practically speaking, it means pass dependenciesinto the constructor

public BookStore( BookInventory inventory,CreditCardService cardService ) {

this.inventory = inventory;this.cardService = cardService;

}

Now the BookStore class doesn’t have to know about theconcrete types of its dependencies

• Let the main program create and configuredependencies

public class BookStoreApp {public static void main(String... args) {

String tmpdir = System.getProperty( "java.io.tmpdir" );File directory = new File( tmpdir );BookInventory inventory =

new BookDatabase( directory, "books.txt" );CreditCardService cardService =

new FirstBankOfPSU( "localhost", 8080 );BookStore store =

new BookStore(inventory, cardService);

6

Testing with Mock Objects

Now that a BookStore gets passed its dependencies, youcan test the class without using the “production”BookInventory and CreditCardService

Instead of using real dependencies, test with “mock”objects that provide an implementation of the interfacethat is useful for testing

package edu.pdx.cs410J.di;

public abstract class MockObject {protected void shouldNotInvoke() {

throw new UnsupportedOperationException("Did not expect this method to be invoked");

}}

package edu.pdx.cs410J.di;

public class MockBookInventory extends MockObjectimplements BookInventory {

public void remove( Book book ) {shouldNotInvoke();

}}

7

Testing with Mock Objects

Test that BookStore invokes the expected methods of itsdependencies

• Override interface methods to keep track of how itwas invoked

package edu.pdx.cs410J.di;

import org.junit.Test;import java.util.Collections;import static org.junit.Assert.assertEquals;import static org.junit.Assert.assertNotNull;

public class BookStoreTest {@Testpublic void testBookIsPurchased() {

final Book[] removedBook = new Book[1];BookInventory inventory = new MockBookInventory() {

public void remove( Book book ) {removedBook[0] = book;

}};CreditCardService cardService = new MockCreditCardService() {

public CreditTransactionCode debit( CreditCard card, double amount ) {return CreditTransactionCode.SUCCESS;

} };

8

Testing with Mock Objects

Once the mock dependencies are set up

• Create the BookStore

• Invoke the method you want to test

• Verify that the dependency was invoked as youexpected

BookStore store =new BookStore(inventory, cardService);

CreditCard card = new CreditCard( "123" );final Book testBook =

new Book("title", "author", 1.00d);double total = store.purchase(

Collections.singletonList(testBook), card );assertEquals( testBook.getPrice(), total, 0.0d );

final Book removed = removedBook[0];assertNotNull( removed );assertEquals(testBook.getTitle(), removed.getTitle());assertEquals(testBook.getAuthor(), removed.getAuthor());assertEquals(testBook.getPrice(), removed.getPrice(), 0.0d);

}}

9

Mock Objects are Awkward

Mock Objects get the job done, but...

• Lots of verbose boilerplate code

• When interface changes (new method, e.g.), mockhas to change

• Subclassing mock objects is hokey

– Lots of anonymous inner classes

– Have to keep track of state that was passed todependency

There’s got to be a better way, right?

10

Mock Objects with Mockito

There are several testing libraries for Java that allow youto easily create mock objects

We’re going to learn about Mockito (http://mockito.org/)

• A Java API that creates mock objects for interfacesand classes

• Contains a domain specific language (DSL) forconstructing mock objects

• You can specify what behavior you expect out of themock objects

Add the dependency in your pom.xml

<dependency><groupId>org.mockito</groupId><artifactId>mockito-core</artifactId><version>1.8.5</version><scope>test</scope>

</dependency>

11

Mock Objects with Mockito

@Testpublic void testCreditCardIsCharged() {

Book book = mock(Book.class);double price = 1.00d;when( book.getPrice() ).thenReturn( price );

BookInventory inventory =mock(BookInventory.class);

CreditCardService cardService =mock(CreditCardService.class);

when(cardService.debit(any( CreditCard.class ),anyDouble() ))

.thenReturn( CreditTransactionCode.SUCCESS );

BookStore store =new BookStore(inventory, cardService);

CreditCard card = mock(CreditCard.class);

double total = store.purchase(Collections.singletonList(book), card );

assertEquals( book.getPrice(), total, 0.0d );

verify( cardService ).debit( card, price );}

Let’s take a closer look...

12

Creating mock objects

The most interesting methods are all in theorg.mockito.Mockito class

The mock method creates a mock object for an interfaceor class

• static <T> T mock(Class<T> classToMock)

• Unless instructed otherwise, invoking a method ofthe mock object will noop or return null

• Mockito uses crazy class loading tricks to accomplishthis

We mocked all of our objects in the previous example

• No need to invoke constructors

• Only had to specify the state we needed– The test doesn’t care about the title or author

of the Book

• We can add a method to an interface without havingto update a mock implementation

13

Specifying behavior of mock objects

The when method specifies how a method of the mockobject should behave

• static <T> OngoingStubbing<T> when(TmethodCall)

• Because they cannot be overridden by the magicalclass loader, final methods cannot be sent to when

The OngoingStubbing class specifies what the behaviorof the mock method

• thenReturn(T value) will cause the mock methodto return a given value

• thenThrow(Throwable t) will throw an exceptionwhen the mock method is invoked

• thenCallRealMethod() will invoked the realimplementation of the mock object to be invoked

In general, you want to chain the when and then callstogether

when( book.getPrice() ).thenReturn( price );

14

Method Argument Matching

The same mock method can be configured with differentargument values

when( cardService.debit(card, price).thenReturn(SUCCESS))

when( cardService.debit(card, -1.0).thenReturn(INVALID_AMOUNT))

Sometimes the behavior of the mock method doesn’tdepend on the value of its arguments

• Always return SUCCESS no matter which card ispassed in

Matchers, the superclass of Mockito, has methods forfiltering which values apply to the when

• Basically, there are bunch of type safe any methods

• any() object (or null) or any(Class<T>) object of agiven type

• anyInt(), anyDouble(), anyString(), etc.

• isNull, isNotNull, and same(T), matches(Stringregex)

15

Method Argument Matching

If at least one arguments to a mock method is one of the“matchers”, then all arguments must be matchers

• Use the eq() methods for matching on a single value

when( cardService.debit( any(CreditCard.class),eq(-1.0) )

.thenReturn(INVALID_AMOUNT);

The AdditionalMatchers class has even more matchers

• Boolean operations on matchers: and, or, not, etc.

• Comparison operations: gt, lt, geq

16

Verifying Mock Objects

After configuring your mock objects and sending them tothe API you want to test, the mock objects are verified

• Mock objects keep a history of what methods wereinvoked with which arguments

• The verify method tests whether or not a mockmethod was invoked with the expected arguments

To verify that cardService was invoked with the expectedcard and price:

verify( cardService ).debit( card, price );

You can also use matchers with verify

verify(cardService).debit( same(card), eq(price) );

17

Managing Dependencies

As your application scales, wiring together dependenciescan become very cumbersome

• Large initialization code that references tons ofclasses

• Difficult to change out old implementations for newimplementations

• End up relying on design patterns to create instances

– Factory Pattern∗ Create instances of a class using a static

method

– Singleton Pattern∗ Access the one-and-only instance of a class via

a static method

• Lots of boilerplate code that is tricky to get right

– Concurrent access to singletons

– Life cycle of pooled/cached objects

18

Google Guice

Google Guice (pronounced “juice”) is a framework forconfiguring and injecting dependencies into Java classes

• Leverages annotations to provide type-safety

• Well-known objects are registered in one place (a“module”)

• Objects that depend on well-known objects arecreated by Guice via reflection

– Dependencies are injected into constructors andfields

Maven dependency:

<dependency><groupId>com.google.code.guice</groupId><artifactId>guice</artifactId><version>2.0</version>

</dependency>

19

Managing Dependencies without Guice

From BookStoreApp.java

public static void main(String... args)throws JAXBException, IOException {

String tmpdir =System.getProperty("java.io.tmpdir");

File directory = new File(tmpdir);BookInventory inventory =

new BookDatabase(directory);addBooks(inventory);CreditCardService cardService =

new FirstBankOfPSU("localhost", 8080);

Logger logger =Logger.getLogger("edu.pdx.cs410J.Logger");

logger.setLevel(Level.INFO);

CheckoutPanel panel =new CheckoutPanel(inventory,cardService,logger);

BookStoreGUI gui = new BookStoreGUI(panel);gui.pack();gui.setVisible( true );

}

If we want to introduce another panel that is for inventorycontrol, we’d have to pass the well-knownBookInventory instance to that code, too.

20

Requesting Objects from Guice

Annotating one of a class’s constructors with @Injectinstructs Guice to invoke that constructor when aninstance is requested

• Guice will provide the well-known instances of theconstructors arguments∗

@Injectpublic CheckoutPanel(BookInventory inventory,

CreditCardService cardService,Logger logger ) {

If Guice is asked to create an instance of CheckoutPanel,it will invoke this constructor and provide an instance ofBookInventory and CreditCardService

• Your code doesn’t need to create the dependenciesany more!

@Inject can also be applied to methods and non-finalinstance fields methods that are invoked by Guice

• @Inject methods are invoked by Guice after theinstance is created

∗Guice automatically injects a Logger whose name is the name of theclass into which it is injected. And you can’t change it.

21

Guice Modules

A Guice “module” tells the Guice infrastructure aboutwell-known objects

• Your modules subclasscom.google.inject.AbstractModule

Objects are “bound” into Guice using the bind method

• Linking a type to its implementation (like a factorymethod)

bind(BookInventory.class).to(BookDatabase.class);

Whenever an object depends on a BookInventory,Guice will provide it with an instance of BookDatabase

By default, Guice will create a new instance of a boundclass when it is requested

Singleton objects in Guice can be configured in two ways

• Annotating the object’s class with @Singleton

• Binding the type in the singleton scope

bind(CreditCardService.class).to(FirstBankOfPSU.class).in(Singleton.class);

22

Annotations for Binding

The @Named annotation can be used to name well-knownobjects

From FirstBankOfPSU.java

@Injectpublic FirstBankOfPSU(

@Named("ServerHost") String serverHost,@Named("ServerPort") int serverPort )

In your module, you can bind the values of the namedobjects:

bind(String.class).annotatedWith(Names.named("ServerHost")).toInstance("localhost");

bind(Integer.class).annotatedWith(Names.named("ServerPort")).toInstance( 8080 );

When Guice instantiates FirstBankOfPSU it will providethe values of the server host and port.

23

Annotations for Binding

You can also create your own annotation types forwell-known objects

DataDirectory is an annotation for the directory in whichapplication data is stored

package edu.pdx.cs410J.di;

import com.google.inject.BindingAnnotation;

import java.lang.annotation.*;

@BindingAnnotation@Retention(RetentionPolicy.RUNTIME)@Target({ElementType.PARAMETER, ElementType.FIELD})@Documentedpublic @interface DataDirectory {

}

@BindingAnnotation means that the annotation beingdefined can be used with Guice

24

Annotations for Binding

You can annotate constructor parameters

@Injectpublic BookDatabase(@DataDirectory File directory)

And bind the value in your module:

String tmpdir =System.getProperty("java.io.tmpdir");

File directory = new File(tmpdir);bind(File.class)

.annotatedWith(DataDirectory.class)

.toInstance(directory);

25

Creating an Injector

A Guice module is used to create an Injector whichprovides access to well-known objects

public class GuicyBookStoreAppextends BookStoreApp {

public static void main(String... args) {Injector injector =

Guice.createInjector(new BookStoreModule());BookInventory inventory =

injector.getInstance(BookInventory.class);addBooks(inventory);

BookStoreGUI gui =injector.getInstance(BookStoreGUI.class);

gui.pack();gui.setVisible(true);

}}

Guice takes care of creating and configuring all of thedependencies of BookStoreGUI and wiring them together

Ultimately, your code is more decoupled and can bereconfigured more easily

26

Provider Methods

Sometimes, objects need more than a constructor call inorder to be initialized

• Or the constructor needs dependencies that are notprovided by Guice

A module method that is annotated with @Providesconstructs a Guice-managed instance of its return type

This is probably a better way to configure theDataDirectory File:

@Provides@DataDirectoryprotected File provideDataDirectory() {

String tmpdir =System.getProperty("java.io.tmpdir");

return new File(tmpdir);}

It would probably be better to construct theFirstBankOfPSU using a provider method, also.

• You wouldn’t need the @Named constructorparameters that reused anywhere

27

Provider classes

A Provider class is used to create very expensiveobjects or objects that you want lazily initialize

public class EncyrptionKeyProviderimplements Provider<EncryptionKey> {

private final EncryptionAlgorithm algorithm;

@Injectpublic EncyrptionKeyProvider(EncryptionAlgorithm algorithm) {

this.algorithm = alrogithm;}

@Overridepublic EncyrptionKey get() {

// Perform expensive operation on demandreturn new EncryptionKey(algorithm);

}}

Bind with:

bind(EncyrptionKey.class).toProvider(EncyrptionKeyProvider.class);

28

Provider classes

Guice can inject a provider into an object

@Singletonpublic class ConnectionManager {

@Injectprivate Provider<EncryptionKey> keyProvider;

public ConnectionManager() {// Should inject Provider as parameter instead

}

public Connection createConnection() {return new Connection(keyProvider.get());

}}

Even though the ConnectionManager is a singleton, itcan get multiple EncyrptionKeys by injecting theProvider∗

∗Okay, ConnectionManager is a factory and we could probably doingthis Guicier, but you get the point

29

Summary

Dependency Injection is the ultimate expression of“programming to the interface”

• The “Hollywood Principle” states that objects shouldreceive their dependencies upon construction

• When dependencies are decoupled, finer-grainedtesting is possible

• Mockito provides facilities for mocking objects andspecifying mocked behavior

• Google Guice manages dependencies by separatingconfiguration code from application code

30