Compartilhar via


Aspect Oriented Interception

Have you used Aspect Oriented Programming (AOP) or Policy Injection? They’re pretty much the same thing. If you haven’t, I’d highly recommend doing some reading, as I believe it is a Software Engineering practice that has moved out of the “fashionable” and into the “enterprise”. For separation of concerns it is a fantastic paradigm.

I’ve done a number of demos now, on topics like the Web Client Software Factory and how it integrates with the Enterprise Library, and when I get to the bit that I add an attribute to my code like this;

[Log]

public void SaveCustomerRecord()

{

    // ... implementation

}

... and click run, only for the attendees to see all the logging happening as if by magic, there is often an impressed intake of breath. There are of course other ways of applying “aspects”, using configuration for example, but this is a quick and easy example.

I’m going to assume you’re aware of the basic principles of AOP – if you’re not, try having a read of some of these resources;

· Wikipedia’s definition of Aspect-Oriented Programming

· Spring .NET’s AOP Chapter

· Type AOP into a search engine (Live Search, obviously J)

To get a basic understanding, make sure you know about aspects, targets, join points & point-cuts, advice, and introductions. One often overlooked one you should know about is weaving.

Weaving Aspects

Weaving is all about adding the calls to your aspects into the existing code. So imagine we have a method that looks like this (* note: excuse the terrible code, it is only meant to be illustrative!) ;

[Log]

public void SaveCustomerRecord(int id, Dictionary<string, object> values)

{

    Customer customerRecord = new Customer();

    customerRecord.ReadFromDatabase(id);

    customerRecord.UpdateDetails(values);

    customerRecord.Save();

}

... then adding the calls to “Log” might effectively change my code to look like this;

public void SaveCustomerRecord(int id, Dictionary<string, object> values)

{

    try

    {

        Logger.LogMethodEntry();

        Customer customerRecord = new Customer();

        customerRecord.ReadFromDatabase(id);

        customerRecord.UpdateDetails(values);

        customerRecord.Save();

    }

    catch (Exception e)

    {

        Logger.LogException(e);

        throw;

    }

    finally

    {

        Logger.LogMethodExit();

    }

}

I’m sure you can see the difference; we now log entry to and exit from the method, and any errors we encounter. So something must alter the code to do this, right?!

Well, yes! That is, either the code is changed, or the call is intercepted and rerouted.

It turns out there are a few ways of doing this, and we’ll cover what these are below, seeing the differences as we go.

Interception Mechanisms

1: Interface Proxy

The “interface proxy” method looks like this;

Interface Proxy

In this scenario, the “target” class must implement an explicit interface. The runtime can then create its own runtime implementation of this interface that simply intercepts the calls, processes the relevant aspects, and then (if applicable) calls onto the real target object. An example of this using Spring .NET is below;

public interface ITarget

{

void DoSomething();

}

public class Target : ITarget

{

public void DoSomething()

{

Console.WriteLine("I'm doing something...");

}

}

public class Program

{

public void Run()

{

ProxyFactory factory = new ProxyFactory(new Target());

factory.AddAdvice(new LoggingAspect());

ITarget target = (ITarget)factory.GetProxy();

// do it!

target.DoSomething();

}

}

Here we can see that our Target class implements ITarget, and that when we retrieve a proxy to it, we actually get a dynamically created implementation of ITarget, that calls LoggingAspect and then passes the call onto our real Target class. Note that the configuration of the factory, when we add advice, could be done in configuration.

So this is a really nice way of doing things, but some people don’t like having to have an interface for every class they want to intercept. And I don’t blame them – if you use an aspect for logging, you might want it on every single class! That’s a lot of interfaces...

This is the mechanism that most of the .NET AOP frameworks use, to some extent; Spring .NET, the Enterprise Library Policy Injection block (although the docs tell us the PIAB is not an AOP framework, it is close enough for this topic), Castle Windsor’s Dynamic Proxies...

2: Remoting Proxy

Our next option is to use some cleverness built into the Remoting infrastructure in .NET.

Remoting Proxy

I’m not going to go into the detail (if you really want to know, there’s some info here), but basically this allows us to intercept calls to any method on our Target object. Of course, this doesn’t come free. In fact, it down right hurts – we have to inherit from MarshalByRefObject, and there is likely to be a performance hit. Let’s see an example using the PIAB;

public class Target : MarshalByRefObject

{

[LogCallHandler]

public void DoSomething()

{

Console.WriteLine("I'm doing something...");

}

}

public class Program

{

public void Run()

{

Target target = PolicyInjection.Create<Target>();

// do it!

target.DoSomething();

}

}

Notice a few things in this code; firstly, we must inherit from the dreaded MarshalByRefObject (which of course prevents us from inheriting from anything else our code might find more useful). Secondly, note that I’ve chosen to use an attribute (“LogCallHandler”) on a method to apply an aspect to the target. Some people hate this (mainly because of the coupling between target and aspect) – you can do it in config if you wish. Lastly, note that we create an object using an overload of PolicyInjection.Create that supports using a class with no interface (or the alternative Wrap method).

Again, many frameworks support this approach.

3: Inheritance Proxy

The third option is an inheritance proxy, characterised by the following diagram;

Inheritance Proxy

Why, when the two above methods can do everything we need? Gotcha; they can’t do everything we need. There’s actually a big hole in what we’ve already covered. Consider the following code, adapted from the example in option (2) – and look carefully;

public class Target : MarshalByRefObject

{

public void DoSomethingFirst()

{

// pass on the call

DoSomething();

}

[LogCallHandler]

public void DoSomething()

{

Console.WriteLine("I'm doing something...");

}

}

public class Program

{

public void Run()

{

Target target = PolicyInjection.Create<Target>();

// do it!

target.DoSomethingFirst();

}

}

All I’ve done is to introduce a new method; now my Program class calls “DoSomethingFirst”, which in turn calls “DoSomething”. This second method is the one that has the logging aspect applied to it.

But of course, our aspect is invoked by a Proxy, and when DoSomethingFirst calls to DoSomething, it does it using the “this” keyword (implicitly). Therefore this call is not going via the proxy, and therefore no aspects get invoked. Owch! So my AOP mechanism only works when calling to a different object!

Now this isn’t a show-stopper, as long as you understand this behaviour, but it is a bit of a pain, which is why the inheritance proxy approach can be useful.

OK, now we need to switch frameworks as the PIAB doesn’t support inheritance proxies... let’s use Castle Windsor’s DynamicProxy2, tweak our example to mark its methods as virtual, and get rid of the MarshalByRefObject base class;

public class Target

{

public virtual void DoSomethingFirst()

{

// pass on the call

DoSomething();

}

public virtual void DoSomething()

{

Console.WriteLine("I'm doing something...");

}

}

public class Program

{

static void Main(string[] args)

{

ProxyGenerator factory = new ProxyGenerator(

new DefaultProxyBuilder());

Target target = factory.CreateClassProxy<Target>(

new LoggingInterceptor());

// do it!

target.DoSomethingFirst();

}

}

This means our reference to what we think is a Target class is now actually a dynamically created subclass of Target... perhaps something like this (note: I’m massively simplifying intentionally);

public class GeneratedTargetSubClass : Target

{

public override void DoSomethingFirst()

{

AOP.CallBeforeAspects();

base.DoSomethingFirst();

AOP.CallAfterAspects();

}

public override void DoSomething()

{

AOP.CallBeforeAspects();

base.DoSomething();

AOP.CallAfterAspects();

}

}

Now, immediately our Target class calls from one of its members (say DoSomethingFirst) into another (such as DoSomething), the call will go through our generated subclass, and hence our aspects will be executed successfully.

4: Modified IL

The last, and probably most impressive approach, is to literally compile the aspects into the IL. IL is “intermediate language”, basically what all .NET code (VB.NET, C#, J#... anything) compiles to, and is pretty much equivalent to Java’s bytecode. Speaking of Java, in Java-land AspectJ can compile aspects into the bytecode using a post-processing step.

In .NET, there are a couple of options for doing this – namely PostSharp and AspectDNG (based on Cecil). For PostSharp, the third and (hopefully) final Release Candidate was recently announced, so I thought it was high time I had a look.

So in our fourth case the diagram looks like this;

Modified IL

This clever approach lets you write aspects that are compiled into the IL in a post-processing step. For PostSharp, the installer also hooks into MSBuild so that it all happens automatically if you reference the PostSharp assemblies. We can specify aspects using attributes again, so have a look at the following code;

public class Target

{

[LoggingAspect]

public void DoSomething()

{

Console.WriteLine("I'm doing something...");

}

}

public class Program

{

public void Run()

{

Target target = new Target();

// do it!

target.DoSomething();

}

}

The first thing you should notice is that there is no need to generate a proxy, wrap a proxy, intercept calls using a remoting infrastructure, or anything else. Just create a Target and call its method. That’s because the [LoggingAspect] attribute is weaved into the main code for DoSomething at compile time.

Our LoggingAspect implementation could look something like this;

[Serializable]

public class LoggingAspect : OnMethodBoundaryAspect

{

public override void OnEntry(MethodExecutionEventArgs eventArgs) ...

public override void OnExit(MethodExecutionEventArgs eventArgs) ...

public override void OnSuccess(MethodExecutionEventArgs eventArgs) ...

public override void OnException(MethodExecutionEventArgs eventArgs) ...

}

If we now look at a sample of the code that exists in our compiled .NET assembly, you might be surprised how little it looks like the code we wrote!

Disassembly

PostSharp has “weaved” (how appropriate does that term seem now you see the code?!) calls to our aspects into the DoSomething method. We could also have applied our attribute at the assembly level – semantically equivalent to “intercept all calls to methods that start with a Y in this assembly”, for example.

Perhaps the biggest issue here that doesn’t apply to the other approaches is that you cannot dynamically apply or remove aspects using configuration, after compile time. But then, you can build config into your aspects – so a logging aspect may have an on/off switch for example. This is not quite the same, so think about it carefully if this matters to you.

What I really like is that I don’t need some kind of factory to create or wrap my objects, and that my code is fixed into the assembly – which surely should perform well? I put a question mark here because PostSharp does still use reflection, so I’d be keen to see some comparisons... but still, it isn’t proxying every call. And of course the aspects will be applied across all calls – not just those from a different object!

Conclusion

I hope that’s been a useful little look at how some of these AOP frameworks (*) work in the background. The key takeaway is that there are differences in behaviour that can affect you when using these frameworks. There are also differing levels of abstraction and decoupling – so choose carefully!

I personally love the idea of compiling aspects into the IL. In fact, I’d love to see making this kind of compiler-level extension easier in .NET some day. I can’t quite get my head around how it would work, but I wonder if lambda expressions and LINQ might work together nicely to help here in some way... more thought required I think.

One important point to note is that I have deliberately not compared the frameworks against AOP principles, the quality of their point-cut language, and so on... if you’re interested in those, do some reading and let me know what you think! A good starting point is the AOP Alliance.

Anyway, happy point-cutting!

* yes I know some of them don’t claim to be AOP frameworks, but they’re frameworks that help with AOP, or are approaching AOP, that’s good enough for me J

Check these out for some more information... and there are many more too!

· The PostSharp home page

· The AspectDNG home page (and Cecil)

· Tom Hollander’s original PIAB announcement

· The Enterprise Library (you want versions 3+ for the PIAB)

· The Castle Project (for Castle Windsor etc)

· The Spring Framework for .NET

Also, after writing 99% of this article, I found a great little post by Ayende comparing approaches to AOP in .NET – worth a quick read.

Comments

  • Anonymous
    June 24, 2008
    The comment has been removed

  • Anonymous
    June 24, 2008
    Hi Simon, AOP is interesting but pretty much all the examples of weaving aspects into existing code show logging and then fall down. Do you have examples of where you've successfully woven aspects into existing code that wasn't related to logging or debugging? For me, it seems like there isn't much you'd want to do that is orthogonal enough to the original code that you wouldn't substantially change it through the aspect. If that's what you're doing, wouldn't it be better to think about that while you are writing the code in the first place. One example people sometimes show is database transactions (and I've written code in the past to manage transactions using some of the same techniques that AOP can rely on) but I'm not sure introducing transactions into code that didn't have this in mind when it was written is a terribly good idea. Was COM+ really a great advert for AOP? Interested to hear your counter examples... :o) Cheers, Ade.

  • Anonymous
    June 24, 2008
    The comment has been removed

  • Anonymous
    June 30, 2008
    Long live “Dependency Resolution”! OK, so I’m not really serious – but I got your attention right? Truth