As part of an effort to create enterprise components, libraries, and architectures, a company may want to take advantage of reflection. For example, a company may want a consistent way to create and populate various components. This might allow your developers to work more quickly since all of the components are handled in the same manner. However, many of the components in Flex (and, likely, many of a company’s custom components), have differences in how they’re created and populated … even when the functionality is conceptually similar. In order to accomplish a consistent interface, you may need some abstraction that allows you to dynamically create and populate objects at runtime. To that end, I thought it might be helpful to discuss the low-level how-to’s of reflection within Flex / ActionScript.
The Flex framework includes an implementation of the Class Factory pattern in mx.core.ClassFactory. This class allows you to instantiate classes determined at runtime.
Here are a couple of examples of how to instantiate a class determined at runtime:
INSTANTIATE AN OBJECT FROM IT’S CLASS
import mx.core.ClassFactory;
public static function instantiateUsingClass(classToInstantiate:Class):* {
var myClassFactory : ClassFactory = new ClassFactory(classToInstantiate);
var myObjectInstance : * = myClassFactory.newInstance();
return myObjectInstance;
}
INSTANTIATE AN OBJECT FROM IT’S CLASS NAME (i.e., from a String like “com.domain.Person”)
import flash.utils.getDefinitionByName;
import mx.core.ClassFactory;
public static function instantiateUsingClassName(className:String):* {
var classToInstantiate : Class = getDefinitionByName(className) as Class;
var myClassFactory : ClassFactory = new ClassFactory(classToInstantiate);
var myObjectInstance : * = myClassFactory.newInstance();
return myObjectInstance;
}
At a basic level, mx.core.ClassFactory does the following to instantiate the class:
public var generator:Class;
...
this.generator = classToInstantiate;
...
var instance:Object = new generator();
There are other times when you need more than to simply instantiate a class. For example, you may need to query the class definition to find out what methods, properties, etc. are declared on that class. The flash.utils package provides some package-level functions for introspecting classes and objects. Here is a link to the flash.utils API: http://livedocs.adobe.com/flex/3/langref/flash/utils/package.html. For the purposes of reflection, the describeType() method is one of the best places to start (the call to getDefinitionByName() that was used above is another package-level function provided by flash.utils). Here is a way to find out what “accessor” methods (get/set methods) are declared for a class:
RETRIEVE NAMES OF ACCESSOR METHODS FOR A GIVEN CLASS
import flash.utils.describeType;
public static function getNonstaticAccessors(classOfInterest:Class):Array {
var xmlDescriptionOfClass:XML = describeType(classOfInterest);
var nonstaticAccessorsXML:XMLList = xmlDescriptionOfClass.factory.accessor;
var accessors:Array = [];
for each (var accessorXML:XML in nonstaticAccessorsXML) {
accessors.push(accessorXML.@name);
}
return accessors;
}
In order to obtain the static accessors, use xmlDescriptionOfClass.accessor, instead of xmlDescriptionOfClass.factory.accessor. In addition to @name, you might also be interested in information contained in @access, @type, and @declaredBy.
When you modify a public property on an object using dot notation (i.e., myPerson.name = “Fred”;), Flex does exactly what you’d expect and modifies the public property value. But, Flex offers an additional convenience. If you decide, at a later time, that you’d like to make the properties private and force them to be accessed through accessor methods (i.e., public function set name(myName:String):void {…}), Flex will automatically call your accessor method (without having to change your code to call the accessor, instead of the property). Hooray!
When it comes to reflection, however, the factual separation between properties (class attributes … class variables … whatever term you prefer) and accessors is maintained. The point of this tangent is that your public properties (that do not have declared accessor methods) will not be returned by the above method. Instead, in order to discover the properties of a given class, you’ll need to query for them specifically (using the “.variable” notation).
Here is an example of how to query for the attributes of a class:
RETRIEVE NAMES OF PROPERTIES (CLASS ATTRIBUTES) FOR A GIVEN CLASS
import flash.utils.describeType;
public static function getProperties(classOfInterest:Class):Array {
var xmlDescriptionOfClass:XML = describeType(classOfInterest);
var nonstaticPropertiesXML:XMLList = xmlDescriptionOfClass.factory.variable;
var properties:Array = [];
for each (var propertyXML:XML in nonstaticPropertiesXML) {
var property:Property = new Property( propertyXML.@name,
propertyXML.@type.toString(),
isStatic );
properties.push(propertyXML.@name);
}
return properties;
}
In order to obtain the static accessors, use xmlDescriptionOfClass.variable, instead of xmlDescriptionOfClass.factory.variable. In addition to @name, you might also be interested in information contained in @type.
Accessor methods (discussed earlier) are also kept separate from all of the other methods. Here is an example of how to query for the non-accessor methods of a given class
RETRIEVE NAMES OF METHODS FOR A GIVEN CLASS
import flash.utils.describeType;
public static function getMethods(classOfInterest:Class):Array {
var xmlDescriptionOfClass:XML = describeType(classOfInterest);
var nonstaticMethodsXML:XMLList = xmlDescriptionOfClass.factory.method;
var methods:Array = [];
for each (var methodXML:XML in nonstaticMethodsXML) {
methods.push(methodXML.@name);
}
return methods;
}
In order to obtain the static accessors, use xmlDescriptionOfClass.method, instead of xmlDescriptionOfClass.factory.method. In addition to @name, you might also be interested in information contained in @type.
OK, great! We now have a few tools (I’m sure you’ll discover more as you begin experimenting with the reflection capabilities provided by Flex) to help us with introspection of ActionScript classes at runtime. But, hey, maybe we actually want to DO something with that information. Here is an example of how to trigger a method call on a class and method discovered at runtime:
INVOKE A METHOD ON AN INSTANTIATED OBJECT
public static function invokeMethod(objectContainingMethod:*, methodName:String, parms:Array):* {
var method:Function = objectContainingMethod[methodName];
var returnValue:* = method.apply(objectContainingMethod, parms);
return returnValue;
}
= = = = = = = = = =
Now that I’ve taken your time discussing some basics of how to implement reflection within Flex / ActionScript, I’m now going to suggest that you do it differently!
I was discussing writing a blog on reflection awhile back with Justin Shacklette (Justin’s blog) and Jon Rose (Jon’s blog). Justin immediately pointed out that Spring was splitting their reflection functionality out into a separate library. This reflection library improves on some of the above techniques by implementing work-arounds for some known bugs, etc., etc. behind the scenes. In addition, it allows you to deal with objects that might be a bit more intuitive than the XML object that is returned by describeType(). On the other hand, this means that you’ll occasionally need to step down through the object hierarchy to find the desired value. I found it pretty easy to investigate the various classes and source code at this site: http://as3-commons.googlecode.com/svn/trunk/as3-commons-reflect/src/main/actionscript/org/as3commons/reflect/. The best classes to start with are Type.as and ClassUtils.as.
To get started, download the AS3 Commons Reflection library (as3commons-reflect-1.0.0.swc) from http://code.google.com/p/as3-commons/.
Add this SWC file to your Flex project’s lib folder.
Now, let’s revisit the above examples to see how they might differ when using the AS3 Commons Reflection library.
INSTANTIATE AN OBJECT FROM IT’S CLASS
import org.as3commons.reflect.ClassUtils;
public static function instantiateUsingClass(classToInstantiate:Class):* {
var myObjectInstance:* = ClassUtils.newInstance(classToInstantiate);
return myObjectInstance;
}
INSTANTIATE AN OBJECT FROM IT’S CLASS NAME (a String like “com.domain.Person”)
import org.as3commons.reflect.ClassUtils;
public static function instantiateUsingClassName(className:String):* {
var classToInstantiate:Class = ClassUtils.forName(className);
var myObjectInstance:* = ClassUtils.newInstance(classToInstantiate);
return myObjectInstance;
}
RETRIEVE NAMES OF ACCESSOR METHODS FOR A GIVEN CLASS
public static function getStaticAccessors(classOfInterest:Class):Array {
var type:Type = Type.forClass(classOfInterest);
var accessors:Array = type.accessors;
return accessors;
}
RETRIEVE NAMES OF PROPERTIES (CLASS ATTRIBUTES) FOR A GIVEN CLASS
public static function getProperties(classOfInterest:Class):Array {
var type:Type = Type.forClass(classOfInterest);
var nonstaticProperties:Array = type.variables;
var staticProperties:Array = type.staticVariables;
var properties:Array = nonstaticProperties.concat(staticProperties);
return properties;
}
RETRIEVE METHODS DECLARED BY A GIVEN CLASS
public static function getMethods(classOfInterest:Class):Array {
var type:Type = Type.forClass(classOfInterest);
var methods:Array = type.methods;
return methods;
}
INVOKE A METHOD ON AN INSTANTIATED OBJECT
public static function invokeMethod(objectContainingMethod:*, methodName:String, parms:Array):* {
var methodInvoker:MethodInvoker = new MethodInvoker();
methodInvoker.target = objectContainingMethod;
methodInvoker.method = methodName;
methodInvoker.arguments = parms;
var returnValue:* = methodInvoker.invoke();
return returnValue;
}
Ultimately, the AS3 Commons Reflection library makes a few things easier or more intuitive. Moreover, it handles some complexities and bug work-arounds for you. But, the nuts-and-bolts are still basically using the the same techniques that I discussed in the upper-half of this post.