3. About us
Two geeks from Holland and Belgium
Run Stack & Heap, a development and consulting company
based in Belgium
Core members of Spring ActionScript and AS3Commons
5. AOP Primer: example "Security"
class MyService {
public function getData():MyData {
// get data
}
public function getSecretData():MyData {
// get secret data
}
public function getMoreSecretData():MyData {
// get more secret data
}
}
6. AOP Primer: example "Security"
Requirement
The methods getSecretData and
getMoreSecretData may only be invoked by authorized
users.
7. AOP Primer: example "Security"
...
public function getData():MyData {
// get data
}
public function getSecretData():MyData {
if (userIsAuthorized) {
// get secret data
} else {
throw new Error("User is not authorized");
}
}
...
8. AOP Primer: example "Security"
...
public function getMoreSecretData():MyData {
if (userIsAuthorized) {
// get more secret data
} else {
throw new Error("User is not authorized");
}
}
...
9. AOP Primer: example "Security"
Notice the following:
- code is "cluttered" with extra security code; lost focus
- code duplication
- the above will increase with more methods and services
What if:
- security requirements change?
- service class must be used in other (non-secure) context
10. AOP Primer: example "Security"
The AOP solution:
Separate the security code from the service code and
merge them.
11. AOP Primer: example "Security"
public class AuthorizationAdvice implements
IMethodBeforeAdvice {
public function beforeMethod( method:Method,
args:Array,
target:* ):void {
if (method.name == "getSecretData"
|| method.name == "getMoreSecretData") {
if (!userIsAuthorized) {
throw new Error("User is not authorized");
}
}
}
}
12. AOP Primer: example "Security"
var factory:AOPProxyFactory = new AOPProxyFactory();
factory.target = MyService;
factory.addAdvice(new AuthorizationAdvice());
var operation:IOperation = factory.load();
operation.addCompleteListener(factory_completeHandler);
function factory_completeHandler(event:OperationEvent):
void {
var service:MyService = factory.getProxy();
service.getData();
service.getSecretData();
}
13. AOP Primer: Terminology
Aspect The general concept of a cross-cutting
Advice concern.
Pointcut
Joinpoint In the example: "security"
Advisor
Logging, Error Handling, Method Run-Time
Monitoring, Validation, etc. are other useful
examples.
14. AOP Primer: Terminology
Aspect An extra piece of code that needs to be
Advice applied.
Pointcut
Joinpoint In the example: throw an error if the user
Advisor is not authorized
Types of advice:
- before
- after
- afterThrows
- around
15. AOP Primer: Terminology
Aspect A rule about when an Advice should be
Advice applied.
Pointcut
Joinpoint In the example: the methods
Advisor "getSecretData" and "getMoreSecretData"
Other examples: all public methods of a
class, all methods starting with "load", all
methods that take a String parameter, ...
16. AOP Primer: Terminology
Aspect A single point in the execution of the code
Advice where Advice is applied because the rule of a
Pointcut Pointcut has been satisfied.
Joinpoint
Advisor
17. AOP Primer: Terminology
Aspect The combination of an Advice and a
Advice Pointcut. This term is not general AOP
Pointcut vocabulary. It was introduced in the (Java)
Joinpoint Spring AOP framework and is also used in
Advisor AS3Commons AOP.
19. Runtime generated dynamic proxies
● A subclass of a class or an implementation of an
interface to which we'd like to add extra functionality
(aspects in AOP)
● Has a reference to an
IMethodInvocationInterceptor injected
● The (sub)class does not exist as ActionScript code.
Instead, it gets generated at runtime
● Not the same as flash.utils.Proxy
20. We asked Adobe for Typed Proxies
... but it didn't happen.
21. The original method
public function conferenceEvaluator(name:String):String
{
if (name == '360|Flex') {
return 'awesome';
}
return 'meh';
}
22. The proxied method
override public function conferenceEvaluator(name:String):
String {
return methodInvocationInterceptor.intercept
(
this,
InvocationKind.METHOD,
new QName("", "conferenceEvaluator"),
[name],
super.conferenceEvaluator
);
}
23. How the bloody hell do we do this?
● AS3eval? (eval.hurlant.com)
● flash.utils.Proxy class?
● Flemit and Floxy? (asmock.sourceforge.org)
● Loom-as3!!
Loom-as3 gets discontinued prematurely :(
Oops... Time to get our hands dirty... (and lose our sanity)
The birth of AS3Commons Bytecode!
28. AS3Commons-Bytecode API
General purpose ABC Bytecode API, not just aimed at
AOP.
● ABCDeserializer
● ABCSerializer
● ByteCodeType (reflection)
● AbcBuilder (emit API)
● ProxyFactory (proxy API)
29. Let's generate this class
package org {
public class Conferences()
{
super();
}
public function myFavoriteConference():String
{
return "360|Flex!";
}
}
30. Creating the AbcFile manually, loads of fun!
var abcFile:AbcFile = new AbcFile(); var method:MethodInfo = new MethodInfo();
var instanceInfo:InstanceInfo = new InstanceInfo(); method.methodName = "org.Conferences/:myFavoriteConference";
instanceInfo.classMultiname = new QualifiedName("Conferences", new LNamespace(NamespaceKind. method.returnType = new QualifiedName("String", LNamespace.PUBLIC);
PACKAGE_NAMESPACE, "org"));
method.methodBody.localCount = 1;
var constructor:MethodInfo = new MethodInfo();
method.methodBody.initScopeDepth = 1;
constructor.methodName = "org.Conferences/:Conferences";
method.methodBody.maxScopeDepth = 2;
constructor.returnType = new QualifiedName("*", LNamespace.ASTERISK);
method.methodBody.maxStack = 2;
constructor.methodBody = new MethodBody();
method.methodBody.opcodes.push(Opcode.getlocal_0.op());
constructor.methodBody.localCount = 1;
method.methodBody.opcodes.push(Opcode.pushscope.op());
constructor.methodBody.initScopeDepth = 1;
method.methodBody.opcodes.push(Opcode.pushstring.op(["360|Flex!"]));
constructor.methodBody.maxScopeDepth = 2;
method.methodBody.opcodes.push(Opcode.returnvalue.op());
constructor.methodBody.maxStack = 1;
trait = new MethodTrait();
constructor.methodBody.opcodes.push(Opcode.getlocal_0.op());
trait.traitKind = TraitKind.METHOD;
constructor.methodBody.opcodes.push(Opcode.pushscope.op());
method.as3commonsByteCodeAssignedMethodTrait = trait;
constructor.methodBody.opcodes.push(Opcode.getlocal_0.op());
instanceInfo.methodInfo.push(method);
constructor.methodBody.opcodes.push(Opcode.constructsuper.op([0]));
var scriptInfo:ScriptInfo = new ScriptInfo();
constructor.methodBody.opcodes.push(Opcode.returnvoid.op());
var scriptInitializer:MethodInfo = new MethodInfo();
var trait:MethodTrait = new MethodTrait();
scriptInfo.scriptInitializer = scriptInitializer;
trait.traitKind = TraitKind.METHOD;
scriptInitializer.methodName = "";
constructor.as3commonsByteCodeAssignedMethodTrait = trait;
scriptInitializer.returnType = new QualifiedName("*", LNamespace.ASTERISK);
instanceInfo.addTrait(trait);
scriptInitializer.methodBody.opcodes.push(Opcode.getlocal_0.op());
instanceInfo.constructor = constructor;
scriptInitializer.methodBody.opcodes.push(Opcode.pushscope.op());
var staticConstructor:MethodInfo = new MethodInfo();
scriptInitializer.methodBody.opcodes.push(Opcode.getscopeobject.op([0]));
staticConstructor.methodName = "org.Conferences:Conferences:::Conferences$cinit";
var mn:QualifiedName = new QualifiedName("Conferences", new LNamespace(NamespaceKind.PACKAGE_NAMESPACE,
staticConstructor.returnType = new QualifiedName("*", LNamespace.ASTERISK); "org"));
staticConstructor.methodBody = new MethodBody(); scriptInitializer.methodBody.opcodes.push(Opcode.findpropstrict.op([mn])) //
staticConstructor.methodBody.localCount = 1; scriptInitializer.methodBody.opcodes.push(Opcode.getproperty.op([mn]));
staticConstructor.methodBody.initScopeDepth = 1; scriptInitializer.methodBody.opcodes.push(Opcode.pushscope.op());
staticConstructor.methodBody.maxScopeDepth = 2; scriptInitializer.methodBody.opcodes.push(Opcode.popscope.op());
staticConstructor.methodBody.maxStack = 1; scriptInitializer.methodBody.opcodes.push(Opcode.newclass, [classInfo]);
staticConstructor.methodBody.opcodes.push(Opcode.getlocal_0.op()); scriptInitializer.methodBody.opcodes.push(Opcode.initproperty, [mn]);
staticConstructor.methodBody.opcodes.push(Opcode.pushscope.op()); scriptInitializer.methodBody.opcodes.push(Opcode.returnvoid);
staticConstructor.methodBody.opcodes.push(Opcode.returnvoid.op()); abcFile.addClassInfo(classInfo);
var classInfo:ClassInfo = new ClassInfo(); abcFile.addScriptInfo(scriptInfo);
classInfo.staticInitializer = staticConstructor; abcFile.addInstanceInfo(instanceInfo);
31. Generate a class with the emit API
var abcBuilder:IAbcBuilder = new AbcBuilder();
var classbuilder:IClassBuilder =
abcBuilder.defineClass("org.Conferences");
var methodBuilder:IMethodBuilder = classbuilder.
defineMethod("myFavoriteConference");
methodBuilder.returnType = "String";
methodBuilder.addOpcode(Opcode.getlocal_0)
.addOpcode(Opcode.pushscope)
.addOpcode(Opcode.pushstring,["360|Flex!"])
.addOpcode(Opcode.returnvalue);
32. Loading the class into the AVM
abcBuilder.addEventListener(Event.COMPLETE,
loadedHandler);
abcBuilder.buildAndLoad();
function loadedHandler(event:Event):void {
var clazz:Class =
ApplicationDomain.currentDomain.getDefinition
("org.Conferences") as Class;
var instance:* = new clazz();
var result:String = instance.myFavoriteConference();
// result == '360|Flex!'
}
34. ProxyFactory: Generating proxy
var factory:IProxyFactory = new ProxyFactory();
factory.defineProxy(Conferences);
factory.generateProxyClasses();
factory.addEventListener(
ProxyFactoryEvent.GET_METHOD_INVOCATION_INTERCEPTOR,
onProxyCreate);
factory.addEventListener(Event.COMPLETE, onComplete);
factory.buildAndLoad();
function onComplete(event:Event):void {
var conf:Conference = factory.createProxy(Conference);
// This will return the proxy class instance!
}
35. ProxyFactory: Injecting interceptors
function onProxyCreate(event:ProxyFactoryEvent):void {
var interceptor:IMethodInvocationInterceptor =
createInterceptor();
event.methodInvocationInterceptor = interceptor;
}
function createInterceptor():IMethodInvocationInterceptor
{
var result:IMethodInvocationInterceptor = new
BasicMethodInvocationInterceptor();
//register IInterceptors...
}
37. IMethodInvocation interface
public interface IMethodInvocation {
function get kind():InvocationKind;
function get targetInstance():Object;
function get targetMember():QName;
function get targetMethod():Function;
function get arguments():Array;
function get proceed():Boolean;
function set proceed(value:Boolean):void;
function get returnValue():*;
function set returnValue(value:*):void;
}
38. So, how to build an AOP framework?
● ABC - I hate myself and I want to die
● AbcBuilder - I think the emit API sucks
● Emit API - I think the proxy API sucks
● Proxy API - I love AS3Commons-Bytecode!
Pick your poison!
41. AS3Commons AOP
Advisor
Combines Pointcut (when) and Advice (what).
Actually, Advice is always wrapped in an Advisor:
// in AOPProxyFactory...
public function addAdvice(advice:IAdvice, target:*=null):void {
addAdvisor(new AlwaysMatchingPointcutAdvisor(advice),
target);
}
42. AS3Commons AOP
Adding an advisor to the proxy factory (1/2)
var factory:AOPProxyFactory = new AOPProxyFactory();
var pointcut:IPointcut = new
MethodNameMatchPointcut(["getSecretData","
getMoreSecretData"]);
var advice:IAdvice = new AuthenticationAdvice();
factory.addAdvisor(new PointcutAdvisor(pointcut, advice));
43. AS3Commons AOP
Adding an advisor to the proxy factory (2/2)
The AuthenticationAdvice can now be simplified:
public function beforeMethod( method:Method,
args:Array,
target:*):void {
if (method.name == "getSecretData"
|| method.name == "getMoreSecretData") {
if (!userIsAuthorized) {
throw new Error("User is not authorized");
}
}
}
}
45. AS3Commons AOP
Interceptors
Use an interceptor if you want full control over the execution of
the advice code.
public class StringToUppercaseSetterInterceptor implements
ISetterInterceptor {
function interceptSetter(invocation:ISetterInvocation) {
if (invocation.value is String) {
invocation.value = invocation.value.toUpperCase();
}
invocation.proceed();
}
}
46. AS3Commons AOP
AOPProxyFactory
Configure it with advice, advisors and/or interceptors and
get proxies from it.
var factory:AOPProxyFactory = new AOPProxyFactory();
factory.target = MyService;
factory.addAdvice(new AuthorizationAdvice());
... (asynchronous loading of the factory)
var service:MyService = factory.getProxy();
Hides Bytecode's ProxyFactory interceptor details.
47. AS3Commons AOP
AOPBatchProxyFactory
Proxy factory to create multiple proxies. Used inside the
AOPProxyFactory.
Uses AS3Commons-Bytecode to generate the proxies.
The interceptor we create is an AdvisorInterceptor.
48. AS3Commons AOP
AdvisorInterceptor
see Chain of Responsibility design pattern
When using an interceptor, call the proceed() method on the
interceptor if you want to move down the chain. If not, the chain
will be ended.
The framework does this for you when using Advice/Advisors.
Easier, but less control.
49. AS3Commons AOP
What's to come and what is possible?
- Pointcut dialect & metadata/annotation driven pointcut
configuration
- Integration with Spring ActionScript or other Dependency
Injection frameworks
50. More info
www.as3commons.org
ActionScript Virtual Machine 2 Spec: http://www.adobe.
com/content/dam/Adobe/en/devnet/actionscript/articles/avm2
overview.pdf
Twitter
@mechhead, @herrodius, @as3commons, @stackandheap