+ All Categories
Home > Technology > How to build an AOP framework in ActionScript

How to build an AOP framework in ActionScript

Date post: 25-May-2015
Category:
Upload: christophe-herreman
View: 881 times
Download: 2 times
Share this document with a friend
Popular Tags:
52
How to build an AOP framework (and how to lose your sanity in the process) Roland Zwaga Christophe Herreman @mechhead @herrodius
Transcript

How to buildan AOP framework

(and how to lose your sanity in the process)

Roland Zwaga Christophe Herreman @mechhead @herrodius

Agenda- Quick AOP primer- Typed Proxies- ABC- AS3Commons Bytecode- AS3Commons AOP

About usTwo geeks from Holland and Belgium Run Stack & Heap, a development and consulting company based in Belgium Core members of Spring ActionScript and AS3Commons

Aspect Oriented Programming

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

} }

AOP Primer: example "Security"

Requirement The methods getSecretData and getMoreSecretData may only be invoked by authorized users.

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");}

}...

AOP Primer: example "Security"...

public function getMoreSecretData():MyData {if (userIsAuthorized) {

// get more secret data} else {

throw new Error("User is not authorized");}

}...

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

AOP Primer: example "Security"The AOP solution: Separate the security code from the service code and merge them.

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");

}}

}}

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();

}

AOP Primer: Terminology

AspectAdvicePointcutJoinpointAdvisor

The general concept of a cross-cutting concern. In the example: "security" Logging, Error Handling, Method Run-Time Monitoring, Validation, etc. are other useful examples.

AOP Primer: Terminology

AspectAdvicePointcutJoinpointAdvisor

An extra piece of code that needs to be applied. In the example: throw an error if the user is not authorized Types of advice:

- before- after- afterThrows- around

AOP Primer: Terminology

AspectAdvicePointcutJoinpointAdvisor

A rule about when an Advice should be applied. In the example: the methods "getSecretData" and "getMoreSecretData" Other examples: all public methods of a class, all methods starting with "load", all methods that take a String parameter, ...

AOP Primer: Terminology

AspectAdvicePointcutJoinpointAdvisor

A single point in the execution of the code where Advice is applied because the rule of a Pointcut has been satisfied.

AOP Primer: Terminology

AspectAdvicePointcutJoinpointAdvisor

The combination of an Advice and a Pointcut. This term is not general AOP vocabulary. It was introduced in the (Java) Spring AOP framework and is also used in AS3Commons AOP.

Dynamic Typed Proxies

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

We asked Adobe for Typed Proxies

... but it didn't happen.

The original method

public function conferenceEvaluator(name:String):String{

if (name == '360|Flex') {return 'awesome';

}return 'meh';

}

The proxied method

override public function conferenceEvaluator(name:String):String {

return methodInvocationInterceptor.intercept(

this,InvocationKind.METHOD,new QName("", "conferenceEvaluator"),[name],super.conferenceEvaluator

);}

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!

ABCActionScript Byte Code

AS3Commons Bytecode

AS3Commons-Bytecode APIGeneral purpose ABC Bytecode API, not just aimed at AOP. ● ABCDeserializer● ABCSerializer● ByteCodeType (reflection)● AbcBuilder (emit API)● ProxyFactory (proxy API)

Let's generate this class package org {

public class Conferences(){

super();}

public function myFavoriteConference():String{

return "360|Flex!";}

}

var abcFile:AbcFile = new AbcFile();

var instanceInfo:InstanceInfo = new InstanceInfo();

instanceInfo.classMultiname = new QualifiedName("Conferences", new LNamespace(NamespaceKind.PACKAGE_NAMESPACE, "org"));

var constructor:MethodInfo = new MethodInfo();

constructor.methodName = "org.Conferences/:Conferences";

constructor.returnType = new QualifiedName("*", LNamespace.ASTERISK);

constructor.methodBody = new MethodBody();

constructor.methodBody.localCount = 1;

constructor.methodBody.initScopeDepth = 1;

constructor.methodBody.maxScopeDepth = 2;

constructor.methodBody.maxStack = 1;

constructor.methodBody.opcodes.push(Opcode.getlocal_0.op());

constructor.methodBody.opcodes.push(Opcode.pushscope.op());

constructor.methodBody.opcodes.push(Opcode.getlocal_0.op());

constructor.methodBody.opcodes.push(Opcode.constructsuper.op([0]));

constructor.methodBody.opcodes.push(Opcode.returnvoid.op());

var trait:MethodTrait = new MethodTrait();

trait.traitKind = TraitKind.METHOD;

constructor.as3commonsByteCodeAssignedMethodTrait = trait;

instanceInfo.addTrait(trait);

instanceInfo.constructor = constructor;

var staticConstructor:MethodInfo = new MethodInfo();

staticConstructor.methodName = "org.Conferences:Conferences:::Conferences$cinit";

staticConstructor.returnType = new QualifiedName("*", LNamespace.ASTERISK);

staticConstructor.methodBody = new MethodBody();

staticConstructor.methodBody.localCount = 1;

staticConstructor.methodBody.initScopeDepth = 1;

staticConstructor.methodBody.maxScopeDepth = 2;

staticConstructor.methodBody.maxStack = 1;

staticConstructor.methodBody.opcodes.push(Opcode.getlocal_0.op());

staticConstructor.methodBody.opcodes.push(Opcode.pushscope.op());

staticConstructor.methodBody.opcodes.push(Opcode.returnvoid.op());

var classInfo:ClassInfo = new ClassInfo();

classInfo.staticInitializer = staticConstructor;

var method:MethodInfo = new MethodInfo();

method.methodName = "org.Conferences/:myFavoriteConference";

method.returnType = new QualifiedName("String", LNamespace.PUBLIC);

method.methodBody.localCount = 1;

method.methodBody.initScopeDepth = 1;

method.methodBody.maxScopeDepth = 2;

method.methodBody.maxStack = 2;

method.methodBody.opcodes.push(Opcode.getlocal_0.op());

method.methodBody.opcodes.push(Opcode.pushscope.op());

method.methodBody.opcodes.push(Opcode.pushstring.op(["360|Flex!"]));

method.methodBody.opcodes.push(Opcode.returnvalue.op());

trait = new MethodTrait();

trait.traitKind = TraitKind.METHOD;

method.as3commonsByteCodeAssignedMethodTrait = trait;

instanceInfo.methodInfo.push(method);

var scriptInfo:ScriptInfo = new ScriptInfo();

var scriptInitializer:MethodInfo = new MethodInfo();

scriptInfo.scriptInitializer = scriptInitializer;

scriptInitializer.methodName = "";

scriptInitializer.returnType = new QualifiedName("*", LNamespace.ASTERISK);

scriptInitializer.methodBody.opcodes.push(Opcode.getlocal_0.op());

scriptInitializer.methodBody.opcodes.push(Opcode.pushscope.op());

scriptInitializer.methodBody.opcodes.push(Opcode.getscopeobject.op([0]));

var mn:QualifiedName = new QualifiedName("Conferences", new LNamespace(NamespaceKind.PACKAGE_NAMESPACE, "org"));

scriptInitializer.methodBody.opcodes.push(Opcode.findpropstrict.op([mn])) //

scriptInitializer.methodBody.opcodes.push(Opcode.getproperty.op([mn]));

scriptInitializer.methodBody.opcodes.push(Opcode.pushscope.op());

scriptInitializer.methodBody.opcodes.push(Opcode.popscope.op());

scriptInitializer.methodBody.opcodes.push(Opcode.newclass, [classInfo]);

scriptInitializer.methodBody.opcodes.push(Opcode.initproperty, [mn]);

scriptInitializer.methodBody.opcodes.push(Opcode.returnvoid);

abcFile.addClassInfo(classInfo);

abcFile.addScriptInfo(scriptInfo);

abcFile.addInstanceInfo(instanceInfo);

Creating the AbcFile manually, loads of fun!

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);

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!'}

The feeling after this finally works...

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!

}

ProxyFactory: Injecting interceptors function onProxyCreate(event:ProxyFactoryEvent):void {

var interceptor:IMethodInvocationInterceptor = createInterceptor();

event.methodInvocationInterceptor = interceptor;} function createInterceptor():IMethodInvocationInterceptor {

var result:IMethodInvocationInterceptor = newBasicMethodInvocationInterceptor();

//register IInterceptors...}

IInterceptor interface public interface IInterceptor {

function intercept(invocation:IMethodInvocation):void; }

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;

}

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!

AS3Commons AOP

AS3Commons AOPAdvice interfaces (some of them)

- IMethodBeforeAdvicebeforeMethod(method:Method, args:Array, target:*):void;

- IConstructorAfterAdviceafterConstructor(constructor:Constructor, args:Array,

target:*):void;

- ISetterAroundAdvicebeforeSetter(setter:Accessor, target:*, value:*):void;afterSetter(setter:Accessor):void;afterSetterThrows(setter:Accessor, value:*, target:*,

error:Error):void;

AS3Commons AOPAdvisor 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);

}

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));

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");

}}

}}

AS3Commons AOPPointcuts

- Name matching- Regular expression matching- Binary: combine pointcuts (and, or, ...)

AS3Commons AOPInterceptors

Use an interceptor if you want full control over the execution of the advice code.

public class StringToUppercaseSetterInterceptor implementsISetterInterceptor {

function interceptSetter(invocation:ISetterInvocation) {if (invocation.value is String) {

invocation.value = invocation.value.toUpperCase();}invocation.proceed();

}}

AS3Commons AOPAOPProxyFactory 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.

AS3Commons AOPAOPBatchProxyFactory Proxy factory to create multiple proxies. Used inside the AOPProxyFactory. Uses AS3Commons-Bytecode to generate the proxies. The interceptor we create is an AdvisorInterceptor.

AS3Commons AOPAdvisorInterceptor

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.

AS3Commons AOPWhat's to come and what is possible?

- Pointcut dialect & metadata/annotation driven pointcut configuration - Integration with Spring ActionScript or other Dependency Injection frameworks

More info www.as3commons.org ActionScript Virtual Machine 2 Spec: http://www.adobe.com/content/dam/Adobe/en/devnet/actionscript/articles/avm2overview.pdf Twitter

@mechhead, @herrodius, @as3commons, @stackandheap

Questions ?

Thank you !


Recommended