How to build an AOP framework in ActionScript

1,049
-1

Published on

Published in: Technology, Education
0 Comments
0 Likes
Statistics
Notes
  • Be the first to comment

  • Be the first to like this

No Downloads
Views
Total Views
1,049
On Slideshare
0
From Embeds
0
Number of Embeds
1
Actions
Shares
0
Downloads
16
Comments
0
Likes
0
Embeds 0
No embeds

No notes for slide

How to build an AOP framework in ActionScript

  1. 1. How to build an AOP framework(and how to lose your sanity in the process)Roland Zwaga Christophe Herreman@mechhead @herrodius
  2. 2. Agenda- Quick AOP primer- Typed Proxies- ABC- AS3Commons Bytecode- AS3Commons AOP
  3. 3. About usTwo geeks from Holland and BelgiumRun Stack & Heap, a development and consulting companybased in BelgiumCore members of Spring ActionScript and AS3Commons
  4. 4. Aspect Oriented Programming
  5. 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. 6. AOP Primer: example "Security"RequirementThe methods getSecretData andgetMoreSecretData may only be invoked by authorizedusers.
  7. 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. 8. AOP Primer: example "Security"... public function getMoreSecretData():MyData { if (userIsAuthorized) { // get more secret data } else { throw new Error("User is not authorized"); } }...
  9. 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 servicesWhat if:- security requirements change?- service class must be used in other (non-secure) context
  10. 10. AOP Primer: example "Security"The AOP solution:Separate the security code from the service code andmerge them.
  11. 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. 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. 13. AOP Primer: TerminologyAspect The general concept of a cross-cuttingAdvice concern.PointcutJoinpoint In the example: "security"Advisor Logging, Error Handling, Method Run-Time Monitoring, Validation, etc. are other useful examples.
  14. 14. AOP Primer: TerminologyAspect An extra piece of code that needs to beAdvice applied.PointcutJoinpoint In the example: throw an error if the userAdvisor is not authorized Types of advice: - before - after - afterThrows - around
  15. 15. AOP Primer: TerminologyAspect A rule about when an Advice should beAdvice applied.PointcutJoinpoint In the example: the methodsAdvisor "getSecretData" and "getMoreSecretData" Other examples: all public methods of a class, all methods starting with "load", all methods that take a String parameter, ...
  16. 16. AOP Primer: TerminologyAspect A single point in the execution of the codeAdvice where Advice is applied because the rule of aPointcut Pointcut has been satisfied.JoinpointAdvisor
  17. 17. AOP Primer: TerminologyAspect The combination of an Advice and aAdvice Pointcut. This term is not general AOPPointcut vocabulary. It was introduced in the (Java)Joinpoint Spring AOP framework and is also used inAdvisor AS3Commons AOP.
  18. 18. Dynamic Typed Proxies
  19. 19. Runtime generated dynamic proxies● A subclass of a class or an implementation of an interface to which wed 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. 20. We asked Adobe for Typed Proxies ... but it didnt happen.
  21. 21. The original methodpublic function conferenceEvaluator(name:String):String{ if (name == 360|Flex) { return awesome; } return meh;}
  22. 22. The proxied methodoverride public function conferenceEvaluator(name:String):String { return methodInvocationInterceptor.intercept ( this, InvocationKind.METHOD, new QName("", "conferenceEvaluator"), [name], super.conferenceEvaluator );}
  23. 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!
  24. 24. ABCActionScript Byte Code
  25. 25. AS3Commons Bytecode
  26. 26. AS3Commons-Bytecode APIGeneral purpose ABC Bytecode API, not just aimed atAOP.● ABCDeserializer● ABCSerializer● ByteCodeType (reflection)● AbcBuilder (emit API)● ProxyFactory (proxy API)
  27. 27. Lets generate this classpackage org { public class Conferences() { super(); } public function myFavoriteConference():String { return "360|Flex!"; }}
  28. 28. 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);
  29. 29. Generate a class with the emit APIvar 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);
  30. 30. Loading the class into the AVMabcBuilder.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!}
  31. 31. The feeling after this finally works...
  32. 32. ProxyFactory: Generating proxyvar 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!}
  33. 33. ProxyFactory: Injecting interceptorsfunction onProxyCreate(event:ProxyFactoryEvent):void { var interceptor:IMethodInvocationInterceptor = createInterceptor(); event.methodInvocationInterceptor = interceptor;}function createInterceptor():IMethodInvocationInterceptor{ var result:IMethodInvocationInterceptor = new BasicMethodInvocationInterceptor(); //register IInterceptors...}
  34. 34. IInterceptor interfacepublic interface IInterceptor { function intercept(invocation:IMethodInvocation):void;}
  35. 35. IMethodInvocation interfacepublic 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;}
  36. 36. 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!
  37. 37. AS3Commons AOP
  38. 38. AS3Commons AOPAdvice interfaces (some of them)- IMethodBeforeAdvice beforeMethod(method:Method, args:Array, target:*):void;- IConstructorAfterAdvice afterConstructor(constructor:Constructor, args:Array, target:*):void;- ISetterAroundAdvice beforeSetter(setter:Accessor, target:*, value:*):void; afterSetter(setter:Accessor):void; afterSetterThrows(setter:Accessor, value:*, target:*, error:Error):void;
  39. 39. AS3Commons AOPAdvisorCombines 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);}
  40. 40. AS3Commons AOPAdding 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));
  41. 41. AS3Commons AOPAdding 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"); } } }}
  42. 42. AS3Commons AOPPointcuts- Name matching- Regular expression matching- Binary: combine pointcuts (and, or, ...)
  43. 43. AS3Commons AOPInterceptorsUse an interceptor if you want full control over the execution ofthe advice code.public class StringToUppercaseSetterInterceptor implements ISetterInterceptor { function interceptSetter(invocation:ISetterInvocation) { if (invocation.value is String) { invocation.value = invocation.value.toUpperCase(); } invocation.proceed(); }}
  44. 44. AS3Commons AOPAOPProxyFactoryConfigure it with advice, advisors and/or interceptors andget 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 Bytecodes ProxyFactory interceptor details.
  45. 45. AS3Commons AOPAOPBatchProxyFactoryProxy factory to create multiple proxies. Used inside theAOPProxyFactory.Uses AS3Commons-Bytecode to generate the proxies.The interceptor we create is an AdvisorInterceptor.
  46. 46. AS3Commons AOPAdvisorInterceptorsee Chain of Responsibility design patternWhen using an interceptor, call the proceed() method on theinterceptor if you want to move down the chain. If not, the chainwill be ended.The framework does this for you when using Advice/Advisors.Easier, but less control.
  47. 47. AS3Commons AOPWhats to come and what is possible?- Pointcut dialect & metadata/annotation driven pointcutconfiguration- Integration with Spring ActionScript or other DependencyInjection frameworks
  48. 48. More infowww.as3commons.orgActionScript Virtual Machine 2 Spec: http://www.adobe.com/content/dam/Adobe/en/devnet/actionscript/articles/avm2overview.pdfTwitter@mechhead, @herrodius, @as3commons, @stackandheap
  49. 49. Questions ?
  50. 50. Thank you !

×