2009-04-08

My first AOP

Here’s what I did.

01: I created a support project which just has a logger class in it.

namespace EcoSupport.pas;
interface
uses
  System.Collections.Generic,
  System.Linq,
  System.Text;

type
  Logger = public class
  private
  protected
  public
    class procedure Log(Message : String);
  end;

implementation

class procedure Logger.Log(Message : String);
begin
  System.Diagnostics.Debug.WriteLine(’Log: ’ + Message);
end;
end.


02: I created an Aspect which decorates methods, giving me the opportunity to intercept all method calls on the class it decorates.

namespace EcoAspects;

interface

uses
  System.Collections.Generic,
  System.Linq,
  System.Text,
  RemObjects.Oxygene.Cirrus,
  EcoSupport;

type
  [AttributeUsage(AttributeTargets.Class)]
  LogAspect = public class(System.Attribute, RemObjects.Oxygene.Cirrus.IMethodImplementationDecorator)
  private
  protected
  public
    method HandleImplementation(Services: IServices; aMethod: IMethodDefinition);
  end;

implementation

method LogAspect.HandleImplementation(Services: IServices; aMethod: IMethodDefinition);
var
  Name : String;
begin
  Name := aMethod.Name;
  aMethod.SetBody(Services,
    method begin
      EcoSupport.pas.Logger.Log(’Entering ’ + Aspects.MethodName);
      try
        Aspects.OriginalBody; //Call the original method body
      finally
        EcoSupport.pas.Logger.Log(’Leaving ’ + Aspects.MethodName);
      end;
    end
    );
end;
end.


03: Now my aspect is ready I can apply it to a class.

namespace PrismConsoleApplication1;

interface

uses
  System.Collections.Generic,
  System.Linq,
  EcoSupport,
  System.Text;

type
  [Aspect:EcoAspects.LogAspect]
  TestSubject = public class
  private
  protected
  public
    method DoSomething();
  end;

implementation

method TestSubject.DoSomething();
begin
  System.Diagnostics.Debug.WriteLine("DoSomething");
end;
end.


04: Finally a test

namespace PrismConsoleApplication1;

interface
uses
  System.Linq;

type
  ConsoleApp = class
  public
    class method Main;
  end;

implementation

class method ConsoleApp.Main;
var
  TS: TestSubject;
begin
  TS := new TestSubject();
  TS.DoSomething();
end;
end.


The output from this is.....

Log: Entering .ctor
Log: Leaving .ctor
Log: Entering .ctor
Log: Leaving .ctor
Log: Entering DoSomething
DoSomething
Log: Leaving DoSomething

Now so far this is all stuff you can do with tools such as PostSharp. The problem with PostSharp is that it is a post-compile processor so the aspects are applied after compilation is complete, which means that we can’t use any of the aspect introduced features within the same library without comprimising compile-time checking. For example in PostSharp if an aspect introduces an interface with a LogMessage method we can’t do this...

[Logger]
public class Person
{
}

var p = new Person();
p.LogMessage(“Hello”);

Because the Person class doesn’t have a LogMessage method until after it has been compiled. To get around this PostSharp users will typically do something like this

var p = new Person();
((object)p as ILogger).LogMessage(“Hello”);

What I don’t like is
1.  More typing
2.  It’s not compile-safe. If I remove the [Logger] attribute from Person it will still compile and I wont know about the problem until runtime.

This is exactly why I like the look of Prism, take a look at this.

type
  [AttributeUsage(AttributeTargets.Class)]
  LogAspect = public class(System.Attribute, RemObjects.Oxygene.Cirrus.IMethodImplementationDecorator)
  private
  protected
  public
    [Aspect:AutoInjectIntoTargetAttribute]
    class method LogMessage(aName: String);
  end;

The AutoInjectIntoTargetAttribute tells the compiler to add that method to the target.

type
  [Aspect:EcoAspects.LogAspect]
  TestSubject = public class
  private
  protected
  public
  end;

p := new TestSubject();
p.LogMessage(‘Hello’);

This compiles even in the same library, because the aspects are able to modify the project structure before the compile process completes, so TestSubject really does have the LogMessage method. Pre-compile AOP, how cool is that?

No comments: