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?
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?
Comments