Guice - dependency injection framework
-
Upload
evgeny-barabanov -
Category
Software
-
view
746 -
download
3
Transcript of Guice - dependency injection framework
Guice
3/4/2014 1 Guice - Evgeny Barabanov
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
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
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
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
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
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
…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
But…
I’d like to order a new computer
Sure! We’ll ship it in few
hours!
3/4/2014 9 Guice - Evgeny Barabanov
…There is a problem
WTF!!??
3/4/2014 10 Guice - Evgeny Barabanov
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
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
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
WOW!!!
3/4/2014 14 Guice - Evgeny Barabanov
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
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
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
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
Bindings
Guice uses bindings to map types to their implementations.
3/4/2014 19 Guice - Evgeny Barabanov
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
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
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
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
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
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
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
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
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
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
Injections
You can find out more about Injections here.
3/4/2014 30 Guice - Evgeny Barabanov
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
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
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
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
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
Best practices
• Minimize mutability • Inject only direct dependencies • Avoid cyclic dependencies • Avoid static state • Use @Nullable • Create fast and side-effects free modules • Be careful about I/O in providers • Avoid conditional logic in modules • Keep constructors as hidden as possible
3/4/2014 36 Guice - Evgeny Barabanov
Useful links
• FAQ
• Using Guice with Google AppEngine
3/4/2014 37 Guice - Evgeny Barabanov