Before we get to doing actual AOP, we need to learn about a few building blocks, namely the TVirtual-MethodInterceptorclass, and the notion of interception in general.
TVirtualMethodInterceptor
TVirtualMethodInterceptoris a class in theRTTI.pasunit. It was introduced in Delphi XE, and you’ll never guess what it does – it intercepts virtual methods. In doing so, it provides easy point cuts for you to
“hook” a given virtual method and insert your own code into it. You can grab a hold of a given virtual method and then change its arguments, change the return value if it is a function, intercept exceptions and even raise new ones, and if you want, you can even completely replace the code itself. It’s pretty powerful, eh?
To demonstrate its use, we’ll first need a class that has interceptable methods. Here is such a class:
type
TVMIDemo = class(TObject)
procedure DoSomething; virtual;
procedure RaiseException; virtual;
end;
This class obviously has two methods. The methods are implemented as follows:
procedure TVMIDemo.DoSomething;
We’ll useTVirtualMethodInterceptorto take control of both methods. Note that any method that is visible via RTTI can be intercepted. For the first, we’ll simply show where the point cuts are and how they can be accessed. In the second, we’ll raise an exception and show how you can control that process.
TVirtualMethodInterceptorhas three events of interest:OnBefore,OnAfter, andOnException. In order to provide the additional functionality, you need to provide event handlers for these events. TheOnBefore
event fires, well, right before the “normal” code of the method is to execute. TheOnAfterevent fires at the end of the method, and theOnExceptionfires ifTVirtualMethodInterceptorruns across an exception.
Providing simple handlers to merely declare these events happening might look like the following:
procedure DoBefore(Instance: TObject; Method: TRttiMethod;
const Args: TArray<TValue>; out DoInvoke: Boolean; out Result: TValue);
begin
Writeln('Before: ', Method.Name);
end;
procedure DoAfter(Instance: TObject; Method: TRttiMethod; const Args: TArray<TValue>; var Result: TValue);
begin
Writeln('After: ', Method.Name);
end;
procedure DoException(Instance: TObject; Method: TRttiMethod;
const Args: TArray<TValue>; out RaiseException: Boolean;
TheException: Exception; out Result: TValue);
var
Str: string;
begin
Str := Format('An exception occurred in %s with the message: %s', [Method.Name, TheException.Message]);
WriteLn(Str);
RaiseException := False;
Result := False;
end;
OnBeforeandOnAftermerely write out to the console that they are happening. Note, however, that they use theTRttiMethod.Nameproperty to declare exactly which method is being intercepted, illustrating that theMethodparameter has all the information about the method available to you. TheOnExceptionevent handler is a bit more complex, as it provides a message about theExceptionand then actually suppresses it by settingRaiseExceptiontoFalse.
Note the signatures of the method declarations. Through the parameters passed in to each, you have access to virtually (sorry) the entire method call itself, including the method name, arguments, and the result value.
In theOnBeforemethod, you can even decide whether to invoke the method at all.
This given implementation is a very simple example, but it illustrates well the principle behind interception.
The interesting work gets done in theMainfunction of the simple console application. Here it is:
procedure Main;
var
VMI: TVirtualMethodInterceptor;
VMIDemo: TVMIDemo;
begin
VMIDemo := TVMIDemo.Create;
VMI := TVirtualMethodInterceptor.Create(TVMIDemo);
try
VMI.OnBefore := DoBefore;
VMI.OnAfter := DoAfter;
VMI.OnException := DoException;
VMI.Proxify(VMIDemo);
VMIDemo.DoSomething;
VMIDemo.RaiseException;
Writeln('class: ', VMIDemo.ClassName);
Writeln('parent class: ', VMIDemo.ClassParent.ClassName);
finally VMI.Free;
VMIDemo.Free;
end;
end;
The first thing the code does is to create an instance of TVMIDemo. In order to intercept methods, we must have a valid instance of a class to intercept.
Then the code creates aTVirtualMethodInterceptor, taking as a parameter in the constructor the type of the class that we want to intercept. From there, it assigns the three event handlers above to the events on the instance ofTVirtualMethodInterceptor.
Then, the rubber hits the road. VMI “proxifies” the instance of VMIDemo, taking as a parameter that very instance. So, at this point,VMIknows the type that is going to be intercepted and “proxified”, and it has an instance of that type. The process of proxification is to do the actual intercepting. OnceProxifyis called on the instance, that instance is “hooked”. That is, the methods have been intercepted and theOnBegin,OnAfter, andOnExceptionevents are part of the reference, along with the event handlers attached to them.
In this case, that means thatVMIDemoisn’t really itself anymore. Instead, it is a proxy for the methods attached to the events of theTVirtualMethodInterceptor.
The code then goes on to call the two methods of TVMIDemo, and then output some basic information about theVMIDemoclass to prove what class was getting called.
Thus, the output looks like the following:
Note if you run the application in the debugger, you will see the IDE’s version of the raised exception appear.
Note, too, that the exception did not stop the flow of the application.
And that, in a very simple example, is the notion of virtual method interception. From here, we can move on to the more general notion of interception found in the Spring For Delphi Framework.
IInterception
The Spring for Delphi Framework has many features. One of the newest is the IInterceptor interface and the supporting code to create a proxy for a class, using the TVirtualMethodInterceptor as the underlying engine. It also supports interface proxies which are done using theTVirtualInterface class internally. As we saw above, interception is a critical part of doing AOP. We need the ability to grab a hold of a class or interface – i.e. intercept it – and add behavior at critical points. This is what the
TVirtualMethodInterceptorclass does. However, it seems to me a bit clumsy to use.
EnterIInterceptorin the Spring Framework. You aren’t going to believe this, but this interface is used to intercept a class that you provide, allowing you to easily add code to the point cuts it provides.IInterceptor
is simple – declared as follows:
IInterceptor = interface
['{B33137BC-AAEE-40D7-86F3-BD6A65F4F7C7}']
procedure Intercept(const invocation: IInvocation);
end;
Of course, the important part is the IInvocationparameter, which gets passed to you when you do the intercepting, and which looks like this:
IInvocation = interface
['{A307FB1B-CA96-4B20-84FE-A71B286F0924}']
function GetArguments: TArray<TValue>;
function GetMethod: TRttiMethod;
function GetResult: TValue;
function GetTarget: TValue;
procedure SetResult(const value: TValue);
procedure Proceed;
property Arguments: TArray<TValue> read GetArguments;
property Method: TRttiMethod read GetMethod;
property Result: TValue read GetResult write SetResult;
property Target: TValue read GetTarget;
end;
When you implementIInterceptor, your implementation automatically gets an instance ofIInvocation
based on the type that you’ve “proxified”. It in turn contains all the information about the methods of your class that is being called. You get the arguments passed to the method, theTRttiMethod instance of the method itself, theResultif the method is a function, and theTarget, which is is the proxied instance itself.
And of course, you get the very interestingly namedProceedmethod.Proceedlets you, well, proceed with the code of the method. You can call it anytime you like, and it will execute the method.
Thus, to create an Aspect, you implement theInterceptmethod ofIInterceptor, writing whatever code you want before and after your call toProceed. You have Point Cuts to add your Advice to any method of any interface you choose to “proxify”.
Let’s take a look at a simple example:
First, we need a class to intercept:
TClassToIntercept = class
procedure InterceptedMethod; virtual;
end;
procedure TClassToIntercept.InterceptedMethod;
begin
WriteLn('Calling InterceptedMethod');
end;
This is a mindless class – you can see that it doesn’t do anything other than output to the console – but it will be intercepted and we’ll add some simple Aspects to it to illustrate the basic idea of using interception to do AOP.
In order to intercept it, we’ll create a class calledTInterceptorthat implementsIInterceptor:
TInterceptor = class(TInterfacedObject, IInterceptor) procedure Intercept(const invocation: IInvocation);
end;
procedure TInterceptor.Intercept(const Invocation: IInvocation);
begin
WriteLn('Before Method call named: ', Invocation.Method.Name);
try
Invocation.Proceed;
except
on E: Exception do begin
WriteLn('Method threw an exception: ' + E.Message);
end;
end;
WriteLn('After Method Call named: ', Invocation.Method.Name);
end;
Note that this code simply outputs to the console the location of the point cuts where we add theWriteln
calls. The code calls Invocation.Proceed, and that’s whenTClassToIntercept.InterceptedMethod
will actually be called. The result, then, will be three lines of text in the console window.
Now of course, you are probably wondering how this all gets “hooked up” and runs? It happens in theMain
function that gets called by the console application:
procedure Main;
Okay, here’s where everything happens. First, we create an instance of ourTInterceptorclass and assign it to an interface variable of typeIInterceptor. Then, we create a proxy ofTClassToInterceptusing a class method of TProxyGenerator. We do that by using theTProxyGeneratorto create a proxy class, passing as a parameterized type the type of the class that we are proxy-ing. Finally we pass in ourInterceptor
instance. (Note that theInterceptorclass is passed as an array, meaning that you can pass more than one interceptor to the proxy. We’ll see an example of this below.)
Once the call toCreateClassProxydoes its thing, we have a valid, proxified reference to Intercepted-Class, an instance ofTClassToInterceptwith our Aspects attached. This means we can call its methods as expected. And when we do, we get the intercepted call, not the “regular” one. Thus, our output is as follows:
One more minor thing to note at this point: the Aspect code knows the name of the method being intercepted because it takes advantage ofIInvocation’sMethodproperty. Remember, when you implement
IInterceptor, you know almost everything there is to know about the class in question via theIInvocation
interface that is passed to you.
At this point, it’s my hope that you are noticing that what Interception is doing is really an implementation of the Decorator Pattern. The intercepted class is really being decorated by the proxy class. You should notice the similarity between the code from the Decorator Pattern chapter and the code inTInterceptor.Interceptmethod above.
But that is, again, a really simple example. Let’s take a look at something a bit more practical.