Implementing an AOP Framework, Part 2

An entry about arcitechture | design patterns Publication date 16. February 2008 11:58

It’s time for the third part of my ongoing series on Aspect Oriented Programming (here's part one and part two), and in this part I’ll be implementing the proposed AOP framework that I described in the previous post. I’ll cover the major parts of the framework and show how some of the core functionality was implemented, and if you want the full story you can download the full source code at the end.

Generating the Proxy

Remembering the architecture described last time; at the core of the API sits the Weaver, which is responsible for generating the transparent proxy that hijacks the invocations for a given instance so that we can apply advice to its members. The Weaver, then, needs to be able to generate and compile code at run time. There are essentially two ways to do this in .NET – the System.CodeDom and the System.Reflection.Emit APIs. The CodeDom, short for Code Document Object Model, allows us to build a representation of the structure of a source code document and either write it out to disk or compile it. Reflection.Emit, on the other hand, allows us to directly emit IL code and compile it (we can also save the compiled assembly to disk). Whereas IL code can be more difficult to work with, especially for larger more complex pieces of code, it has the benefit of being a more efficient and light-weight API than the CodeDom.  Additionally it does not have a link demand such as the CodeDom, which will only work when the caller has full-trust permission. My Weaver implementation will be using Reflection.Emit.

Essentially, the code we want to emit is fairly straight forward. Recalling the architecture described last week, we want each of the members of the interface to have their invocation controlled by an instance of the Advisor class. Thus, assuming that we have the following interface:

public interface IFoo
{
    void Bar(object arg);
}

We want to the Weaver to generate the following proxy type for it:

public class FooProxy : IFoo
{
    private readonly IFoo _real;
    private readonly Advisor[] _advisors;
 
    public FooProxy(IFoo real, Advisor[] advisors)
    {
        _real = real;
        _advisors = advisors;
    }
 
    public void Bar(object arg)
    {
        int id = 0;
        Advisor advisor = _advisors[id];
        object[] arguments = new object[1];
        arguments[0] = arg;
        advisor.Invoke(_real, arguments);
    }
}

A call to the Bar() method will now be forwarded to the associated Advisor, which can then execute any advice and so forth. We’ll look at the actual implementation of the Advisor later – for now we only define it like this:

public sealed class Advisor
{
    public void Invoke(object obj, params object[] arguments)
    {
        throw new NotImplementedException();
    }
}

Before moving on, lets write some unit tests that we can use to verify our code generator before implementing it (TDD style!). Essentially there are several types of members our code generator needs to successfully deal with:

  • Method with no return value and no arguments
  • Method with no return value and one or more arguments
  • Methods with a return value but no arguments
  • Methods with a return value and one or more arguments
  • Properties (get and set)
  • Immutable properties (no set)
  • Settable properties (no get)

 

The downloadable source code contains tests for all these - here, I'll only reproduce the simplest scenario; a method with no return value and no arguments.

public interface INoReturnNoArgs
{
    void Foo();
}
[TestMethod]
public void WeaveMethodTest()
{
    MockObject<INoReturnNoArgs> mock = MockManager.MockObject<INoReturnNoArgs>();
 
    Mock advisorMock = MockManager.MockAll<Advisor>();
 
    INoReturnNoArgs proxy = new Weaver().Weave<INoReturnNoArgs>(mock.Object);
    Assert.IsNotNull(proxy, "Generated proxy should not be null");
 
    advisorMock.ExpectCall("InvokeAndReturn", typeof(object))
        .Args(mock.Object,
              new ParameterCheckerEx(delegate(ParameterCheckerEventArgs args) 
                {
                    // should pass an empty object array as the second parmeter, as there are no arguments for Foo()
                    object[] expected = args.ArgumentValue as object[];                            
                    return null != expected && expected.Length == 0;
                }));
 
    proxy.Foo();
}

I’m using TypeMock Isolator to help me isolate the Weaver class, so that I can test it and nothing else - I don’t want the success or failure of my test to be in any way affected by the correctness of the Advisor class or the implementation of the interface I’m proxying. More so – I haven’t even implemented these yet! When I do, they will have their own tests to verify their correctness; my Weaver tests should only care about verifying the Weaver itself. The test then, simply mocks up an instance of the INoReturnNoArgs interface which contains a single method that has no return value and no arguments. Then, I call the Weaver and tell it to generate a proxy for the given object. Finally, I use TypeMocks ability to verify that the Invoke method on my mocked Advisor class actually gets invoked when I then use the generated proxy to call the Foo method. If this test passes, I can be certain of two things – the proxy type generated by the Weaver compiles without errors, and a method call on the proxy results in the call being forwarded to the Advisor. That’s a fairly good indication that the Weaver is functioning correctly.

The next step now, then, is to make this test pass. To do that, we need to implement the Weaver.Weave<T> method:

public T Weave<T>(T instance, params IPointcut[] pointcuts)
{
    Type iType = typeof (T);
    Type proxyType;
 
    int hash = iType.GetHashCode();
 
    if (!_proxyTypeCache.TryGetValue(hash, out proxyType)) // get proxy type from cache?
    {
        lock (_proxyTypeCache) // double-checked lock for optimized synchronization
        {
            if (!_proxyTypeCache.TryGetValue(hash, out proxyType))
            {
                proxyType = BuildProxyType(iType); // first time; build proxy type and cache it
                _proxyTypeCache.Add(hash, proxyType);
            }
        }
    }
 
    // create advisors for instance
    List<Advisor> advisors = new List<Advisor>();
 
    foreach (MemberInfo member in typeof(T).GetMembers())
    {
        if(member is MethodInfo && (((MethodInfo)member).Attributes & MethodAttributes.SpecialName) > 0)
        {
            continue; // skip special-name methods (property get_ & set_ methods, for instance)
        }
 
        // set up advisor for each proxied member
        Advisor advisor = new Advisor(member);
 
        foreach(IPointcut pc in pointcuts)
        {
            foreach (IAdvice advice in pc.Advise(member))
            {
                advisor.AddAdvice(advice);
            }
        }
 
        advisors.Add(advisor);
    }
 
    // instantiate proxy
    T proxy = (T)Activator.CreateInstance(proxyType, instance, advisors.ToArray());
 
    return proxy;
}

That looks deceptively simple – where’s the code generation I was talking about? I’ve refactored it out into its own method, BuildProxyType. I’m not going to reproduce the code for it here, as it’s fairly long. Its pretty cool stuff however - if you want to have a look at it you can download all the source code at the end of this post and open up the Weaver.cs file. For the purposes of this walkthrough though, all we need to know is that it generates and compiles a type implementing the given interface, as earlier described. The Weaver then creates a new instance of this type, using constructor injection to pass it the real implementation and the associated advisors.

Advising a Member Invocation

The next step now, is to implement the Advisor. Essentially there are three scenarios here – there could be no advice, one advice or many advice that should be chained together. Each of these three scenarios needs to be supported for different types of members – we’ve settled on methods and properties so far, but we should make sure to have extensibility at this point so that we may later add support for events, for example. Additionally we need to allow the advice to control the target invocation, because each advice should be allowed to execute before and after logic and optionally stop the invocation from proceeding (a cache advice may wish to return a value from its cache and skip actually invoking the target member at all, for example).  

Assuming the Advisor holds a stack of advice, we can think of the flow like this:  The Advisor executes the first advice, passing it the current target. This target is either the next advice in the chain, or if there is no more advice in the chain it is the actual target method or property. The advice, then, can execute its before logic before notifying the target it received to proceed. This then goes on recursively until the real target has been executed, and the unwinding begins, allowing each advice to execute any after logic. In the previous part of this series, we saw a figure representing this:

image

To implement this flow, we need the notion of a target. The target should do one of two things - proceed to the next advice when there is advice left on the stack, otherwise invoke the real target. The real target can be different things, so we’ll use inheritance to allow for this. Thus, we can define an abstract base type AdviceTarget which holds the flow control logic, and defers the invocation logic to the specific implementations.  Again, lets write our test first:

public class MockAdvice : IAdvice
{
    public object Execute(AdviceTarget target)
    {
        return target.Proceed();
    }
}
[TestMethod]
public void SingleAdviceProceedTest()
{
    MockObject<AdviceTarget> adviceTargetMock = MockManager.MockObject<AdviceTarget>();
    AdviceTarget target = adviceTargetMock.Object;
 
    FieldInfo adviceField = typeof(AdviceTarget).GetField("_advice", BindingFlags.NonPublic | BindingFlags.Instance);
 
    Assert.IsNotNull(adviceField, "Invalid test data");
 
    // inject an advice into the target
    MockObject adviceMock = MockManager.MockObject<MockAdvice>();
    adviceField.SetValue(target, adviceMock.Object);
 
    // expected run: Proceed should first Execute the advice; advice will Proceed on target again, which will finally Invoke
    adviceTargetMock.ExpectUnmockedCall("Proceed", 2);
    adviceMock.ExpectUnmockedCall("Execute");
    adviceTargetMock.ExpectCall("Invoke");
    target.Proceed();
}

Here, we set up a mocked instance of the AdviceTarget (notice that this is actually an abstract class - TypeMock generates a mock implementation for us on the spot. Neat!). Now, the target needs to know about the advice it contains – we’ll probably implement this using constructor injection. However, since we’re mocking an abstract class we can’t instantiate it using a constructor, so we’ll use a bit of reflection instead – this means we’re relying on our AdviceTarget implementation to store the advice in a field named _advice, so we assert that this field really exists to make sure our tests fails in a meaningful manner if that should not be the case (remember we've yet to implement this class, so we're making an educated guess here). The interesting part is when we have a target set up properly and can test it – we know that the flow we’re looking for is for a Proceed call on the target to find a single advice on the stack, which should thus be executed. This advice is of type MockAdvice, and from its implementation you can see that it does nothing but proceed on the target, beginning our recursion. This second call to Proceed will find no more advice on the stack, so it should now invoke the target. In the full source code, there are variations of this test which verifies the scenarios where there are no advice and more than one advice, but this is essentially all there is to it. If this test passes, we can be fairly certain that our target handling is working properly. To make it pass, we need to implement the AdviceTarget class:

public abstract class AdviceTarget
{
    private IAdvice _advice;
 
    public AdviceTarget(IAdvice advice, object target, MemberInfo memberInfo, params object[] arguments)
    {
        this.Target = target;
        this.TargetInfo = memberInfo;
        this.Arguments = arguments;
        _advice = advice;
    }
 
    /// <summary>
    /// Gets the target
    /// </summary>
    public object Target { get; protected set; }
 
    /// <summary>
    /// Gets the MemberInfo describing the target
    /// </summary>
    public MemberInfo TargetInfo { get; protected set; }
 
    /// <summary>
    /// Gets the arguments of the target 
    /// </summary>
    public object[] Arguments { get; protected set; }
 
    /// <summary>
    /// Proceeds with the invocation towards the target
    /// </summary>
    /// <returns></returns>
    public object Proceed()
    {
        // execute all advice in chain recursively, then execute the target
 
        if (null != _advice)
        {
            IAdvice next = _advice;
 
            if (_advice is ChainedAdvice)
            {
                _advice = ((ChainedAdvice)_advice).Next;
            }
            else
            {
                _advice = null;
            }
 
            return next.Execute(this);
        }
        else
        {
            // end of advice chain; invoke target
            return Invoke();
        }
    }
 
    /// <summary>
    /// When implemented in a deriving class, should invoke the target
    /// </summary>
    /// <returns></returns>
    protected abstract object Invoke();
}

Of interest here is the Proceed implementation. Advice can be either single, or chained. A chained advice is essentially an advice that knows who the next advice in the chain is – a one-directional linked list, if you will. The Proceed method then walks this chain and calls each advice recursively until it reaches the end, at which point it calls the Invoke method. This method is abstract, as the invoke logic will differ for different targets. For example, the MethodTarget implementation looks like this:

public class MethodTarget : AdviceTarget
{
    public MethodTarget(object target, IAdvice advice, MethodInfo memberInfo, params object[] arguments)
        : base(advice, target, memberInfo, arguments)
    {}
 
    protected override object Invoke()
    {
        MethodInfo method = (MethodInfo)this.TargetInfo; 
        return method.Invoke(this.Target, this.Arguments);
    }
}

In Use

So far, we've covered most of the interesting aspects of the implementation. Download the source code at the end of this post if you want to look at the complete implementation - now, I'd like to briefly explain how the API can be used. Below is an example usage (also included in the download):

 

// add logging as an advice
ConsoleLoggerAdvice advice1 = new ConsoleLoggerAdvice();
 
// caching advice; performing the same calculation several times should be superfast!
ResultCacheAdvice advice2 = new ResultCacheAdvice();
 
// set up a pointcut for the method CalculateSomething on IFoo, applying the advice to this method
MemberPointcut pc = new MemberPointcut(typeof(IFoo).GetMethod("CalculateSomething"), advice1, advice2);
 
ProxyFactory.AddPointcut("1", pc);
 
// test it
for (int i = 0; i < 2; i++)
{
    Console.WriteLine("Begin.");
 
    IFoo foo = ProxyFactory.CreateProxy<IFoo>(new Foo());
 
    int val = foo.CalculateSomething(5);
    Console.WriteLine("Result: " + val);
 
    Console.WriteLine("Done." + Environment.NewLine);
}
 
Console.ReadLine();

Here, we set up a couple of advice and configure a pointcut for the CalculateSomething method on the IFoo interface. Then, we create a proxy for this interface, passing it an instance of our own implementation. The proxy will then apply the logging and caching advice whenever we call the method - you can see this from the output the program produces:

Begin.
** Log: Calling member IFoo.CalculateSomething
** Log: argument 1: 5
** Log: Member returned in 1501 ms. Return value: 500
Result: 500
Done.
Begin.
** Log: Calling member IFoo.CalculateSomething
** Log: argument 1: 5
** Log: Member returned in 0 ms. Return value: 500
Result: 500
Done.

Notice that the logging advice writes out the time spent executing the method, and that for the second run the cache advice has kicked in and just returned the value from the cache, skipping the target method resulting in a super fast invocation. Obviously this is a fairly naive example, but at least it shows the possibilities of using an AOP framework. If you look at the source code, you'll also notice how cleanly separated everything is; the CalculateSomething method is not cluttered with logging and caching logic, and this logic can easily be reused for other methods, improving not only code reuse but maintainability and lowering the change resilience of our code.

Source Code here!

In this post I’ve tried to highlight the most interesting parts of the implementation of my AOP framework. There are details I’ve not covered here though, for example the implementation of different pointcuts. If you’re interested in digging further, then you can download the source code here. It includes a set of unit tests (which require TypeMock to run) plus a small demo application which shows the basic usage of the framework.

Moving on...

Initially, my plan was to end my tour of AOP with this post, but I’ve found the topic so interesting that I’ve decided to keep going a bit further. So far I’ve covered the core of what AOP is all about, but there are things that are closely related which have not been touched upon. For instance, in order to really take advantage of an AOP framework in our code, we'd need an Inversion of Control container. This can also make configuring advice and pointcuts much more elegant - perhaps we'd like to allow for loading this from an XML configuration file for example. And then there's the concept of mixins and introductions, which can be used to kind of simulate multiple inheritance. Additionally, I've neglected to talk about the implications of using an AOP framework, especially concerning performance. So, stay tuned for more on some of these topics soon :)

Be the first to rate this post

  • Currently 5/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5

Powered by BlogEngine.NET 1.4.5.0

Welcome!

My name is Fredrik Kalseth, and this is my blog - thanks for visiting! I am fortunate enough to work with what I love for a living, and this blog is essentially the biproduct of that.

I work as a senior consultant for Capgemini, and am also an active participant in the Norwegian .NET community, as an avid attendee but also as a speaker (most recently at NNUG and MSDN Live).

As a developer, I have a wide circle of interest. My primary passion is for agile, test-driven development, with focus on best practices and clean code. That said, I also love to work on the frontend, especially with web development.

On Twitter? My handle is fkalseth. On LinkedIn? I`m there too.

NDC 2010

The conference to attend this summer happens June 16th-18th in Oslo, Norway. Are you going? Be sure to catch my talk on AOP while you're there!

 

Disclaimer

This is a personal blog; any opinions expressed here are my own and do not necessarily reflect those of my employer. All content herein is my own original creation, and as such is protected by copyright law. Unless otherwise stated, all source code posted on this blog is freely usable under the Microsoft Permissive License.

What Readers Talk About

Comment RSS