Dependency INjection










by
Milan Baran
... and Google Guice

What is Dependency INjection (DI)?


Actually, it is implementation of the Inverse of Control (IoC) pattern.


So, what is IoC then?

It is an OO pattern used to invert interfaces, flows and creation, mainly used with dependency injection to decouple object dependencies.


Anyway what that Means?


Lets imagine we live in Objective Java World...

We are in JavaGroupBrewery, which contains JavaBeerFaucet , which can be connected to a BeerKeg,

and any Customer can come and order some Beer.


Well, lets have a closer look...
in JAVA of course!

Java Group Brewery Domain

  • JavaBrewery can be open or closed
  • JavaBrewery is visited by JavaCustomers
  • JavaBrewery accepts beer orders.
  • JavaBrewery uses JavaBeerFaucets to make a JavaBeer
  • JavaBeerFaucets pulls JavaBeer from a JavaBeerKeg
  • Empty JavaBeerKeg can be replaced with a new one 
  • JavaBeerKeg has limited capacity of a JavaBeer
  • JavaBrewery can make or order a new BeerKeg
  • JavaCustomer can make a JavaBeer order
  • JavaCustomer can drink a JavaBeer and get in better mood
  • JavaCustomer can go home (usually after closing hours :))

Traditional approach


High level module
I
I
I
I
V
Low level module


traditionally low level modules define how higher level modules interface with them


Best Effort basis


Whats wrong with that in terms of ioC?


Strong coupling 


which BRINGS some problems:

  • To replace or update dependencies you need to change your classes as well
  • The concrete implementation of the dependencies have to be available
  • Classes are difficult to test because dependencies can't be replaced
  • Classes contain repetitive code for creating and managing dependencies.
  • Code examples

    Can be found: https://github.com/xbaran/guice-presentation
    public class JavaGroupBrewery {
    
      private JavaBeerFaucet javaBeerFaucet;
      private Boolean openStatus = Boolean.FALSE;
    
      public JavaGroupBrewery() {
        buildBrewery();
        this.javaBeerFaucet = new JavaBeerFaucet();
      }
    
      public void openBrewery() { ... }
      public void closeBrewery() { ... }
      public Boolean isOpen() { ... }
    
      public JavaBeer orderBeer() {
        try {
          return this.javaBeerFaucet.pourBeer();
        } catch (JavaBeerKeg.EmptyKegException e) {
          this.javaBeerFaucet.replaceEmptyKeg(new JavaBeerKeg());
          return orderBeer();
        }
      }
    
      public JavaBeer orderSmallBeer() { ... }
      private void cleanUp(JavaBeerFaucet javaBeerFaucet) { ... }
      private void buildBrewery() { ... }
    
    } 
     public class JavaBeerFaucet {
      private JavaBeerKeg beerKeg;
    public JavaBeerFaucet() { this.beerKeg = new JavaBeerKeg(); } public void replaceEmptyKeg(JavaBeerKeg javaBeerKeg) { this.beerKeg = javaBeerKeg; } public JavaBeer pourBeer() throws JavaBeerKeg.EmptyKegException { return beerKeg.pull(0.5F); }
     public class JavaBeerKeg {
      private float capacity;
    
      public JavaBeerKeg() {
        this.capacity = 50.0F;
      }
    
      public JavaBeer pull(float amount) throws EmptyKegException {
        if((capacity - amount) < 0) throw new EmptyKegException();
        this.capacity = this.capacity - amount;
        return new JavaBeer();
      }

    WORLD CREATION

       public World() {
        JavaCustomer Milan = new JavaCustomer("Milan");
        JavaCustomer Peter = new JavaCustomer("Peter");
        JavaCustomer Jaro = new JavaCustomer("Jaro");
    
        JavaGroupBrewery brewery = new JavaGroupBrewery();
            brewery.openBrewery();
    
        Milan.goGetSomeBeer(brewery);
        Peter.goGetSomeBeer(brewery);
        Jaro.goGetSomeBeer(brewery);
    
        Peter.goGetSomeBeer(brewery);
    
        brewery.closeBrewery();
    
        Peter.goGetSomeBeer(brewery);
        Peter.goGetSomeBeer(brewery);
    
      }

    Where to start?

    Change approach to inverted

    High level model -----> Interface








    with IoC high level modules define interfaces that lower level modules should implement

    A
    I
    I
    I
    I
    Low level model
    THINK FIRST BASIS

    WHat CHANGED?

     public interface Brewery {
      void openBrewery();
      void closeBrewery();
      Boolean isOpen();
      Beer orderBeer();
      Beer orderSmallBeer();
    }
    public interface BeerFaucet {
      public void replaceEmptyKeg(BeerKeg beerKeg);
      public Beer pourBeer() throws BeerKeg.EmptyKegException;
    }
    public interface BeerKeg {
      Beer pull(float amount) throws EmptyKegException;
      public class EmptyKegException extends Throwable {};
    }
    public interface Beer {
      Boolean isStrong();
      String getBrand();
    }
    
     public class SimpleBrewery implements Brewery {
      protected BeerFaucet beerFaucet;
      protected BeerKegFactory beerKegFactory;
      protected Boolean openStatus = Boolean.FALSE;
    
      public SimpleBrewery(BeerFaucet beerFaucet, BeerKegFactory beerKegFactory) {
        buildCoffeeShop();
        this.beerFaucet = beerFaucet;
        this.beerKegFactory = beerKegFactory;
      }  public Beer orderBeer() {
        try {
          return beerFaucet.pourBeer();
        } catch (BeerKeg.EmptyKegException e) {
          beerFaucet.replaceEmptyKeg(beerKegFactory.orderBeerKeg());
          return orderBeer();
        }
      }
      public void openBrewery() {...}
      public void closeBrewery() {...}
      public Boolean isOpen() {...}
      private void cleanUp(BeerFaucet beerFaucet) {...}
      private void buildCoffeeShop() {...}

     public class SimpleBeerFaucet implements BeerFaucet {
    
      private BeerKeg beerKeg;
    
      public SimpleBeerFaucet(BeerKeg beerKeg) {
        this.beerKeg = beerKeg;
      }
     public abstract class SimpleBeerKeg implements BeerKeg {
    
    public SimpleBeerKeg(float capacity) { this.capacity = capacity; } @Override public Beer pull(float amount) throws EmptyKegException { if((capacity - amount) < 0) throw new BeerKeg.EmptyKegException(); this.capacity = this.capacity - amount; return createBeer(); } protected abstract Beer createBeer(); }


    Factory

    public class GenericBeerKeg extends SimpleBeerKeg {
      private BeerFactory beerFactory;
    
      public GenericBeerKeg(float capacity, BeerFactory beerFactory) {
        this.beerFactory = beerFactory;
      }
    
      protected Beer createBeer() {
        return beerFactory.get();
      };
    }
    new BeerFactory(JavaBeer.class)
    public Beer get() {
        try {
          return beerClass.newInstance();
        } catch (InstantiationException e) {
          e.printStackTrace();
        } catch (IllegalAccessException e) {
          e.printStackTrace();
        }
        return null;
      }

    Where is DI?

       public World() {
        BeerKegFactory javaBeerKegFactory = 
                 new SimpleBeerKegFactory(50, JavaBeer.class);    Brewery javaPivarna = 
                 new SimpleBrewery(
                    new SimpleBeerFaucet(javaBeerKegFactory.orderBeerKeg()),
                    javaBeerKegFactory);
    
    
        Customer Milan = new CommonCustomer("Milan"); 
        Customer Peter = new CommonCustomer("Peter",
                                             new NeverGoHomeStrategy());
        Customer Jaro = new CommonCustomer("Jaro",
                                            new PesimisticDrinkingStrategy(),
                                            new TimeToGoHomeStrategy());
    
        javaPivarna.openBrewery();
    
        Milan.goGetSomeBeer(zamockyBrewery);
        Milan.goGetSomeBeer(javaPivarna);
        Peter.goGetSomeBeer(zamockyBrewery);
        Jaro.goGetSomeBeer(zamockyBrewery);
    
        zamockyBrewery.closeBrewery();
        javaPivarna.closeBrewery();
      }

    The guice Way!


    Motivation

    1. Multiline construtors and factories aren't fun
    2. @Inject is the new new
    3. Extensions and advanced features
    4. No XML
    5. Typesafe binding
    6. More up front checking
    7. Rich injection error messages
    8. Small overhead
    9. Small memory footprint (<700kB)

    How to extend ioc example

    How to inject

    Constructor injection










    IMMUTABLE!
    to supply dependencies when creating an object
    public class GuicedBrewery extends SimpleBrewery {
    
    @Inject public GuicedBrewery(BeerFaucet beerFaucet, BeerKegFactory beerKegFactory) { super(beerFaucet, beerKegFactory); }}
    public class SimpleBrewery implements Brewery {
    protected final BeerFaucet beerFaucet; protected final BeerKegFactory beerKegFactory;}

    Method injection

    sets dependencies into a new or existing instance

    public class GuicedBrewery extends SimpleBrewery {
    
    @Inject public void setBeerFaucet(BeerFaucet beerFaucet) { this.beerFaucet = beerFaucet;}
    Field injection (or Rape your instance)
    sets dependencies into a new or existing instance
    doesn't play with inheritance well
    public class GuicedBrewery extends SimpleBrewery {  @Inject protected BeerFaucet beerFaucet;
      @Inject protected BeerKegFactory beerKegFactory;

    public GuicedBrewery() { super(beerFaucet, beerKegFactory); //Cannot reference before supertype }}

    Define bindings in module

     public class SimpleWorldModule extends AbstractModule {
    
      @Override
      protected void configure() {
        bind(Brewery.class).to(GuicedBrewery.class);
        bind(BeerFaucet.class).to(GuicedBeerFaucet.class);
        bind(BeerKeg.class).to(GuicedBeerKeg.class);
        bindConstant().annotatedWith(Names.named("keg.capacity")).to(50F);
        bind(Beer.class).to(JavaBeer.class);
      }
     public World() {
        BeerKegFactory javaBeerKegFactory = 
                     new SimpleBeerKegFactory(50, JavaBeer.class);
        Brewery javaPivarna = new SimpleBrewery(
                     new impleBeerFaucet(javaBeerKegFactory.orderBeerKeg()),
                     javaBeerKegFactory);
     com.google.inject.CreationException: Guice creation errors:
    
    1) No implementation for me.baran.brewery.BeerKegFactory was bound.
      while locating me.baran.brewery.BeerKegFactory
        for parameter 1 at me.baran.brewery.GuicedBrewery.<init>(GuicedBrewery.java:15)
      at me.baran.guice.SimpleWorldModule.configure(SimpleWorldModule.java:23)

    Guice factory => Provider<T>

    • Implicitly all bindings are providers
    • Binding can be declaread as Provider<BindingClass>
    • to lazy loading
    • to get multiple instances
    • to mix scopes
    • to debug
    public interface Provider<T> {
      T get();
    }
     bind(Beer.class).to(JavaBeer.class);
     bind(Beer.class).toProvider(JBeerProvider.class);//impl. Provider<Beer>
     bind(Beer.class).toProvider(new Provider<Beer>() {
          @Override
          public Beer get() {
            return new JavaBeer();
          }
     });


    @Provides annotation

    public class SimpleWorldModule extends AbstractModule {
    @Override protected void configure() {...}
    @Provides public BeerKegFactory  providesBeerKegFactory(final Provider<BeerKeg> beerKeg) { return new BeerKegFactory() { @Override public BeerKeg orderBeerKeg() { return beerKeg.get(); } }; }

    Provider<T> and Factory are equal.

    WeLL, we are all set, what now?

    • high level interfaces
    • generic low level implementation
    • integraton with Guice
    • and SimpleWorldModule

    How to get JavaBrewery instance, then?

     Injector inj = Guice.createInjector(new SimpleWorldModule());
     Brewery javaPub = inj.getInstance(Brewery.class);
        BeerKegFactory javaBeerKegFactory = 
                      new SimpleBeerKegFactory(50, JavaBeer.class);    Brewery javaPivarna = new SimpleBrewery(
                      new SimpleBeerFaucet(javaBeerKegFactory.orderBeerKeg()),
                      javaBeerKegFactory);

    Wanna more pubs in the town!!!

    Pubs want to be different, so, they introduce new Beers.
     [PilsnerBeer, KozelBeer, JavaBeer, CorgonBeer, SvijanyBeer]




    Lets bind some more breweries

     protected void configure() {
        bind(Brewery.class).to(GuicedBrewery.class);
        bind(BeerFaucet.class).to(GuicedBeerFaucet.class);
        bind(BeerKeg.class).to(GuicedBeerKeg.class);
        bindConstant().annotatedWith(Names.named("keg.capacity")).to(50F);
        bind(Beer.class).to(JavaBeer.class);       bind(Brewery.class).to(GuicedBrewery.class);
        bind(Beer.class).to(PilsnerBeer.class);     }
     1) A binding to me.baran.brewery.blueprint.Beer was already configured at me.baran.guice.SimpleWorldModule.configure(SimpleWorldModule.java:30).
      at me.baran.guice.SimpleWorldModule.configure(SimpleWorldModule.java:33)
    What a smart boy! But, what now?!

    Annotation vs. Private Modules

    Annotation

    @BindingAnnotation
    @Target({ FIELD, PARAMETER, METHOD })
    @Retention(RUNTIME)
    public @interface BreweryPub {
      String value();
    }
     bind(Brewery.class)
     .annotatedWith(new BreweryPubImpl("Java")).to(GuicedBrewery.class); bind(Beer.class)
     .annotatedWith(new BreweryPubImpl("Java")).to(JavaBeer.class);
    
     bind(Brewery.class)
     .annotatedWith(new BreweryPubImpl("Pilsner")).to(GuicedBrewery.class);
     bind(Beer.class)
     .annotatedWith(new BreweryPubImpl("Pilsner")).to(PilsnerBeer.class);
    @Inject
      public GuicedBeerKeg(
        @Named("keg.capacity") float capacity, 
        @BreweryPub("Java") Provider<Beer> beerProvider) {
        super(capacity);
        this.beerProvider = beerProvider;
      }




    PRIVate modules

    • Configuration information is hidden from its environment by default
    • Only bindings that are explicitly exposed will be available to other modules and to the users of the injector
    • A private module can be nested within a regular module or within another private module using install()
    class PrivateBreweryModule extends PrivateModule {
    
      private final Annotation pubAnnotation;
      private final Class<? extends Beer> beerClass;
    
      PrivateBreweryModule(Class<? extends Beer> beerClass) {
        this.pubAnnotation = new BreweryPubImpl(beerClass.getSimpleName());
        this.beerClass = beerClass;
      }
    
      @Override
      protected void configure() {
                bind(Brewery.class).annotatedWith(pubAnnotation).to(GuicedBrewery.class);
        bind(BeerFaucet.class).to(GuicedBeerFaucet.class);
        bind(BeerKeg.class).to(GuicedBeerKeg.class);
        bindConstant().annotatedWith(Names.named("keg.capacity")).to(50F);
    
        bind(Beer.class).to(beerClass);
    
        expose(Brewery.class).annotatedWith(pubAnnotation);
      }
    
      @Provides
      public BeerKegFactory providesBeerKegFactory(final Provider<BeerKeg> beerKegProvider) {
    
        return new BeerKegFactory() {
          @Override
          public BeerKeg orderBeerKeg() {
            return beerKegProvider.get();
          }
        };
      }
    }
    1) Unable to create binding for me.baran.brewery.blueprint.Brewery annotated with BreweryPub(value=JavaBeer). It was already configured on one or more child injectors or private modules (PrivateBreweryModule.java:35)
     If it was in a PrivateModule, did you forget to expose the binding?
     ComplexWorldModuleTest.getJavaPub(ComplexWorldModuleTest.java:31)

    Pure ELEGANCE ...


      public class ComplexWorldModule extends AbstractModule {
    
      @Override
      protected void configure() {
        install(new PrivateBreweryModule(PilsnerBeer.class));
        install(new PrivateBreweryModule(KozelBeer.class));
        install(new PrivateBreweryModule(JavaBeer.class));
        install(new PrivateBreweryModule(ZamockyBeer.class));
      }

    world creation

     @Inject
      public GuicedWorld(
          @BreweryPub("PilsnerBeer") Brewery pilsnerPub,
          @BreweryPub("KozelBeer")   Brewery kozelPub,
          @BreweryPub("JavaBeer")    Brewery javaPub,
          @BreweryPub("ZamockyBeer") Brewery zamockyPub
      ) {
        pilsnerPub.openBrewery();
        kozelPub.openBrewery();
        javaPub.openBrewery();
        zamockyPub.openBrewery();
            ... and get the party started!!!
      }
     GuicedWorld world = Guice.createInjector(
                          new ComplexWorldModule())
                          .getInstance(GuicedWorld.class);

    HOw to test?

     public class ComplexWorldModuleTest {
      @Test
      public void testComplexWorldInjection() throws Exception {
        Injector inj = Guice.createInjector(new ComplexWorldModule());
        inj.injectMembers(this);
    
        Brewery pilsnerPub = inj.getInstance(
                 Key.get(Brewery.class, new BreweryPubImpl("PilsnerBeer")));
        Brewery javaPub = getJavaPub();
        Assert.assertNotNull(javaPub);
        Assert.assertNotSame(javaPub,pilsnerPub);
        Assert.assertEquals("Java",javaPub.orderBeer().getBrand());  }
    
      @Inject
      public void getJavaPub(@BreweryPub("JavaBeer") Brewery javaPub) {
        this.javaPub = javaPub;
      }
    
      public Brewery getJavaPub() {
        return this.javaPub;
      };

    WHat was said...

    • IoC pattern
    • Dependency injection via creation inversion
    • Guice introduction
    • Types of injections
    • Modules - Abstract, Private
    • Module bindings
    • Providers
    • Annotations


    WHAT COULD BE NEXT

    • Scopes, Stages
    • Extensions
      • ServletModule
      • MultiBindings
      • AssistedInjection
      • Throwing providers
      • SPI
    • Integration with 3th party frameworks
      • Apache Shiro - security
      • Apache Camel - integration
      • Sitebricks - lightweight web framework
      • Vaadin, Jersey, ...




    AND THIS IS END!

    Dependency Injection with Google Guice

    By Milan Baran

    Dependency Injection with Google Guice

    Google Guice, Dependency Injection, Examples, https://github.com/xbaran/guice-presentation

    • 3,613