Skip to content
This repository has been archived by the owner on Feb 6, 2020. It is now read-only.

DawnInjections

digitalally edited this page Sep 13, 2010 · 41 revisions

More Details on Injecting Stuff

It’s time to go into some more detail about how to make Dawn inject the classes we want. Below we’ll run through the metadata Dawn needs sprinkled across those classes as well as the ActionScript configuration (where necessary) to make it all come together.

There’s a few different ways to inject classes, which needs a bit of explaining, so here we go:

Basic Injections

Lets talk about that car again. The car depends directly on a LittleEngine, we (and Dawn) can see since the [Inject] metadata has been added to the engine variable:

class Car {
    [Inject] public var engine:LittleEngine;
    public function Car(){}

    public function startCar():void {
        engine.start();
    }
}

Dawn needs no configuration at all to create that Car for us! That’s because Dawn can make sensible decisions where you don’t tell it otherwise, so when we ask Dawn to create our Car it will just create a Car object, and a LittleEngine object, wire them up and we’re away!

Creating the Car with Dawn would look something like this:

// create a Dawn injector
var injector:IInjector = Injector.createInjector();

// ask the injector to create and inject a Car for us
var car:Car = Car(injector.inject(Car))

That’s a pretty simplistic example (fairly limited wow factor?), so let’s go for something a little more concrete: a concrete implementation… What if we wanted a Car that was more flexible? We could, for example, create an IEngine interface and rework our car to use one of those.

class Car {
    private var engine:IEngine;
    
    public function Car(engine:IEngine){
      this.engine = engine;
    }

    public function startCar():void {
        engine.start();
    }
}

Now our Car could work with any engine that implements the IEngine interface, and we have the power via Dawn configuration to swap out the engine for something else.

Also worth noting is that the engine instance is now a constructor argument, that’s because I had a think about it, and a Car without an engine isn’t much use! (Dawn is fine with constructor dependencies, so it’s no bother)

So… if we still wanted the LittleEngine the configuration would look like this injector.map(IEngine).to(LittleEngine);, but wait, maybe we want to up-size: injector.map(IEngine).to(MassiveEngine);. Depending on which of those lines we use when Dawn creates our Car it will also create and inject the mapped implementation of IEngine

We don’t need to create an external configuration for something this simple, so I’ll just use the handy map function on the injector

  // create a Dawn injector
  var injector:IInjector = Injector.createInjector();
  
  // lets still have a little engine (think of the children... or something)
  injector.map(IEngine).to(LittleEngine);
  
  // ask the injector to create and inject a @Car@ for us
  var car:Car = Car(injector.inject(Car))
  
  // Ta Da!!

Well that’s the basics over with, good job!

Named Injections

Now we’ve covered the basics of injections we can move on to some more contrived automobile examples! Lets extend out Car a couple of times, and create a SportsCar and a HybridCar. They are going to want different types of engines, and why not some different types of interior.

First we can add the interior property to our Car, I’ll use a method to inject it this time, just to mix it up:

class Car {
    private var interior:IInterior;
    private var engine:IEngine;
    
    public function Car(engine:IEngine){
      this.engine = engine;
    }
    
    [Inject]
    public function setInterior(interior:IInterior):void{
      this.interior = interior;
    }
    
    public function startCar():void {
        engine.start();
    }
}

Now to create the two subclasses. Both SportsCar and HybridCar will have very different engines, but that is easy enough for us to configure, we can simply type their constructor’s engine parameter to the type of engine they use eg function SportsCar(engine:SportyEngine).

How about the interior? The setInterior method is typed to IInterior, and there is no way to override and change that (to a concrete type, or sub-interface) in SportsCar or HybridCar, but we do want them to have different interiors. This is where named injections come in! We can override the setInterior method and set unique names for the instances the two types of cars require. Take a look at these class definitions:

class SportsCar extends Car {
    public function SportsCar(engine:SportyEngine){
      super(engine)
    }

    [Inject]
    [Named(index="1", name="sporty")]
    override public function setInterior(interior:IInterior):void{
      super.setInterior(interior);
    }
}
class HybridCar extends Car {
    public function HybridCar(engine:HybridEngine){
      super(engine);
    }

    [Inject]
    [Named(index="1", name="hybrid")]
    override public function setInterior(interior:IInterior):void{
      super.setInterior(interior);
    }
}

You can see that in the overrides for setInterior in both the sub classes I have added the Named metadata. Named allows us to explain to Dawn via metadata that there is a particular mapping that we are after, and it can be distinguished by name.

In the case of the SportsCar we can see that it would like to be injected with the sporty IInterior mapping. Lets take a look at the configuration for these classes and see if that makes it all clear.

  injector.map(IInterior).to(BasicInterior);
  injector.map(IInterior).to(SportyInterior).named("sporty");
  injector.map(IInterior).to(HybridInterior).named("hybrid");

With the above configuration we have mapped IInterior three times. The first line maps the IInterior interface to the BasicInterior class, so that any class that wants a IInterior and does not specify a name will get a BasicInterior. The next two lines again map the IInterior interface to two concrete classes, only in these cases the mappings are given names.

In the above scenario when Dawn creates a HybridCar it will look for an IInterior mapping with the name hybrid, and it will find the HybridInterior class.

And there you have it, the named feature allows us to chose the implementation we want for a given class. WIN

Instance Injections

In all the examples above Dawn has created the objects as well as injecting them with their dependencies. Sometimes however you already have the instance of an object. When this happens (don’t fear!!) you can use the toInstance mapping Dawn provides

injector.map(AbstractService).named("customer service").toInstance(custService);

// ... in mxml

<mx:RemoteObject id="custService" ... />

Factory Driven Injections

It is not always possible for Dawn to construct everything you need, perhaps you are relying on some third party code, or you have some logic you want to perform before creating a instance of a class. This is when factory providers come in handy, they allow you to write a custom class that performs the instantiation of your object.

To demonstrate we might as well pretend our car needs some GPS, a third part GPS of course. The GpsSystem class depends on having a CountryMap instance of the locale we’re in. To provide each Car with a new GpsSystem we will use a factory provider

Our upgraded Car now looks like this

class Car {
    protected var engine:IEngine;
    protected var _gps:GpsSystem;

    public function Car(engine:IEngine){
      this.engine = engine;
    }

    [Inject]
    public function set gps(value:GpsSystem):void{
        _gps = value;
    }
}

Now we need some custom code to create the GpsSystem for the Car

class GpsFactory{

    [Inject(name="country code")]
    public var locale:String

    public function GpsFactory(){}

    [Provider]
    public function getNewGps():GpsSystem{
        var map:CountryMap = new CountryMap( locale );
        var gps:GpsSystem = new GpsSystem( map );
        return gps;
    }
}

Now we can map the GpsSystem to our factory so Dawn knows to use it to create instances.

  mapper.map(GpsSystem).toFactory(GpsFactory);

Thats all there is to it. The gps property of the Car ’s will be populated by Dawn using the custom factory we wrote above. That means that whenever Dawn sees that a class has a dependency on GpsSystem it will call the getNewGps function within its GpsFactory instance. It knows to call the getNewGps function as that is the one with the [Provider] metadata.

The great thing about Dawn factory providers is that your code need never depend on them. Dawn creates the GpsFactory can calls its provider method, all your code needs to do it require a GpsSystem.

You might also have noticed that the GpsFactory has a dependency of its own on a String with a name “country code”. Factories can have dependencies just like any other Dawn managed object. More on injecting String ’s and other properties further down the page in injecting properties

Scopes

asSingleton

A glorious benefit of Dawn and dependency injection is that you no longer need to use the dreaded singleton pattern (or any nasty static state come to think of it). Single scope is not the singleton pattern in anther guise, its just a way of explaining to Dawn how and when you want to create new instances of classes.

There are two built in scopes in Dawn, Singleton or well, not singleton (transient) and the difference is simple but very important. A class that is scoped as a singleton will only be instantiated once by Dawn (within the life time of a IInjector). Otherwise a new instance of a class is created every time Dawn need to inject it.

Heres an example, lets say we have two classes that require one object… A Flower and a Tree, both require a Sun… you see?

class Flower {
  public var sun:Sun;
  public function Flower(sun:Sun){
    this.sun = sun;
  }
}

class Tree {
  public var sun:Sun;
  public function Tree(sun:Sun){
    this.sun = sun;
  }
}

class Sun{}

If we were to create a Flower and a Tree using Dawn with no configuration both will get different instances of Sun… that sounds worrying! So we need to tell Dawn that there really is only ONE Sun, and both trees and flowers should get the same instance of Sun.

We can do that with one little line:

  injector.map(Sun).asSingleton();

And nature is as it should be.

asEagerSingleton

One common requirement of a project is that a model or service object should be create at startup time even though the views that use (depend) on it are not created until some later date. To force creation of such objects we can use the asEagerSingleton scope. Such objects will be created as soon as their configuration in installed, or if the map function on the injector is used, then as soon as the first object in injected.

Injecting Properties

Dawn makes it nice and easy to remove hardcoded properties from your classes. Ever got in a pickle when you find the url to the development server in the live swf… eeeek! With Dawn you INJECT… I expect you’ve got the message by now.. so what does injecting these properties look like? Glad you asked…

class RpcService {
  [Inject(name="rpc url")] public var url:String;
  
}

injector.map(String).named("rpc url").to("http://www.wibble.com/service/messagebroker/")

Now any class that want the rpc url can just request it via the [Inject] metadata, no more hardcoding!! WIN

Completion Trigger

Lots of objects need to do something when they are created, add some listeners, set some properties, you know what its like. But how does a object know when it has had all its dependencies injected???… Well Dawn will let it know, if it asks or course

class RpcService {
  [Inject(name="rpc url")] public var url:String;
  
  [PostConstruct]
  public function loginAtStartup():void{
    // do things.. ummmm
  }
}

Just add the PostConstruct metadata and Dawn will call the annotated function when it has finished creating it, so in the above example the loginAtStartup function will be called after the rpc url has been set on that object.

Clone this wiki locally