4 Dependency Injection

Defining the dependencies of your classes is one of the core tasks when you configure the services and actions of your application. This chapter demonstrates the various options Parsley offers you for Dependency Injection.

The preferred configuration style for dependencies is using AS3 Metadata tags. Since the dependencies of a class are part of the core aspects of a class definition it makes sense to define dependencies right in the ActionScript class itself. It some cases you may still prefer to externalize dependency declarations, the corresponding options are described in the final section of this chapter 4.5 Declaring Dependencies in MXML or XML.

4.1 Constructor Injection

Many consider this to be the cleanest injection style in terms of encapsulation, since it allows you to create immutable classes. Since (unfortunately) the Flash Player currently ignores metadata tags placed on constructors you have to place a [InjectConstructor] tag on the class declaration to tell Parsley to perform Constructor Injection:

package com.bookstore.actions {

[InjectConstructor]
class LoginAction {

    private var service:LoginService;
    private var manager:UserManager;

    function LoginAction (service:LoginService, manager:UserManager = null) {
        this.service = service;
        this.manager = manager;    
    }

}
}

Note that in the example above the manager parameter is optional. Parsley reflects on this information and uses it as a hint whether the defined dependency is required or optional. So in this case the container will throw an error if it does not contain an object of type LoginService, but it will simply silently ignore the second parameter if the Context does not contain an object of type UserManager.

Constructor injection selects the dependencies based on the parameter types. This means that it only works for dependencies where you know that the Context will always contain at most one object with a matching type. It is also good practice to use interfaces as parameter types so that you can switch implementations in the configuration without modifying the class.

As explained in 3.2 MXML Configuration you cannot use simple MXML tags for configuration if you want to use Constructor Injection since in that case the MXML compiler generates the object creation code and Parsley only gets hold of the object after it was instantiated to perform additional configuration. So instead of defining such a class like this:

<Objects
    xmlns:fx="http://ns.adobe.com/mxml/2009"
    xmlns="http://www.spicefactory.org/parsley"
    xmlns:actions="com.bookstore.actions.*">
    
    <fx:Declarations>
        
        <actions:LoginAction/>
    
    </fx:Declarations>
    
</Objects> 

you should simply declare it like this:

<Objects 
    xmlns:fx="http://ns.adobe.com/mxml/2009"
    xmlns="http://www.spicefactory.org/parsley">
    
    <fx:Script>
        <![CDATA[
            import com.bookstore.actions.*;        
        ]]>
    </fx:Script>

    <fx:Declarations>
        
        <Object type="{LoginAction}"/>
        
    </fx:Declarations>
    
</Objects> 

When using Parsley's Object tag it's the framework that is responsible for instantiating the object so that Constructor Injection can be performed.

There are no restrictions when you are using XML configuration.

Unfortunately there is a nasty bug in some Flash Player versions where reflection on constructor parameter types using describeType does not work properly (the Player always reports '*' as the type in these cases. If you run into this bug the only workaround (unfortunately) is to create instances of these classes before you initialize Parsley:

new LoginAction();
new ShoppingCartAction();

You can simply throw away these instances, just creating an instance "fixes" describe type for the parameter types of that class.

4.2 Method Injection

Method Injection is similar to Constructor Injection. You can place [Inject] metadata tags on any number of methods:

package com.bookstore.actions {

class LoginAction {

    private var service:LoginService;
    private var manager:UserManager;

    [Inject]
    public function init (service:LoginService, manager:UserManager = null) : void {
        this.service = service;
        this.manager = manager;    
    }

}
}

As with Constructor Injection Parsley will recognize whether a method parameter is optional or not and accordingly treat the dependency as optional or required. The object to be injected will be selected by type, so you should make sure to include at most one object with a matching type into your configuration. For Method Injection there are no restrictions on MXML configuration, so in contrast to Constructor Injection you could also use simple MXML tags for adding the objects to the container.

4.3 Property Injection by Type

This injection mechanism is simliar to Method Injection, but is performed for properties instead:

package com.bookstore.actions {

class LoginAction {

    private var manager:UserManager;
    
    [Inject]
    public var service:LoginService;
    
    [Inject(required="false")]
    public function set manager (manager:UserManager) : void {
        this.manager = manager;
    }

}
}

You can place the [Inject] tag on a var declaration or a setter function. For properties Parsley cannot detect whether the dependency is optional or not so you can explicitly set it with the required attribute. The default is true if the attribute is omitted.

Like with Constructor or Method Injection shown above this mode select dependencies by type. So again you should make sure to include at most one object with a matching type into your configuration.

4.4 Property Injection by Id

Instead of letting the container select the dependency by type you can alternatively explicitly set the id of the object to inject:

[Inject(id="defaultLoginService")]
public var service:LoginService;

In this case Parsley will select the dependency by id so the configuration has to include an object with a corresponding id:

<Objects
    xmlns:fx="http://ns.adobe.com/mxml/2009"
    xmlns="http://www.spicefactory.org/parsley"
    xmlns:actions="com.bookstore.services.*">
    
    <fx:Declarations>
    
        <services:LoginServiceImpl id="defaultLoginService"/>
        
    </fx:Declarations>
        
</Objects> 

Often it's not the best idea to include configuration artifacts like ids into your class files. Inject metadata tags are usually more appropiate for injection by type like demonstrated in previous sections. If you have to set the id of a dependency explicitly it is often preferrable to externalize it to MXML or XML configuration like shown in the next section.

4.5 Declaring Dependencies in MXML or XML

Finally you can also declare dependencies in MXML or XML.

MXML example:

<Objects 
    xmlns:fx="http://ns.adobe.com/mxml/2009"
    xmlns="http://www.spicefactory.org/parsley">
    
    <fx:Script>
        <![CDATA[
            import com.bookstore.actions.*;        
            import com.bookstore.services.*;        
        ]]>
    </fx:Script>
    
    <fx:Declarations>

        <Object id="loginService" type="{LoginServiceImpl}">
            <Property name="timeout" value="3000"/>
        </Object>

        <Object id="userManager" type="{UserManager}"/>
   
        <Object type="{LoginAction}">
            <ConstructorArgs>
                <ObjectRef idRef="userManager"/>
            </ConstructorArgs>
            <Property name="service" idRef="loginService"/>
        </Object>
    
    </fx:Declarations>
    
</Objects> 

XML example:

<objects
    xmlns="http://www.spicefactory.org/parsley">
    
    <object id="loginService" type="com.bookstore.services.LoginServiceImpl">
        <property name="timeout" value="3000"/>
    </object>

    <object id="userManager" type="com.bookstore.services.UserManager"/>
   
    <object type="com.bookstore.actions.LoginAction">
        <constructor-args>
            <object-ref id-ref="userManager"/>
        </constructor-args>
        <property name="service" id-ref="loginService"/>
    </object>
    
</objects> 

As you see MXML and XML configuration is almost identical except for some subtle notation differences (capitalized camel-case vs. lower case names with dashes). You can set dependencies for constructor arguments or properties. For constructor arguments you could even mix it with tags for simple properties:

<constructor-args>
    <object-ref id-ref="userManager"/>
    <string>http://www.bookstore.com/services/</string>
    <uint>3000</uint>
</constructor-args>

For properties you just use the id-ref attribute instead of the value attribute to point to another object definition.

Declaring dependencies inline

If a dependency is only needed by a single object you can alternatively declare it inline:

<objects
    xmlns="http://www.spicefactory.org/parsley">
    
    <object type="com.bookstore.actions.LoginAction">
        <constructor-args>
            <object type="com.bookstore.services.UserManager"/>
        </constructor-args>
        <property name="service">
            <object type="com.bookstore.services.LoginServiceImpl">
                <property name="timeout" value="3000"/>
            </object>
        </property>
    </object>
    
</objects> 

Note that you cannot set an id for an inline object definition. The MXML example would be analogous.

4.6 Overriding Dependencies in Child Contexts

Whenever you inject by type and the dependency is not optional the Context must contain exactly one instance of a matching type. If the dependency is missing or ambiguous (with more than one matching type) the Context will throw an error.

Nevertheless, in an application with more than one Context you have the option to "override" a dependency in a child Context without causing these errors for ambiguous dependencies. This allows for more flexibility. You could, for example, add a default implementation of an interface to the root Context and still leave it up to a loaded module to install alternatives themselves.

When you declare a dependency by type the framework will look up this type in the Context the dependent object belongs to first. Only if no matching instance is found it will look up the next parent in the Context inheritance tree. This also means that the overriding only affect the objects in the child Context. It does not mean that the dependency in the parent Context gets "uninstalled". It will still be used for injections into other objects in the parent Context. So the overriding is always interpreted as a local override, not a global one.

This mechanism helps to avoid using string identifiers in [Inject] tags even though you want to add different implementations of the same interface to different child Contexts. Only when you add multiple implementations to the same Context, you must switch to using injection by id to avoid errors.

You can also add overrides for injection by id in a similar way. It is not allowed to have two or more# objects with the same id within a single Context, but it is legal to add an object with an id that is already present in a parent Context.