Quantcast
Channel: Enterprise ActionScript Development
Viewing all articles
Browse latest Browse all 3

Getting Started with Swiz AOP

$
0
0

Getting Started


In our last article we gave a brief introduction to the basic concepts of Aspect Oriented Programming. In this article we are going to dive head first into working with AOP with the Swiz framework. As discussed in the introduction to aspect oriented programing article, AOP is all about the Interception of method calls and Introducing of logic as a means to cleanly implement cross-cutting concerns in your application, utilizing a highly declarative approach. Swiz AOP gives you the powerful ability to easily configure new functionality into existing code, instead of muddying up your fundamental business logic. It is an extremely powerful methodology that Swiz makes very easy to work with.

Working with Swiz AOP can be broken down into three steps:

  1. Write the functionality that you would like applied throughout your application. This will reside in its own class that is separate from the rest of your application's logic.
  2. Apply a few metadata tags which help Swiz locate points in your application where the new functionality should execute.
  3. Add a small bit of configuration to your Swiz instance to enable the AOP magic.

 

Let's start with a simple example: logging.  The first thing we need to do is implement the logging code. The following class defines a simple method to trace a debug message.  The method containing the logic to introduce throughout the application is called “advice” in the AOP world. The first implementation is quite basic but we'll expand the functionality as we go on:

package org.swiz.demo
{
public class TestLogger
{
public function logMethodCall():void
{
trace( "[TestLogger] a method was executed!" );
}
}
}

As you can see, this is not going to do very much but it will at least let us know a method was called. Now, let's say we have a few controllers defined in a package called ‘org.foo.demo.controller'. In order to have the TestLogger's logMethodCall() execute when any method is called on the controllers in that package, we need to tell Swiz AOP how to search for the controller methods. In typical Swiz fashion, this is done via metadata such as the following:

[Before("org.foo.demo.controller.*.*")]
public function logMethodCall():void
{
trace( "[TestLogger] a method was executed!" );
}

The tag [Before] tells Swiz that we want the logging method executed before the controller method. The expression 'org.foo.demo.controller.*.*' defined in the metadata instructs Swiz to look for methods in every class in the org.foo.demo.controller package.

We have defined a simple logging class, and have configured it with the necessary AOP metadata. Next, we need to alter our Swiz configuration to enable the AOP engine to process the logging class, introducing the new functionality to our application.

<?xml version="1.0" encoding="utf-8"?>
<swiz:Swiz xmlns:mx="http://www.adobe.com/2006/mxml"
xmlns:swiz="http://swiz.swizframework.org"
xmlns:demo="org.swiz.demo.*">
<!-- configure the test logger with the new aop property -->
<swiz:aop>
<demo:TestLogger/>
</swiz:aop>

<swiz:beanProviders>
<demo:AppBeans/>
</swiz:beanProviders>

<swiz:config>
<swiz:SwizConfig eventPackages="org.swiz.demo.events.*" />
</swiz:config>
</swiz:Swiz>

The Swiz AOP beta release adds a new 'aop' property [a]to the Swiz configuration. All we need to do is add the TestLogger class under the <aop> tag. When your application starts up, Swiz will inspect the TestLogger class and find the Before metadata, then use the search pattern to locate the controller classes in order to wire in the logging functionality. Certainly there is a lot going on behind the scenes but the basic configuration is extremely simple to use. Grab the Swiz AOP SWC and give it a try to get your feet wet!

 

Using the Swiz AOP Expression Language

 

Let's dig into some more advanced topics. 

Since the primary function of Swiz's AOP framework is to locate points in your application to introduce new functionality, it needs a powerful expression language to allow the developer to define exactly where methods should be intercepted.  The basic structure of an expression is: 

[package].[class].[method]

Previously, we introduced a simple expression to match all methods in all classes in a specific package, by defining a '*' for the classes and omitted the method pattern. That same expression could have also been written as 'org.foo.demo.controller.*.*, with 'org.foo.demo.controller' representing the package, '*' for all classes, and '*' for all methods. In 'all classes, all methods' scenarios, including the second '*' is up to the developer's discretion. 

Let's say we have an Advice method that we only want to introduce to 'save' methods in our controllers. We could use the following tag to match all classes but only methods beginning with 'save':

[After('org.foo.demo.controller.*.save*')]

The locations of wildcards help determine the pattern matching behavior. Using 'save*' in the method pattern will match ONLY methods beginning with the string 'save'. The wildcard in the class pattern, means ALL classes in the package defined in the package pattern will be matched. If I would like to include all classes in all subpackages under the defined package, you must use '..' at the end of the package string, like this:

[After('org.foo.demo.controller..*.save*')]

However, if you completely omit the package pattern, Swiz assumes you want to look in ALL classes in ALL packages. The following tag would match methods beginning with 'save' anywhere in your application:

[After('save*')]

However, Swiz AOP is not limited to class/method matching based on expressions. Swiz also supports matching based on the presence of metadata tags. For example:

[Around(annotation="PreAuthorize")]

With this expression, Swiz will look for user-defined metadata on methods, in this case [PreAuthorize], to find target methods. You can even combine the expression and annotation parameters, so if I only want to intercept methods with the [PreAuthorize] metadata in the 'org.foo.demo.controller' package, I would use the following configuration:

[Around(expression=”org.foo.demo.controller.*”, annotation="PreAuthorize")]

Using annotation-based matching opens the possibility to supply information to your advice method through parameters on the user defined metadata as well. What is especially nice is that Swiz will register the user-defined metadata into it's own type caching system allowing you to take full advantage of the same typed MetadataTag system available to Custom Metadata Processors. We'll see this in practice later in this documentation.

 

Advice Method Parameters

 

In the earlier logging example, the advice method implementation was pretty basic, having no real context associated with the target method being called or the arguments supplied. A more reasonable logging implementation would most likely be able display a bit more detail. Let's look at a more advanced implementation and see what Swiz AOP is capable of supplying to your advice methods.

Swiz AOP can infer quite a bit from your advice method's signature to decide what information it should supply. Internally, Swiz AOP utilizes a dynamic method chaining approach to invoke both the advice method and the target method which was initially called. This chain is managed through various implementations of Swiz's IMethodInvocation interface, which also carries a wealth of information about the context of the method call, contained in a Method object. If we add an argument of type IMethodInvocation to the logging method, we can access this information from within the advice method body without any additional configuration. Here's an updated logging implementation using this technique:

[Before("org.foo.demo.controller.*")]
public function logMethodDetails( invocation:IMethodInvocation ):void
{
// retrieve Method object
var method:Method = invocation.getMethod();

// retrieve qualified class name of target object
var targetClass:String = getQualifiedClassName( method.getTarget() );

// retrieve method name being called
var methodName:String = method.getMethodName();

// retrieve the argument collection for the target method
var arguments:Array = method.getArgs();

trace( "[TestLogger] '" + targetClass + "." + methodName + "' called with the following arguments: " + ObjectUtil.toString(arguments) );
}

As you can see, Swiz AOP gives you direct access to quite a bit of information making this logger considerably more useful.

In the method above you see we can access the argument array for the target method via the getArgs() method on Swiz's Method class. What's more, Swiz AOP can also provide arguments from the target method call directly to your advice method. 

Let's say we have a controller with a method to save a user like the following:

public function saveUser( user:User ):void
{
// perform save logic...
}

You can have Swiz AOP supply the user being saved directly to your advice method by simply adding it to the method signature:

[Before("org.foo.demo.controller.*.saveUser")]
public function beforeUserIsSaved( user:User ):void
{
// do something with the user before it is saved...
}

Swiz will attempt to locate a User object in the target method's argument collection and pass it into your Advice method. There is no extra configuration needed, Swiz will just do the right thing and give your Advice method the User passed to the save method. However, be careful with this approach. Your Advice method will now only work on a method that has a User as one of its arguments! You can also mix these styles, and provide both the IMethodInvocation and extra parameters, such as the following, which gives you access to the method invocation details as well as direct access to the user.

[After("org.foo.demo.controller.*.saveUser")]
public function afterUserIsSaved( invocation:IMethodInvocation, user:User ):void
{
// do some logic after the user was saved
}

 

Around Advice

 

Now that we've shown a few examples of BeforeAdvice and AfterAdvice, let's move onto AroundAdvice. AroundAdvice is like a combination of both Before and After advice types, except that the advice method must decide whether the target method should actually be invoked by calling proceed() on the method invocation argument. Because of this requirement, AroundAdvice methods must define their first argument as of type IMethodInvocation. A basic implementation will look like:

[Around("org.foo.demo.controller.*.saveUser")]
public function whenUserIsSaved( invocation:IMethodInvocation ):*
{
trace( “User object is about to be saved...” );
return invocation.proceed();
}

When proceed() is called on the invocation, the chain will continue until target method is called. It's important to return the results, as most likely the target method would return something, and this assures your Advice method behaves in a similar fashion so the results flow back through the chain.

 

Accessing Target Method Metadata in Advice Methods

 

If you are using expressions based on metadata tags, Swiz can extract the information in that metadata and pass an instance of Swiz's BaseMetadataTag to your advice methods.  This will allow the advice method to inspect the payload from the metadata and execute logic based on it.

Suppose we are building a security system with AOP: we want to prevent certain methods from being invoked if the user does not have sufficient permissions.  We define a PreAuthorize metadata element on each method we want to secure and specify the name of the permission the user must have in order to invoke the method.  Swiz will allow the metadata from the target method to be passed into your advice method.  Consider the following save method and authorization advice method.

[PreAuthorize( requires=”ADMIN_ROLE” )]
public function saveUser( user:User ):void
{
// perform save logic...
}

[Around( annotation="PreAuthorize" )]
public function performAuthorization( invocation:IMethodInvocation, md:BaseMetadataTag ):*
{
var requiredRole:String = md.getArg( "requires" ).getValue();
var authorized:Boolean = AuthorizationManager.userHasRole( requiredRole );
if( authorized )
{
return invocation.proceed();
}
else
{
trace( “[AuthorizationAdvice] WARNING! Current User does not have ADMIN_ROLE!”);
return null;
}
}

Our fictitious AuthorizationManager class will perform some sort of logic to ensure the current user has the required ADMIN_ROLE (we'll leave that implementation up to the developer's imagination) but the important part is Swiz wraps up the metadata information found on the target method and supplies it to the authorization advice method as a Swiz BaseMetadataTag. This allows you to retrieve the arguments provided in the Metadata on the target method by calling getArg( argName ).getValue(). This is an extremely powerful approach, giving developers the ability to create highly configurable sub-systems which can be introduced into their applications through a simple Swiz configuration, and carry a wealth of information about the methods being intercepted. 

I hope you find Swiz AOP a powerful new addition to your toolset. You can download the beta release from www.swizframework.org and join the mailing list for more information!


Viewing all articles
Browse latest Browse all 3

Latest Images

Trending Articles





Latest Images