Note before starting: For a primer on Inversion of Control (IoC) and Dependency Injection, please read "Inversion of Control Containers and the Dependency Injection pattern" by Martin Fowler.
Prana's IoC container: an example
As an example, I took the Cairngorm Store modified by Renaun Erickson and used Prana to configure its service locator. You could imagine this application being deployed on several clients and they would all have their own AMFPHP gateway. It would be a pain if the application would have to be recompiled for each client just because the path to the remoting gateway changed. With Prana, we can specify the actual implementation of the remote services and the service locator in an external xml file called the application context (applicationContext.xml) and the IoC container will be responsible for setting things up for us. Let's see some code.
The service locator is now configured in the Services.mxml file. One of the services, the 'productService', is configured like this:
XML:
-
<renaun:RemoteObjectAMF0
-
endpoint="http://localhost/amfphp/gateway.php"
-
id="productService"
-
source="com.adobe.cairngorm.samples.store.business.ProductDelegate"
-
showBusyCursor="true"
-
makeObjectsBindable="true">
-
</renaun:RemoteObjectAMF0>
You can see that the endpoint property is pointing to a hard coded remoting gateway. As said earlier, in order to change this value for another deployed client, we would have to change the value and recomile the application. With Prana, we will just leave out the actual implementation like this:
XML:
-
<renaun:RemoteObjectAMF0 id="productService"/>
So when the application is compiled, the service locator will know about a remote service called 'productService' but it will not know anything about its implementation details.
Defining the application context
We define the external configuration of the productService as follows. This configuration is stored in an xml file, called applicationContext.xml by convention (but you can call it whatever you like):
XML:
-
<object id="productService" class="com.renaun.rpc.RemoteObjectAMF0">
-
<property name="id" value="productService"/>
-
<property name="endpoint" value="http://localhost/amfphp/gateway.php"/>
-
<property name="source" value="com.adobe.cairngorm.samples.store.business.ProductDelegate"/>
-
<property name="showBusyCursor" value="true"/>
-
<property name="makeObjectsBindable" value="true"/>
-
</object>
Every object is defined in an 'object' node. We need to give each object and identifier (the 'id' attribute) and a class name (the 'class' attribute) so that the container knows which class to instantiate. The properties of the productService are defined in 'property' nodes which all have a 'name' and 'value' attribute. You can see for instance that the 'endpoint' property will be assigned the value 'http://localhost/amfphp/gateway.php'.
Note: Prana currently supports setter injection through implicit setters and not through explicit setters. (set myPropery vs. setMyProperty)
Besides the productService, we also have to configure the service locator so that it knows it is assigned a new instance of a remote service:
XML:
-
<object id="serviceLocator" class="com.adobe.cairngorm.business.ServiceLocator" factory-method="getInstance">
-
<property name="productService">
-
<ref>productService</ref>
-
</property>
-
</object>
Notice that we have specified a 'factory-method' attribute on the definition of the service locator. This is needed because Cairngorm's ServiceLocator is a singleton which you can access through the getInstance() method. When the container finds a factory method defined, it will not invoke the constructor of the class but instead will call the specified factory method to retrieve an/the instance of the class.
The productService is defined as a property of the serviceLocator, but notice here that the property node does not have an actual value assigned. Instead, is it has a 'ref' node which describes a reference to the id of another object defined in the application context.
Note: Currently reference properties only work when the refered to object is configured before the object that is refering to it. This will be solved in later versions of Prana.
The application context is ready. All we need to do now, is let the application know that it should load the context before starting up. In the Cairngorm Store example the application starts by getting the products for the store. This is defined in the onCreationComplete handler:
Actionscript:
-
CairngormEventDispatcher.getInstance().dispatchEvent( new CairngormEvent( GetProductsEvent.EVENT_GET_PRODUCTS ) );
We will remove this here line and instead load the application context. Once the context is loaded, we can dispatch the event to get the products:
Actionscript:
-
private var _objectDefinitionsLoader:IObjectDefinitionsLoader;
-
-
private function onCreationComplete():void {
-
_objectDefinitionsLoader = new XmlObjectDefinitionsLoader();
-
_objectDefinitionsLoader.addEventListener(ObjectDefinitionsLoaderEvent.COMPLETE, onObjectDefinitionsLoaderComplete);
-
_objectDefinitionsLoader.load("../applicationContext.xml");
-
}
-
-
private function onObjectDefinitionsLoaderComplete(event:ObjectDefinitionsLoaderEvent):void {
-
CairngormEventDispatcher.getInstance().dispatchEvent( new CairngormEvent( GetProductsEvent.EVENT_GET_PRODUCTS ) );
-
}
That's it. By loading the application context, all objects will be wired together behind the scenes. If wanted, we could access the container and request the productService from it:
Actionscript:
-
var container:ObjectContainer = _objectDefinitionsLoader.container;
-
var productService:RemoteObjectAMF0 = container.getObject("productService");
Other Prana capabilities
The Dependency Injection method in the example above is Setter Injection. Prana however also supports Constructor Injection. An example object definition for a constructor defined as Person(name:String = "", age:int = 0, isMarried:Boolean = false) is:
XML:
-
<object id="johnDoe" class="be.indiegroup.prana.ioc.testclasses.Person">
-
<constructor-arg value="John Doe"/>
-
<constructor-arg value="36"/>
-
<constructor-arg value="true"/>
-
</object>
Another thing is that you can (recursively) define properties as arrays or objects like this:
XML:
-
<object id="testObject" class="be.indiegroup.prana.ioc.testclasses.TestObject">
-
<property name="anArray">
-
<array>
-
<value>stringValue</value>
-
<value>13</value>
-
<value>true</value>
-
<array>
-
<value>12</value>
-
<value>test</value>
-
</array>
-
</array>
-
<property>
-
<property name="anObject">
-
<object>
-
<property name="key1" value="value1"/>
-
<property name="key2" value="35"/>
-
<property name="key3" value="false"/>
-
<property name="key4">
-
<array>
-
<value>12</value>
-
<value>test</value>
-
</array>
-
</property>
-
</object>
-
</property>
-
</object>
Download Prana 0.1
I hope you enjoyed this primer on Prana. Below is the Prana Framework available for download. It has the Prana.swc file, sources and docs. Please note that documentation is far from complete at this moment. The other download is the Cairngorm Store, modified by Renaun Erickson to use AMFPHP and modified by myself to use Prana. You can download them here:
Prana Framework 0.1
Prana Framework 0.1.1
Prana Framework 0.1.1 with dependencies
Cairngorm Store Prana Example
- Download Prana on SourceForge: Prana SourceForge page
Have fun!
Add to Bloglines -
Digg This! -
del.icio.us -
Stumble It! -
Twit This! -
Technorati links -
Share on Facebook -
Feedburner
Recent Comments