glad to help, lemme know how you're faring.
later,
ROland
P.S. Oh, and do check out http://www.springactionscript.org again, its just had a huge update today with the latest version of all the docs.
Printable View
glad to help, lemme know how you're faring.
later,
ROland
P.S. Oh, and do check out http://www.springactionscript.org again, its just had a huge update today with the latest version of all the docs.
Woop!
So, I managed to get my Injection adapter working a little while ago.. for the most part.
useValue is sorted out (extending DefaultListableObjectFactory and throwing the value/instance in the singletonCache worked perfectly), but now I'm having trouble with the singletons: dependencies aren't being injected on lazily instantiated singletons.
In essence I think it comes down to this: things work out when I instantiate objects and then inject into them manually (using injectInto), but objects that get constructed by the Spring container, don't end up having their dependencies resolved - Spring obviously ignores my [Inject] annotations.
I tried to work around this by inspecting class metadata for dependencies when I set up injection rules through my adapter. But how Spring is supposed to figure out my named injection points I'm not sure :(
Here's how it's looking:
Code:package net.boyblack.robotlegs.adapters
{
import net.boyblack.robotlegs.core.IInjector;
import org.as3commons.lang.ClassUtils;
import org.as3commons.lang.ObjectUtils;
import org.as3commons.reflect.Field;
import org.as3commons.reflect.MetaData;
import org.as3commons.reflect.Type;
import org.springextensions.actionscript.ioc.ObjectDefinition;
import org.springextensions.actionscript.ioc.ObjectDefinitionScope;
import org.springextensions.actionscript.ioc.factory.support.RobotLegsObjectFactory;
public class SpringInjector implements IInjector
{
protected var factory:RobotLegsObjectFactory;
public function SpringInjector()
{
factory = new RobotLegsObjectFactory();
}
public function bindValue(whenAskedFor:Class, useValue:Object, named:String = null):void
{
var whenAskedForClassName:String = ClassUtils.getFullyQualifiedName(whenAskedFor);
var useClassName:String = ObjectUtils.getFullyQualifiedClassName(useValue);
var key:String = named ? named : whenAskedForClassName;
registerObjectDefinition(whenAskedForClassName, useClassName, named, ObjectDefinitionScope.SINGLETON);
factory.cacheSingletonValue(key, useValue);
}
public function bindClass(whenAskedFor:Class, instantiateClass:Class, named:String = null):void
{
var whenAskedForClassName:String = ClassUtils.getFullyQualifiedName(whenAskedFor);
var useClassName:String = ClassUtils.getFullyQualifiedName(instantiateClass);
var dependencies:Array = findDependencies(instantiateClass);
registerObjectDefinition(whenAskedForClassName, useClassName, named, ObjectDefinitionScope.PROTOTYPE, dependencies);
}
public function bindSingleton(whenAskedFor:Class, named:String = null):void
{
var whenAskedForClassName:String = ClassUtils.getFullyQualifiedName(whenAskedFor);
var useClassName:String = whenAskedForClassName;
var dependencies:Array = findDependencies(whenAskedFor);
registerObjectDefinition(whenAskedForClassName, useClassName, named, ObjectDefinitionScope.SINGLETON, dependencies);
}
public function bindSingletonOf(whenAskedFor:Class, useSingletonOf:Class, named:String = null):void
{
var whenAskedForClassName:String = ClassUtils.getFullyQualifiedName(whenAskedFor);
var useClassName:String = ClassUtils.getFullyQualifiedName(useSingletonOf);
var dependencies:Array = findDependencies(useSingletonOf);
registerObjectDefinition(whenAskedForClassName, useClassName, named, ObjectDefinitionScope.SINGLETON, dependencies);
}
public function injectInto(target:Object):void
{
var type:Type = Type.forInstance(target);
var fields:Array = type.fields;
for each (var field:Field in fields)
{
if (field.hasMetaData('Inject'))
{
var named:String = null;
var mds:Array = field.getMetaData('Inject');
for each (var md:MetaData in mds)
{
if (md.hasArgumentWithKey('name'))
{
named = md.getArgument('name').value;
break;
}
else if (md.hasArgumentWithKey('id'))
{
named = md.getArgument('id').value;
break;
}
}
named = named ? named : field.type.fullName;
target[field.name] = factory.getObject(named);
}
}
}
public function unbind(clazz:Class, named:String = null):void
{
named = named ? named : ClassUtils.getFullyQualifiedName(clazz);
factory.removeObjectDefinition(named);
}
protected function registerObjectDefinition(whenAskedForClassName:String, useClassName:String, named:String, scope:ObjectDefinitionScope, dependencies:Array = null):void
{
var objdef:ObjectDefinition = new ObjectDefinition(useClassName);
var key:String = named ? named : whenAskedForClassName;
objdef.dependsOn = dependencies ? dependencies : objdef.dependsOn;
objdef.scope = scope;
factory.registerObjectDefinition(key, objdef);
}
protected function findDependencies(clazz:Class):Array
{
var type:Type = Type.forClass(clazz);
var fields:Array = type.fields;
var dependencies:Array = new Array();
for each (var field:Field in fields)
{
if (field.hasMetaData('Inject'))
{
var named:String = null;
var mds:Array = field.getMetaData('Inject');
for each (var md:MetaData in mds)
{
if (md.hasArgumentWithKey('name'))
{
named = md.getArgument('name').value;
break;
}
else if (md.hasArgumentWithKey('id'))
{
named = md.getArgument('id').value;
break;
}
}
named = named ? named : field.type.fullName;
dependencies.push(named);
}
}
trace('dependencies for ' + clazz + ' : ' + dependencies);
return dependencies;
}
}
}
You will have to construct those dependencies yourself and add them to the objectdefinition.
So, take a good look at the ObjectDefinition sources, all dependency and injection information can be stored in there.
One thing you have remember, if you are injecting another object (one that is managed by your container) into a property or whatever, add this to the ObjectDefinition as a RuntimeObjectReference. This is an implementation of the IObjectReference interface, which only needs to have one property: objectName.
This objectName is the name with which an object is registered in the container, by adding using this RuntimeObjectReference in your ObjectDefinitions the container will understand that it needs to retrieve the object from itself, through the usual getObject() pipeline.
I created a graphical view of the getObject flow in the reference docs:
http://www.springactionscript.org/docs/reference/html/container-documentation.html#a_graphical_overview_of_the_get object_flow
I hope this helps, good luck to you,
Roland
Thank you Roland, you rock! All I needed to do was set some RuntimeObjectReferences on the properties object of the ObjectDefinition.
For anyone who's interested, here's how to use Spring "Guice/SmartyPants style" (I'm sure it can be improved though):
And the Object Factory:Code:package net.boyblack.robotlegs.adapters
{
import net.boyblack.robotlegs.core.IInjector;
import org.as3commons.lang.ClassUtils;
import org.as3commons.lang.ObjectUtils;
import org.as3commons.reflect.Field;
import org.as3commons.reflect.MetaData;
import org.as3commons.reflect.Type;
import org.springextensions.actionscript.ioc.ObjectDefinition;
import org.springextensions.actionscript.ioc.ObjectDefinitionScope;
import org.springextensions.actionscript.ioc.factory.config.RuntimeObjectReference;
import org.springextensions.actionscript.ioc.factory.support.RobotLegsObjectFactory;
public class SpringInjector implements IInjector
{
protected var factory:RobotLegsObjectFactory;
public function SpringInjector()
{
factory = new RobotLegsObjectFactory();
}
public function bindValue(whenAskedFor:Class, useValue:Object, named:String = null):void
{
var whenAskedForClassName:String = ClassUtils.getFullyQualifiedName(whenAskedFor);
var useClassName:String = ObjectUtils.getFullyQualifiedClassName(useValue);
var key:String = named ? named : whenAskedForClassName;
registerObjectDefinition(whenAskedForClassName, useClassName, named, ObjectDefinitionScope.SINGLETON);
factory.cacheSingletonValue(key, useValue);
}
public function bindClass(whenAskedFor:Class, instantiateClass:Class, named:String = null):void
{
var whenAskedForClassName:String = ClassUtils.getFullyQualifiedName(whenAskedFor);
var useClassName:String = ClassUtils.getFullyQualifiedName(instantiateClass);
var properties:Object = findProperties(instantiateClass);
registerObjectDefinition(whenAskedForClassName, useClassName, named, ObjectDefinitionScope.PROTOTYPE, properties);
}
public function bindSingleton(whenAskedFor:Class, named:String = null):void
{
var whenAskedForClassName:String = ClassUtils.getFullyQualifiedName(whenAskedFor);
var useClassName:String = whenAskedForClassName;
var properties:Object = findProperties(whenAskedFor);
registerObjectDefinition(whenAskedForClassName, useClassName, named, ObjectDefinitionScope.SINGLETON, properties);
}
public function bindSingletonOf(whenAskedFor:Class, useSingletonOf:Class, named:String = null):void
{
var whenAskedForClassName:String = ClassUtils.getFullyQualifiedName(whenAskedFor);
var useClassName:String = ClassUtils.getFullyQualifiedName(useSingletonOf);
var properties:Object = findProperties(useSingletonOf);
registerObjectDefinition(whenAskedForClassName, useClassName, named, ObjectDefinitionScope.SINGLETON, properties);
}
public function injectInto(target:Object):void
{
var type:Type = Type.forInstance(target);
var fields:Array = type.fields;
for each (var field:Field in fields)
{
if (field.hasMetaData('Inject'))
{
var named:String = null;
var mds:Array = field.getMetaData('Inject');
for each (var md:MetaData in mds)
{
if (md.hasArgumentWithKey('name'))
{
named = md.getArgument('name').value;
break;
}
else if (md.hasArgumentWithKey('id'))
{
named = md.getArgument('id').value;
break;
}
}
named = named ? named : field.type.fullName;
target[field.name] = factory.getObject(named);
}
}
}
public function unbind(clazz:Class, named:String = null):void
{
named = named ? named : ClassUtils.getFullyQualifiedName(clazz);
factory.removeObjectDefinition(named);
}
protected function registerObjectDefinition(whenAskedForClassName:String, useClassName:String, named:String, scope:ObjectDefinitionScope, properties:Object = null):void
{
var objdef:ObjectDefinition = new ObjectDefinition(useClassName);
var key:String = named ? named : whenAskedForClassName;
objdef.properties = properties ? properties : objdef.properties;
objdef.scope = scope;
factory.registerObjectDefinition(key, objdef);
}
protected function findProperties(clazz:Class):Object
{
var type:Type = Type.forClass(clazz);
var fields:Array = type.fields;
var properties:Object = new Object();
for each (var field:Field in fields)
{
if (field.hasMetaData('Inject'))
{
var named:String = null;
var mds:Array = field.getMetaData('Inject');
for each (var md:MetaData in mds)
{
if (md.hasArgumentWithKey('name'))
{
named = md.getArgument('name').value;
break;
}
else if (md.hasArgumentWithKey('id'))
{
named = md.getArgument('id').value;
break;
}
}
named = named ? named : field.type.fullName;
properties[field.name] = new RuntimeObjectReference(named);
}
}
return properties;
}
}
}
Code:package org.springextensions.actionscript.ioc.factory.support
{
import org.springextensions.actionscript.ioc.IObjectDefinition;
public class RobotLegsObjectFactory extends DefaultListableObjectFactory
{
public function RobotLegsObjectFactory()
{
super();
}
public function cacheSingletonValue(objectName:String, value:Object):void
{
singletonCache[objectName] = value;
}
}
}
Hey Shaun,
I'm happy to see you got everything working fairly quickly, cheers on that!
I already had a feeling you were the Shaun from 'RobotLegs' fame :) Really nice to see this type of integration, thanks!
greets,
Roland
Couldn't have done it without your help!
As for "RobotLegs fame"... ha! I have a sneaking suspicion that I've completely missed the point with my framework, and done something along the lines of turning an IoC container back into a Service Locator! Actually, I started RobotLegs with the hopes that someone else (read: someone more skilled) would pick it up and run with it. In the meantime, I'll keep playing around, and hopefully come up with something useful.
Anyhow, looking forward to the Spring ActionScript 0.8 release. Best of luck with that.
PS: great job on the documentation for the project - very comprehensive and useful.
I haven't gotten a chance to take an in-depth look at your framework, so I can't honestly say that you missed the point :)
From what I gleaned from it, it seems you're taking the metadata based injection approach, which isn't bad at all, just a matter of preference or convenience. The interface itself seems quite friendly at first sight.
But even if, in your opinion, you've missed the point in hindsight, you still gave yourself a bunch of valuable experience. Knowing and understanding 'the wrong way' is very good to know as well :)
greets,
Roland