Guice - dependency injection framework

37
Guice 3/4/2014 1 Guice - Evgeny Barabanov

Transcript of Guice - dependency injection framework

Page 1: Guice - dependency injection framework

Guice

3/4/2014 1 Guice - Evgeny Barabanov

Page 2: Guice - dependency injection framework

Agenda

• Background

– Testability

– Dependency injection pattern (DI)

– Factory pattern

• Guice basics

• Unit testing with Guice and Mockito

• Best practices

• Useful links

3/4/2014 2 Guice - Evgeny Barabanov

Page 3: Guice - dependency injection framework

Testability class A {

int complexCalculation(){

//Some calculation

}

}

class B {

A a;

B(){

a = new A();

}

int doubleCalculation{

return a.complexCalulation() * 2;

}

}

Why it is difficult to unit test class B?

3/4/2014 3 Guice - Evgeny Barabanov

Page 4: Guice - dependency injection framework

Testability class A {

int complexCalculation(){

//Some calculation

}

}

class B {

A a;

B(){

a = new A();

}

int doubleCalculation{

return a.complexCalulation() * 2;

}

}

Why it is difficult to unit test class B?

What if a.complexCalculation

computes wrong result?

3/4/2014 4 Guice - Evgeny Barabanov

Page 5: Guice - dependency injection framework

Testability

Why it is difficult to unit test class B? - B implementation depends directly on actual A implementation, therefore we cannot test B as standalone class.

class A {

int complexCalculation(){

//Some calculation

}

}

class B {

A a;

B(){

a = new A();

}

int doubleCalculation{

return a.complexCalulation() * 2;

}

}

What if a.complexCalculation

computes wrong result?

3/4/2014 5 Guice - Evgeny Barabanov

Page 6: Guice - dependency injection framework

The solution – Dependency injection

interface IA {

int complexCalculation();

}

class A implements IA {

int complexCalculation(){

//Some calculation

}

}

class B {

IA a;

B(IA _a){

a = _a;

}

int doubleCalculation{

return a.complexCalculation() * 2;

}

}

Hollywood principle: “Don’t call us, we’ll call you”

3/4/2014 6 Guice - Evgeny Barabanov

Page 7: Guice - dependency injection framework

The solution – Dependency injection

interface IA {

int complexCalculation();

}

class A implements IA {

int complexCalculation(){

//Some calculation

}

}

class B {

IA a;

B(IA _a){

a = _a;

}

int doubleCalculation{

return a.complexCalculation() * 2;

}

}

Hollywood principle: “Don’t call us, we’ll call you”

Now A implementation is absolutely decoupled from B implementation…

3/4/2014 7 Guice - Evgeny Barabanov

Page 8: Guice - dependency injection framework

…It makes the unit testing of B much easier:

class AStub implements IA {

int complexCalculation(){

return 1;

}

}

class BTest(){

void testCalc(){

B b = new B(new AStub());

assert(b.doubleCalculation() == 2)

}

}

Now a.complexCalculation() may return wrong result and we don’t care, because we’re only testing the pure functionality of B.

3/4/2014 8 Guice - Evgeny Barabanov

Page 9: Guice - dependency injection framework

But…

I’d like to order a new computer

Sure! We’ll ship it in few

hours!

3/4/2014 9 Guice - Evgeny Barabanov

Page 10: Guice - dependency injection framework

…There is a problem

WTF!!??

3/4/2014 10 Guice - Evgeny Barabanov

Page 11: Guice - dependency injection framework

class HardDrive implements IHardDrive {

HardDrive(IDiscCase c, IDiscPlatter p, ISpindle s, IActuator a ...){

//

}

///

}

class GraphicCard implements IGraphicCard {

GraphicCard(IGraphicMemory m, IHeatSink hs, IFun f ...){

//

}

//

}

class Computer implements IComputer {

Computer(IHardDrive hd, IGraphicCard gc, ....){

//

}

//

}

3/4/2014 11 Guice - Evgeny Barabanov

Page 12: Guice - dependency injection framework

class Homer {

IComputer homersPC;

Homer(){

IDiscCase c = new DiscCase();

IDiscPlatter p = new DiscPlatter();

ISpindle s = new Spindle();

IActuator a = new Actuator();

...

IHardDrive hd = new HardDrive(c,p,s,a,...);

IGraphicMemory m = new GraphicMemory();

IHeatSink hs = new HeatSink();

IFun f = new Fun();

...

IGraphicCard gc = new GraphicCard(m, hs, f, ...);

...

...

//And finally!!!

homersPC = new Computer(hd, gc, ... );

}

}

The amount of “new” grows exponentially!!!

3/4/2014 12 Guice - Evgeny Barabanov

Page 13: Guice - dependency injection framework

The solution - Factory

I’d like to order a new computer

Sure! We’ll ship it in few hours! And

we will assemble it for you!

3/4/2014 13 Guice - Evgeny Barabanov

Page 14: Guice - dependency injection framework

WOW!!!

3/4/2014 14 Guice - Evgeny Barabanov

Page 15: Guice - dependency injection framework

class HardDriveFactory {

static IHardDrive GetHardDrive{

IDiscCase c = new DiscCase();

IDiscPlatter p = new DiscPlatter();

ISpindle s = new Spindle();

IActuator a = new Actuator();

...

return new HardDrive(c,p,s,a,...);

}

}

class GraphicCardFactory {

static IGraphicCard GetGraphicCard{

IGraphicMemory m = new GraphicMemory();

IHeatSink hs = new HeatSink();

IFun f = new Fun();

...

return new GraphicCard(m, hs, f, ...);

}

}

class ComputerFactory {

static IComputer GetComputer{

IHardDrive hd = HardDriveFactory.GetHardDrive();

IGraphicCard gc = GraphicCardFactory.GetGraphicCard();

...

return new Computer(hd, gc, ... );

}

}

class Homer {

IComputer homersPC;

Homer(){

homersPC = ComputerFactory.GetComputer();

}

}

3/4/2014 15 Guice - Evgeny Barabanov

Page 16: Guice - dependency injection framework

Factory

• Advantages: – Single responsibility. There is a separate entity which is responsible for instantiation and

configuration of a concrete object.

– Decoupling. A factory class decouples the client and implementing class.

– Avoid code duplication. If, for example, we are changing the signature of A constructor, we don’t have to go over all the classes which are using A, but just change it in A factory.

– Homer doesn’t have to assemble the computer by himself.

• Disadvantages:

– We still have tight coupling between Factory class and products.

– We still have testability issues with naive factory implementation (can be solved by using more advanced factory techniques, like factory with reflection)

– We shall maintain additional classes

3/4/2014 16 Guice - Evgeny Barabanov

Page 17: Guice - dependency injection framework

The most powerful solution - Guice

Guice is a dependency injection framework, which alleviates the need for factories and use of new in your java code. Your code will be easier to change, unit test and reuse in other contexts. Guice aims to make development and debugging easier and faster, not harder and slower.

3/4/2014 17 Guice - Evgeny Barabanov

Page 18: Guice - dependency injection framework

With dependency injection, objects accept dependencies in their constructors. To construct an object, you first build its dependencies. But to build each dependency you need its dependencies, and so on. So when you build an object, you really need to build an object graph.

Building object graphs by hand (as you have seen) is labour

intensive, error prone, and makes testing difficult. Instead, Guice can build the object graph for you. But first, Guice needs to be configured to build the graph exactly as you want it.

Injector is Guice's object-graph builder. First we create the injector,

and then we can use that to build.

The most powerful solution - Guice

3/4/2014 18 Guice - Evgeny Barabanov

Page 19: Guice - dependency injection framework

Bindings

Guice uses bindings to map types to their implementations.

3/4/2014 19 Guice - Evgeny Barabanov

Page 20: Guice - dependency injection framework

Explicit bindings

A module is a collection of bindings which is passed to Injector on its creation. In module we specify all the bindings we want to be used in current context.

3/4/2014 20 Guice - Evgeny Barabanov

Page 21: Guice - dependency injection framework

Explicit bindings

Consider previous example with a computer (for simplicity let’s assume that HardDrive and GraphicCard receive nothing in their constructors):

class HardDrive implements IHardDrive {

HardDrive(){

//

}

///

}

class GraphicCard implements IGraphicCard {

GraphicCard(){

//

}

//

}

class Computer implements IComputer {

Computer(IHardDrive hd, IGraphicCard gc, ....){

//

}

//

}

3/4/2014 21 Guice - Evgeny Barabanov

Page 22: Guice - dependency injection framework

Explicit bindings

Now lets define a module which will bind the interfaces to their implementations: public class ComputerModule extends AbstractModule {

@Override

protected void configure() {

/*

* This tells Guice that whenever it sees a dependency on a IHardDrive,

* it should satisfy the dependency using a HardDrive.

*/

bind(IHardDrive.class).to(HardDrive.class);

/*

* Similarly, this binding tells Guice that when IGraphicCard is used in

* a dependency, that should be satisfied with a GraphicCard.

*/

bind(IGraphicCard.class).to(GraphicCard.class);

/*

* Finally, this binding tells Guice that when IComputer is used in

* a dependency, that should be satisfied with a Computer.

*/

bind(IComputer.class).to(Computer.class);

}

3/4/2014 22 Guice - Evgeny Barabanov

Page 23: Guice - dependency injection framework

Explicit bindings

Once we defined a module and passed it to the injector, Guice will be able to create all the dependencies for us.

class Homer {

static Injector injector = Guice.createInjector(new ComputerModule());

IComputer homersPC;

Homer(){

homerPC = injector.getInstance(IComputer.class);

}

}

3/4/2014 23 Guice - Evgeny Barabanov

Page 24: Guice - dependency injection framework

Implicit bindings

When the injector needs an instance of a type, it needs a binding. The bindings in a modules are called explicit bindings, and the injector uses them whenever they're available. 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 or implicit bindings.

3/4/2014 24 Guice - Evgeny Barabanov

Page 25: Guice - dependency injection framework

Impicit bindings

Implicit binding is done by specifying @ImplementedBy annotation on the interface. Thus, instead of creating a module we can tell Guice what are default implicit bindings for our interfaces:

@ImplementedBy(GraphicCard.class)

public interface IGraphicCard {

//

//...

//

}

@ImplementedBy(HardDrive.class)

public interface IHardDrive {

//

//...

//

}

@ImplementedBy(Computer.class)

public interface IComputer {

//

//...

//

}

3/4/2014 25 Guice - Evgeny Barabanov

Page 26: Guice - dependency injection framework

Bindings

There are much more bindings, we have only seen the basic ones.

You can find out more about bindings here.

3/4/2014 26 Guice - Evgeny Barabanov

Page 27: Guice - dependency injection framework

Injections

It’s not enough to tell Guice what are the bindings. We also need to tell him what are injected objects for each class.

There are several ways to do that, let’s look at couple of them.

3/4/2014 27 Guice - Evgeny Barabanov

Page 28: Guice - dependency injection framework

Constructor injection

Constructor injection combines instantiation with injection. To use it, we should annotate the constructor with the @Inject annotation. This constructor should accept class dependencies as parameters. Most constructors will then assign the parameters to fields (better final fields).

public class Computer implements IComputer{

final IHardDrive m_hd;

IGraphicCard m_gc;

@Inject

public Computer(IHardDrive hd, IGraphicCard gc){

m_hd = hd;

m_gc = gc;

}

}

If your class has no @Inject-annotated constructor, Guice will use a public, no-arguments constructor if it exists. Constructor injection works nicely with unit testing. If your class accepts all of its dependencies in a single constructor, you won't accidentally forget to set a dependency

3/4/2014 28 Guice - Evgeny Barabanov

Page 29: Guice - dependency injection framework

Field injection

Guice injects fields with the @Inject annotation. This is the most concise injection, but the least testable.

public class Homer {

@Inject

private IComputer m_comp;

}

public class Main {

public static void main(String [] args)

{

Injector injector = Guice.createInjector();

Homer homer = injector.getInstance(Homer.class);

}

}

At the end of “main” method Homer will have an instance of Computer with all its subcomponents.

3/4/2014 29 Guice - Evgeny Barabanov

Page 30: Guice - dependency injection framework

Injections

You can find out more about Injections here.

3/4/2014 30 Guice - Evgeny Barabanov

Page 31: Guice - dependency injection framework

Unit testing with Guice and Mockito

We want to unit test Computer, so we don’t want to be dependent on GraphicCard and HardDrive implementations. The only thing we want to test is a Computers functionality. Guice provides us great ability to change binding. All we need is just define two stubs (for GraphicCard and HardDrive), define a new module which will bind interfaces to these stubs and pass this module to the injector in test module. But are we really want to maintain stub classes? Absolutely – NO. Mockito provides us great ability to simulate classes behavior with its Mocks, but as Mock is not a user defined class, we cannot bind interface to the Mock. So how can we combine and use both of these great frameworks?

3/4/2014 31 Guice - Evgeny Barabanov

Page 32: Guice - dependency injection framework

Unit testing with Guice and Mockito

The solution is @Provides methods:

@Provides method allows us to create an object when the object requires additional code to be created, set up or configured . The method must be defined within a module, and it must have an @Provides annotation. The method's return type is the bound type. Whenever the injector needs an instance of that type, it will invoke the method. Guice does not allow exceptions to be thrown from Providers. Exceptions thrown by @Provides methods will be wrapped in a ProvisionException. It is bad practice to allow any kind of exception to be thrown -- runtime or checked -- from an @Provides method.

3/4/2014 32 Guice - Evgeny Barabanov

Page 33: Guice - dependency injection framework

Unit testing with Guice and Mockito Consider previous example with computer:

public class HardDrive implements IHardDrive {

@Override

public int CalculateSpeed() {

int result = 0;

//Complicated and long computation

return result;

}

}

public class GraphicCard implements IGraphicCard {

@Override

public int GetPerformanceRate() {

int result = 0;

//Complicated and long computation

return result;

}

}

public class Computer implements IComputer{

IHardDrive m_hd;

IGraphicCard m_gc;

@Inject

public Computer(IHardDrive hd, IGraphicCard gc){

m_hd = hd;

m_gc = gc;

}

@Override

public int GetComputerPerformanceRate() {

return m_hd.CalculateSpeed()+m_gc.GetPerformanceRate();

}

}

3/4/2014 33 Guice - Evgeny Barabanov

Page 34: Guice - dependency injection framework

Unit testing with Guice and Mockito We’d like to test GetComputerPerformanceRate method. So lets do that! First, we create a module which defines @Provides methods for GraphicCard and HardDrive stubs:

public class ComputerTestsModule extends AbstractModule {

@Override

protected void configure() {

}

@Provides

IHardDrive provideHardDrive(){

IHardDrive hd = mock(IHardDrive.class);

when(hd.CalculateSpeed()).thenReturn(5);

return hd;

}

@Provides

IGraphicCard provideGraphicCard(){

IGraphicCard gc = mock(IGraphicCard.class);

when(gc.GetPerformanceRate()).thenReturn(10);

return gc;

}

}

3/4/2014 34 Guice - Evgeny Barabanov

Page 35: Guice - dependency injection framework

Unit testing with Guice and Mockito

After we tell Guice to use our module instead of default binding for HardDrive and GraphicCard creation, we can unit test the desired class:

public class ComputerTests extends TestCase {

static Injector injector = Guice.createInjector(new ComputerTestsModule());

IComputer cpForTest = injector.getInstance(IComputer.class);

@Test

public void testComputerPerformance(){

assertEquals(15, cpForTest.GetComputerPerformanceRate());

}

}

3/4/2014 35 Guice - Evgeny Barabanov