This chapter lists several features that go beyond the configuration features listed in previous chapters.
Sometimes configuring the target object directly may not be sufficient. Maybe you want to execute some complex logic on object creation that would be difficult to setup declaratively. In these cases you can use a factory instead. The factory is a normal AS3 class that you can configure like any other class in Parsley, using Metadata, MXML or XML tags. The only difference is that one method is marked as a factory method (again using Metadata, MXML or XML):
class CategoryFactory {
public var categoryName:String;
[Inject]
public var bookManager:BookManager;
[Factory]
public function createCategory () : Category {
var books:Array = bookManager.getBooksForCategory(categoryName);
return new Category(categoryName, books);
}
}
You can then use this factory in your configuration, for example in MXML:
<mx:Object
xmlns:mx="http://www.adobe.com/2006/mxml"
xmlns:model="com.bookstore.model.*">
<model:BookManager/>
<model:CategoryFactory id="historyCategory" categoryName="history"/>
<model:CategoryFactory id="politicsCategory" categoryName="politics"/>
<model:CategoryFactory id="artsCategory" categoryName="arts"/>
</mx:Object>
Each of the factories we defined above will get the BookManager instance injected and then produce
instances of Category.
The special thing about using factories is that under the hood Parsley actually
creates two object definitions for each factory: One for the factory itself and one for the type the
factory method produces. This means that you can also place metadata tags on the target type (in this case the
Category class) if you want the object that the factory produces to send and receive application messages
managed by Parsley for example.
This also means that you can use the factory and the objects it creates at injection points, although in most cases you'll be interested in the objects produced by the factory:
[Inject(id="historyCategory")]
public var historyBooks:Category;
It is recommended not to use factory methods with a return type of Object like this:
[Factory]
public function createInstance () : Object {
It would work, Parsley would happily process this kind of factory method. But you'll lose some of Parsley's useful capabilities, since it uses the return type of the method for producing the object definition for the target type. If the target type is just Object, the metadata tags on the objects this factory actually produces would not be processed, since this happens before the factory method will be invoked for the first time. Furthermore you cannot use objects produced by such a factory for Dependency Injection by Type, since the type can only be determined dynamically. You would then be constrained to Injection by Id.
Of course, like with most other Parsley features, you may also declare the factory method in
MXML or XML. This may come in handy in some edge cases, for example for a factory that has more than
one method that produces objects. In this case placing metadata tags in the class itself would not
be possible (only one [Factory] tag is allowed).
MXML Example
<Object id="monkey" type="{ZooFactory}">
<Factory method="createMonkey"/>
</Object>
<Object id="polarBear" type="{ZooFactory}">
<Factory method="createPolarBear"/>
</Object>
XML Example
<object id="monkey" type="com.example.ZooFactory">
<factory method="createMonkey"/>
</object>
<object id="polarBear" type="com.example.ZooFactory">
<factory method="createPolarBear"/>
</object>
A lot of operations in the Flash Player execute asynchronously. So it might happen that you want an object
configured in the Parsley IOC Container to load some data or assets first, before the rest of the Context gets
initialized and before this asynchronously initializing object gets injected into other objects. In this cases
you can use the [AsyncInit] tag on any EventDispatcher that fires events when the initialization
is completed (or if it has failed):
[AsyncInit]
public class FileLoader extends EventDispatcher {
public var filename:String;
[PostConstruct]
public function init () : void {
var loader:URLLoader = new URLLoader();
loader.addEventListener(Event.COMPLETE, fileLoaded);
loader.addEventListener(IOErrorEvent.IO_ERROR, handleError);
loader.addEventListener(SecurityErrorEvent.SECURITY_ERROR, handleError);
loader.load(new URLRequest(filename));
}
private function fileLoaded (event:Event) : void {
// handle loaded file
dispatchEvent(new Event(Event.COMPLETE));
}
private function handleError (event:ErrorEvent) : void {
dispatchEvent(new ErrorEvent(ErrorEvent.ERROR, false, false, event.text));
}
}
In the example above the [AsyncInit] tag tells the framework that this is an asynchronously initializing
object. In the method annotated with [PostConstruct] which will be invoked after configuration and
dependency injection has been processed for that object (see 6.5 Context Lifecycle for details) we start the
loading process. Depending on whether the loading succeeds or not we then dispatch either an Event.COMPLETE
or an ErrorEvent.ERROR. The container will listen for those two events. In case of Event.COMPLETE it
will proceed with Context initialization. In case of ErrorEvent.ERROR the whole Context initialization process
will be cancelled.
Switching event types
Event.COMPLETE and ErrorEvent.ERROR are the default event types to signal whether initialization
has completed or failed. They can be switched with attributes of the [AsyncInit] tag:
[AsyncInit(completeEvent="myCustomCompleteEvent",errorEvent="myCustomErrorEvent")]
Initialization order
If you use more than one asynchronously initializing object and one depends on the other you may want to explicitly specify the initialization order. You can do that with the order attribute:
[AsyncInit(order="1")]
The default value if you omit this attribute is int.MAX_VALUE so that all objects without an order attribute
will execute last and in arbitrary order. The attribute can be set to any positive or negative integer value.
Of course you can also omit all the metadata tags and declare everything in MXML or XML:
MXML Example
<Object id="welcomeText" type="{FileLoader}">
<AsyncInit order="1"/>
<PostConstruct method="init"/>
<Property name="filename" value="welcome.txt"/>
</Object>
XML Example
<object id="welcomeText" type="com.example.FileLoader">
<async-init order="1"/>
<post-construct method="init"/>
<property name="filename" value="welcome.txt"/>
</object>
If you want the Parsley Container to invoke methods on your object when it is created or when it is destroyed,
you can add the [PostConstruct] or [PreDestroy] metadata tags to the corresponding methods:
[PostConstruct]
public function init () : void {
[...]
}
[PreDestroy]
public function dispose () : void {
[...]
}
The methods marked with [PostConstruct] get invoked after the object has been instantiated and
all injections have been processed.
The methods marked with [PreDestroy] get invoked after the Context instance they belong to has been
destroyed with Context.destroy(). See 6.5 Context Lifecycle for details.
For Flex Components declared in regular MXML files and wired to the Context as described in 7 Flex Component Wiring
the lifecycle is different: For those objects the methods get invoked whenever the object is added to or removed from the
stage respectively. Of course the [PreDestroy] method additionally gets invoked if the Context the object
was wired to was destroyed.
In larger applications you may want to split your application into modules which are loaded on demand. In this case it would be unfortunate to have a monolithic Context that is fully loaded and initialized at application startup. Even splitting the configuration into multiple files like shown in 3.5 Combining multiple Configuration mechanisms won't help since those files will be merged into a single Context and loaded and initialized as if it was a single large configuration file.
This is where another feature of Parsley comes in handy: when creating a Context
you can define an existing context as its parent with optional parameters of all the various context builder methods
shown in 3 Configuration and Initialization:
var parent:Context = ...;
FlexContextBuilder.build(BookStoreConfig, parent);
or:
var parent:Context = ...;
XmlContextBuilder.build("config.xml", parent);
The parent context may be the main context loaded at application startup and the child context may be one required by a module loaded on demand. In the configuration for the child Context you can use any object from the parent Context for Dependency Injection (but not vice versa).
When an application module is unloaded you can destroy the child Context
(for details see 6.5 Context Lifecycle) without affecting the parent. You can create deeply
nested hierarchies of contexts, but often the structure would be rather flat, with one
root context and any number of child contexts on the same level.
If you are using Flex Modules for modularization it will be even easier. Parsley 2 comes with special integration features as described in 8 Using Flex Modules.
If you load multiple Context instances as modules like described
in the previous section, you may want to get rid of them when you unload a module. Actually
that is quite easy, just do this:
context.destroy();
When using the Parsley support for Flex Modules as described in 8 Using Flex Modules it is even easier. You don't have to explicitly destroy the Context then, it will happen automatically when the Flex Module gets unloaded.
When a Context gets destroyed the following actions occur:
MessageRouter. This affects all elements annotated with MessageHandler, MessageBinding
or MessageInterceptor. ManagedEvents they will be ignored from now
on and no longer dispatched trough Parsleys MessageRouter. [PreDestroy] (or the corresponding MXML or XML tags)
on any object of the destroyed Context get invoked. destroy. Any subsequent method invocations
on that Context throw Errors. The parent of the destroyed Context (if any) is not affected and may continue
to operate normally.