This is an introductory guide to Aspect Oriented Programming (AOP) with Spring.NET.
This guide assumes little to no prior experience of having
used Spring.NET AOP on the part of the reader.
However, it does assume a certain familiarity with
the terminology of AOP in general. It is probably better if you have read
(or at least have skimmed through) the AOP section of the reference
documentation beforehand, so that you are familiar with a) just what AOP
is, b) what problems AOP is addressing, and c) what the AOP concepts of
advice
, pointcut
, and
joinpoint
actually mean... this guide spends absolutely
zero time defining those terms. Having said all that, if you are the kind
of developer who learns best by example, then by all means follow along...
you can always consult the reference documentation as the need arises (see
Section 13.1.1, “AOP concepts”).
The examples in this guide are intentionally simplistic. One of the core aims of this guide is to get you up and running with Spring.NET's flavor of AOP in as short a time as possible. Having to comprehend even a simple object model in order to understand the AOP examples would not be conducive to learning Spring.NET AOP. It is left as an exercise for the reader to take the concepts learned from this guide and apply them to his or her own code base. Again, having said all of that, this guide concludes with a number of cookbook-style AOP 'recipes' that illustrate the application of Spring.NET's AOP offering in a real world context; additionally, the Spring.NET reference application contains a number of Spring.NET AOP aspects particular to it's own domain model (see Chapter 37, SpringAir - Reference Application).
This initial section introduces the basics of defining and then applying some simple advice.
Lets see (a very basic) example of using Spring.NET AOP. The following example code simply applies advice that writes the details of an advised method call to the system console. Admittedly, this is not a particularly compelling or even useful application of AOP, but having worked through the example, you will then hopefully be able to see how to apply your own custom advice to perform useful work (transaction management, auditing, security enforcement, thread safety, etc).
Before looking at the AOP code proper lets quickly look at the domain classes that are the target of the advice (in Spring.NET AOP terminology, an instance of the following class is going to be the advised object.
public interface ICommand { object Execute(object context); } public class ServiceCommand : ICommand { public object Execute(object context) { Console.Out.WriteLine("Service implementation : [{0}]", context); return null; } }
Find below the advice that is going to be applied to the
object Execute(object context)
method of the
ServiceCommand
class. As you can see, this is an
example of around advice (see Section 13.3.2, “Advice types”).
public class ConsoleLoggingAroundAdvice : IMethodInterceptor { public object Invoke(IMethodInvocation invocation) { Console.Out.WriteLine("Advice executing; calling the advised method..."); object returnValue = invocation.Proceed(); Console.Out.WriteLine("Advice executed; advised method returned " + returnValue); return returnValue; } }
Some simple code that merely prints out the fact that the advice is executing. | |
The advised method is invoked. | |
The return value is captured in the
returnValue
variable.
| |
The value of the captured
returnValue
is printed out.
| |
The previously captured
returnValue
is returned.
|
So thus far we have three artifacts: an interface
(ICommand
); an implementation of said interface
(ServiceCommand
); and some (trivial) advice
(encapsulated by the ConsoleLoggingAroundAdvice
class). All that remains is to actually apply the
ConsoleLoggingAroundAdvice
advice to the
invocation of the Execute()
method of the
ServiceCommand
class. Lets look at how to effect
this programmatically...
ProxyFactory factory = new ProxyFactory(new ServiceCommand()); factory.AddAdvice(new ConsoleLoggingAroundAdvice()); ICommand command = (ICommand) factory.GetProxy(); command.Execute("This is the argument");
The result of executing the above snippet of code will look something like this...
Advice executing; calling the advised method... Service implementation : [This is the argument] Advice executed; advised method returned
The output shows that the advice (the
Console.Out
statements from the
ConsoleLoggingAroundAdvice
was applied
around the invocation of the advised method.
So what is happening here? The fact that the preceding code used a
class called ProxyFactory
may have clued you in.
The constructor for the ProxyFactory
class took
as an argument the object that we wanted to advise (in this case, an
instance of the ServiceCommand
class). We then
added some advice (a ConsoleLoggingAroundAdvice
instance) using the AddAdvice()
method of the
ProxyFactory
instance. We then called the
GetProxy()
method of the
ProxyFactory
instance which gave us a proxy... an
(AOP) proxy that proxied the target object (the
ServiceCommand
instance), and called the advice
(a single instance of the
ConsoleLoggingAroundAdvice
in this case). When we
invoked the Execute(object context)
method of the
proxy, the advice was 'applied'
(executed), as can be
seen from the attendant output.
The following image shows a graphical view of the flow of execution through a Spring.NET AOP proxy.
One thing to note here is that the AOP proxy that was returned
from the call to the GetProxy()
method of the
ProxyFactory
instance was cast to the
ICommand
interface that the
ServiceCommand
target object implemented. This is
very important... currently, Spring.NET's AOP implementation mandates
the use of an interface for advised objects. In short, this means that
in order for your classes to leverage Spring.NET's AOP support, those
classes that you wish to use with Spring.NET AOP must implement at least one interface. In
practice this restriction is not as onerous as it sounds... in any case,
it is generally good practice to program to
interfaces anyway (support for applying advice to classes that do not
implement any interfaces is planned for a future point release of
Spring.NET AOP).
The remainder of this guide is concerned with fleshing out some of the finer details of Spring.NET AOP, but basically speaking, that's about it.
As a first example of fleshing out one of those finer details, find below some Spring.NET XML configuration that does exactly the same thing as the previous example; it should also be added that this declarative style approach to Spring.NET AOP is preferred to the programmatic style.
<object id="consoleLoggingAroundAdvice" type="Spring.Examples.AopQuickStart.ConsoleLoggingAroundAdvice"/> <object id="myServiceObject" type="Spring.Aop.Framework.ProxyFactoryObject"> <property name="target"> <object id="myServiceObjectTarget" type="Spring.Examples.AopQuickStart.ServiceCommand"/> </property> <property name="interceptorNames"> <list> <value>consoleLoggingAroundAdvice</value> </list> </property> </object>
ICommand command = (ICommand) ctx["myServiceObject"];
command.Execute();
Some comments are warranted concerning the above XML configuration
snippet. Firstly, note that the
ConsoleLoggingAroundAdvice
is itself a plain
vanilla object, and is eligible for configuration just like any other
class... if the advice itself needed to be injected with any
dependencies, any such dependencies could be injected as normal.
Secondly, notice that the object definition corresponding to the
object that is retrieved from the IoC container is a
ProxyFactoryObject
. The
ProxyFactoryObject
class is an implementation of
the IFactoryObject
interface;
IFactoryObject
implementations are treated
specially by the Spring.NET IoC container... in this specific case, it
is not a reference to the ProxyFactoryObject
instance itself that is returned, but rather the object that the
ProxyFactoryObject
produces. In this case, it
will be an advised instance of the ServiceCommand
class.
Thirdly, notice that the target of the
ProxyFactoryObject
is an instance of the
ServiceCommand
class; this is the object that is
going to be advised (i.e. invocations of its methods are going to be
intercepted). This object instance is defined as an inner object
definition... this is the preferred idiom for using the
ProxyFactoryObject
, as it means that other
objects cannot acquire a reference to the raw object, but rather only
the advised object.
Finally, notice that the advice that is to be applied to the
target object is referred to by its object name in the list of the names
of interceptors for the ProxyFactoryObject
's
interceptorNames
property. In this particular case,
there is only one instance of advice being applied... the
ConsoleLoggingAroundAdvice
defined in an object
definition of the same name. The reason for using a list of object names
as opposed to references to the advice objects themselves is explained
in the reference documentation...
'... if the ProxyFactoryObject
's
singleton property is set to false, it must be able to return
independent proxy instances. If any of the advisors is itself a
prototype, an independent instance would need to be returned, so it is
necessary to be able to obtain an instance of the prototype from the
context; holding a reference isn't sufficient.'
The advice that was applied in the previous section was rather
indiscriminate with regard to which methods on the advised object were
to be advised... the ConsoleLoggingAroundAdvice
simply intercepted all methods (that
were part of an interface implementation) on the target object.
This is great for simple examples and suchlike, but not so great
when you only want certain methods of an object to be advised. For
example, you may only want those methods beginning with
'Start'
to be advised; or you may only want those
methods that are called with specific runtime argument values to be
advised; or you may only want those methods that are decorated with a
Lockable
attribute to be advised.
The mechanism that Spring.NET AOP uses to discriminate about where
advice is applied (i.e. which method invocations are intercepted) is
encapsulated by the IPointcut
interface (see
Section 13.2, “Pointcut API in Spring.NET”). Spring.NET provides many
out-of-the-box implementations of the IPointcut
interface... the implementation that is used if none is explicitly
supplied (as was the case with the first example) is the canonical
TruePointcut
: as the name suggests, this
pointcut always matches, and hence all
methods that can be advised will be advised.
So let's change the configuration of the advice such that it is
only applied to methods that contain the letters
'Do'
. We'll change the
ICommand
interface (and it's attendant
implementation) to accommodate this...
public interface ICommand { void Execute(); void DoExecute(); } public class ServiceCommand : ICommand { public void Execute() { Console.Out.WriteLine("Service implementation : Execute()..."); } public void DoExecute() { Console.Out.WriteLine("Service implementation : DoExecute()..."); } }
Please note that the advice itself (encapsulated within the
ConsoleLoggingAroundAdvice
class) does not need
to change; we are changing where this advice is
applied, and not the advice itself.
Programmatic configuration of the advice, taking into account the
fact that we only want methods that contain the letters
'Do'
to be advised, looks like this...
ProxyFactory factory = new ProxyFactory(new ServiceCommand()); factory.AddAdvisor(new DefaultPointcutAdvisor( new SdkRegularExpressionMethodPointcut("Do"), new ConsoleLoggingAroundAdvice())); ICommand command = (ICommand) factory.GetProxy(); command.DoExecute();
The result of executing the above snippet of code will look something like this...
Intercepted call : about to invoke next item in chain...
Service implementation...
Intercepted call : returned
The output indicates that the advice was applied around the
invocation of the advised method, because the name of the method that
was executed contained the letters 'Do'
. Try changing
the pertinent code snippet to invoke the Execute()
method, like so...
ProxyFactory factory = new ProxyFactory(new ServiceCommand()); factory.AddAdvisor( new DefaultPointcutAdvisor( new SdkRegularExpressionMethodPointcut("Do"), new ConsoleLoggingAroundAdvice())); ICommand command = (ICommand) factory.GetProxy(); // note that there is no 'Do' in this method name command.Execute();
Run the code snippet again; you will see that the advice will not
be applied : the pointcut is not matched (the method name does not
contain the letters 'Do'
), resulting in the following
(unadvised) output...
Service implementation...
XML configuration that accomplishes exactly the same thing as the previous programmatic configuration example can be seen below...
<object id="consoleLoggingAroundAdvice" type="Spring.Aop.Support.RegularExpressionMethodPointcutAdvisor"> <property name="pattern" value="Do"/> <property name="advice"> <object type="Spring.Examples.AopQuickStart.ConsoleLoggingAroundAdvice"/> </property> </object> <object id="myServiceObject" type="Spring.Aop.Framework.ProxyFactoryObject"> <property name="target"> <object id="myServiceObjectTarget" type="Spring.Examples.AopQuickStart.ServiceCommand"/> </property> <property name="interceptorNames"> <list> <value>consoleLoggingAroundAdvice</value> </list> </property> </object>
You'll will perhaps have noticed that this treatment of pointcuts
introduced the concept of an advisor
(see Section 13.4, “Advisor API in Spring.NET”). An advisor is nothing more the composition
of a pointcut (i.e. where advice is going to be
applied), and the advice itself (i.e. what is going
to happen at the interception point). The
consoleLoggingAroundAdvice
object defines an advisor
that will apply the advice to all those methods of the advised object
that match the pattern 'Do'
(the pointcut). The
pattern to match against is supplied as a simple string value to the
pattern
property of the
RegularExpressionMethodPointcutAdvisor
class.
The first section should (hopefully) have demonstrated the basics of firstly defining advice, and secondly, of choosing where to apply that advice using the notion of a pointcut. Of course, there is a great deal more to Spring.NET AOP than the aforementioned single advice type and pointcut. This section continues the exploration of Spring.NET AOP, and describes the various advice and pointcuts that are available for you to use (yes, there is more than one type of advice and pointcut).
The advice that was demonstrated and explained in the preceding
section is what is termed 'around advice'. The name
'around advice' is used because the advice is
applied around the target method invocation. In the
specific case of the ConsoleLoggingAroundAdvice
advice that was defined previously, the target was made available to the
advice as an IMethodInvocation
object... a call
was made to the Console
class before the target
was invoked, and a call was made to the Console
class after the target method invocation was invoked. The advice
surrounded the target, one could even say that the advice was totally
'around' the target... hence the name, 'around
advice'.
'around advice' provides one with the opportunity to do things both before the target gets a chance to do anything, and after the target has returned: one even gets a chance to inspect (and possibly even totally change) the return value.
Sometimes you don't need all that power though. If we stick with
the example of the ConsoleLoggingAroundAdvice
advice, what if one just wants to log the fact that a method was called?
In that case one doesn't need to do anything after
the target method invocation is to be invoked, nor do you need access to
the return value of the target method invocation. In fact, you only want
to do something before the target is to be invoked
(in this case, print out a message to the system
Console
detailing the name of the method). In the
tradition of good programming that says one should use only what one
needs and no more, Spring.NET has another type of advice that one can
use... if one only wants to do something before the
target method invocation is invoked, why bother with having to manually
call the Proceed()
method? The most expedient
solution simply is to use 'before advice'.
'before advice' is just that... it is
advice that runs before the target method
invocation is invoked. One does not get access to the target method
invocation itself, and one cannot return a value... this is a good
thing, because it means that you cannot inadvertently forget to call
the Proceed()
method on the target, and it also
means that you cannot inadvertently forget to return the return value
of the target method invocation. If you don't need to inspect or
change the return value, or even do anything after the successful
execution of the target method invocation, then 'before
advice' is just what you need.
'before advice' in Spring.NET is defined by
the IMethodBeforeAdvice
interface in the
Spring.Aop
namespace. Lets just dive in with an
example... we'll use the same scenario as before to keep things
simple. Let's define the 'before advice'
implementation first.
public class ConsoleLoggingBeforeAdvice : IMethodBeforeAdvice { public void Before(MethodInfo method, object[] args, object target) { Console.Out.WriteLine("Intercepted call to this method : " + method.Name); Console.Out.WriteLine(" The target is : " + target); Console.Out.WriteLine(" The arguments are : "); if(args != null) { foreach (object arg in args) { Console.Out.WriteLine("\t: " + arg); } } } }
Let's apply a single instance of the
ConsoleLoggingBeforeAdvice
advice to the
invocation of the Execute()
method of the
ServiceCommand
. What follows is programmatic
configuration; as you can see, its pretty much identical to the
previous version... the only difference is that we're using our new
'before advice' (encapsulated as an instance of
the ConsoleLoggingBeforeAdvice
class).
ProxyFactory factory = new ProxyFactory(new ServiceCommand()); factory.AddAdvice(new ConsoleLoggingBeforeAdvice()); ICommand command = (ICommand) factory.GetProxy(); command.Execute();
The result of executing the above snippet of code will look something like this...
Intercepted call to this method : Execute The target is : Spring.Examples.AopQuickStart.ServiceCommand The arguments are :
The output clearly indicates that the advice was applied
before the invocation of the advised
method. Notice that in contrast to 'around
advice', with 'before advice' there is
no chance of forgetting to call the Proceed()
method on the target, because one does not have access to the
IMethodInvocation
(as is the case with
'around advice')... similarly, you cannot forget
to return the return value either.
If you can use 'before advice', then do so. The simpler programming model offered by 'before advice' means that there is less to remember, and thus potentially less things to get wrong.
Here is the Spring.NET XML configuration for applying our 'before advice' declaratively...
<object id="beforeAdvice" type="Spring.Examples.AopQuickStart.ConsoleLoggingBeforeAdvice"/> <object id="myServiceObject" type="Spring.Aop.Framework.ProxyFactoryObject"> <property name="target"> <object id="myServiceObjectTarget" type="Spring.Examples.AopQuickStart.ServiceCommand"/> </property> <property name="interceptorNames"> <list> <value>beforeAdvice</value> </list> </property> </object>
Just as 'before advice' defines advice that executes before an advised target, 'after advice' is advice that executes after a target has been executed.
'after advice' in Spring.NET is defined by
the IAfterReturningAdvice
interface in the
Spring.Aop
namespace. Again, lets just fire on
ahead with an example... again, we'll use the same scenario as before
to keep things simple.
public class ConsoleLoggingAfterAdvice : IAfterReturningAdvice { public void AfterReturning( object returnValue, MethodInfo method, object[] args, object target) { Console.Out.WriteLine("This method call returned successfully : " + method.Name); Console.Out.WriteLine(" The target was : " + target); Console.Out.WriteLine(" The arguments were : "); if(args != null) { foreach (object arg in args) { Console.Out.WriteLine("\t: " + arg); } } Console.Out.WriteLine(" The return value is : " + returnValue); } }
Let's apply a single instance of the
ConsoleLoggingAfterAdvice
advice to the
invocation of the Execute()
method of the
ServiceCommand
. What follows is programmatic
configuration; as you can, its pretty much identical to the
'before advice' version (which in turn was pretty
much identical to the original 'around advice'
version)... the only real difference is that we're using our new
'after advice' (encapsulated as an instance of
the ConsoleLoggingAfterAdvice
class).
ProxyFactory factory = new ProxyFactory(new ServiceCommand()); factory.AddAdvice(new ConsoleLoggingAfterAdvice()); ICommand command = (ICommand) factory.GetProxy(); command.Execute();
The result of executing the above snippet of code will look something like this...
This method call returned successfully : Execute The target was : Spring.Examples.AopQuickStart.ServiceCommand The arguments were : The return value is : null
The output clearly indicates that the advice was applied
after the invocation of the advised
method. Again, it bears repeating that your real world development
will actually have an advice implementation that does something useful
after the invocation of an advised method. Notice that in contrast to
'around advice', with 'after
advice' there is no chance of forgetting to call the
Proceed()
method on the target, because just like
'before advice' you don't have access to the
IMethodInvocation
... similarly, although you
get access to the return value of the target, you cannot forget to
return the return value either. You can however change the state of
the return value, typically by setting some of its properties, or by
calling methods on it.
The best-practice rule for 'after advice' is much the same as it is for 'before advice'; namely that if you can use 'after advice', then do so (in preference to using 'around advice'). The simpler programming model offered by 'after advice' means that there is less to remember, and thus less things to get potentially wrong.
A possible use case for 'after advice' would include performing access control checks on the return value of an advised method invocation; consider the case of a service that returns a list of document URI's... depending on the identity of the (Windows) user that is running the program that is calling this service, one could strip out those URI's that contain sensitive data for which the user does not have sufficient privileges to access. That is just one (real world) scenario... I'm sure you can think of plenty more that are a whole lot more relevant to your own development needs.
Here is the Spring.NET XML configuration for applying the 'after advice' declaratively...
<object id="afterAdvice" type="Spring.Examples.AopQuickStart.ConsoleLoggingAfterAdvice"/> <object id="myServiceObject" type="Spring.Aop.Framework.ProxyFactoryObject"> <property name="target"> <object id="myServiceObjectTarget" type="Spring.Examples.AopQuickStart.ServiceCommand"/> </property> <property name="interceptorNames"> <list> <value>afterAdvice</value> </list> </property> </object>
So far we've covered 'around advice', 'before advice', and 'after advice'... these advice types will see you through most if not all of your AOP needs. However, one of the remaining advice types that Spring.NET has in its locker is 'throws advice'.
'throws advice' is advice that executes when an advised method invocation throws an exception.. hence the name. One basically applies the 'throws advice' to a target object in much the same way as any of the previously mentioned advice types. If during the execution of ones application none of any of the advised methods throws an exception, then the 'throws advice' will never execute. However, if during the execution of your application an advised method does throw an exception, then the 'throws advice' will kick in and be executed. You can use 'throws advice' to apply a common exception handling policy across the various objects in your application, or to perform logging of every exception thown by an advised method, or to alert (perhaps via email) the support team in the case of particularly of critical exceptions... the list of possible uses cases is of course endless.
The 'throws advice' type in Spring.NET is
defined by the IThrowsAdvice
interface in the
Spring.Aop
namespace... basically, one defines on
one's 'throws advice' implementation class what
types of exception are going to be handled. Lets take a quick look at
the IThrowsAdvice
interface...
public interface IThrowsAdvice : IAdvice { }
Yes, that is really it... it is a marker interface that has no methods on it. You may be wondering how Spring.NET determines which methods to call to effect the running of one's 'throws advice'. An example would perhaps be illustrative at this point, so here is some simple Spring.NET style 'throws advice'...
public class ConsoleLoggingThrowsAdvice : IThrowsAdvice { public void AfterThrowing(Exception ex) { Console.Out.WriteLine("Advised method threw this exception : " + ex); } }
Lets also change the implementation of the
Execute()
method of the
ServiceCommand
class such that it throws an
exception. This will allow the advice encapsulated by the above
ConsoleLoggingThrowsAdvice
to kick in.
public class ServiceCommand : ICommand { public void Execute() { throw new UnauthorizedAccessException(); } }
Let's programmatically apply the 'throws
advice' (an instance of our
ConsoleLoggingThrowsAdvice
) to the invocation
of the Execute()
method of the above
ServiceCommand
class; to wit...
ProxyFactory factory = new ProxyFactory(new ServiceCommand()); factory.AddAdvice(new ConsoleLoggingThrowsAdvice()); ICommand command = (ICommand) factory.GetProxy(); command.Execute();
The result of executing the above snippet of code will look something like this...
Advised method threw this exception : System.UnauthorizedAccessException: Attempted to perform an unauthorized operation.
As can be seen from the output, the
ConsoleLoggingThrowsAdvice
kicked in when the
advised method invocation threw an exception. There are a number of
things to note about the
ConsoleLoggingThrowsAdvice
advice class, so
lets take them each in turn.
In Spring.NET, 'throws advice' means that
you have to define a class that implements the
IThrowsAdvice
interface. Then, for each type of
exception that your 'throws advice' is going to
handle, you have to define a method with this signature...
void AfterThrowing(Exception ex)
Basically, your exception handling method has to be named
AfterThrowing
. This name is important... your
exception handling method(s) absolutely must be called
AfterThrowing
. If your handler method is not called
AfterThrowing
, then your 'throws
advice' will never be
called, it's as simple as that. Currently, this naming restriction is
not configurable (although it may well be opened up for configuration
in the future).
Your exception handling method must (at the very least) declare
a parameter that is an Exception
type... this
parameter can be the root Exception
class (as
in the case of the above example), or it can be an
Exception
subclass if you only want to handle
certain types of exception. It is good practice to always make your
exception handling methods have an Exception
parameter that is the most specialized
Exception
type possible... i.e. if you are
applying 'throws advice' to a method that could
only ever throw ArgumentException
s, then
declare the parameter of your exception handling method as...
void AfterThrowing(ArgumentException ex)
Note that your exception handling method can have any return
type, but returning any value from a Spring.NET 'throws
advice' method would be a waste of time... the Spring.NET
AOP infrastructure will simply ignore the return value, so always
define the return type of your exception handling methods to be
void
.
Finally, here is the Spring.NET XML configuration for applying the 'throws advice' declaratively...
<object id="throwsAdvice" type="Spring.Examples.AopQuickStart.ConsoleLoggingThrowsAdvice"/> <object id="myServiceObject" type="Spring.Aop.Framework.ProxyFactoryObject"> <property name="target"> <object id="myServiceObjectTarget" type="Spring.Examples.AopQuickStart.ServiceCommand"/> </property> <property name="interceptorNames"> <list> <value>throwsAdvice</value> </list> </property> </object>
One thing that cannot be done using 'throws
advice' is exception swallowing. It is not possible to
define an exception handling method in a 'throws
advice' implementation that will swallow any exception and
prevent said exception from bubbling up the call stack. The nearest
thing that one can do is define an exception handling method in a
'throws advice' implementation that will wrap the
handled exception in another exception; one would then throw the
wrapped exception in the body of one's exception handling method. One
can use this to implement some sort of exception translation or
exception scrubbing policy, in which implementation specific
exceptions (such as SqlException
or
OracleException
exceptions being thrown by an
advised data access object) get replaced with a business exception
that has meaning to the service objects in one's business layer. A toy
example of this type of 'throws advice' can be
seen below.
public class DataAccessExceptionScrubbingThrowsAdvice : IThrowsAdvice { public void AfterThrowing (SqlException ex) { // business objects in higher level service layer need only deal with PersistenceException... throw new PersistenceException ("Cannot access persistent storage.", ex.StackTrace); } }
Spring.NET's data access library already has this kind of functionality (and is a whole lot more sophisticated)... the above example is merely being used for illustrative purposes.
This treatment of 'throws advice', and of
Spring.NET's implementation of it is rather simplistic.
'throws advice' features that have been omitted
include the fact that one can define exception handling methods that
permit access to the original object, method, and method arguments of
the advised method invocation that threw the original exception. This
is a quickstart guide though, and is not meant to be exhaustive... do
consult the 'throws advice' section of the
reference documentation, which describes how to declare an exception
handling method that gives one access to the above extra objects, and
how to declare multiple exception handling methods on the same
IThrowsAdvice
implementation class (see Section 13.3.2.3, “Throws advice”).
In a nutshell, introductions are all about adding new state and behaviour to arbitrary objects... transparently and at runtime. Introductions (also called mixins) allow one to emulate multiple inheritance, typically with an eye towards applying crosscutting state and operations to a wide swathe of objects in your application that don't share the same inheritance hierarchy.
The examples shown so far have all demonstrated the application of a single advice instance to an advised object. Spring.NET's flavor of AOP would be pretty poor if one could only apply a single advice instance per advised object... it is perfectly valid to apply multiple advice to an advised object. For example, one might apply transactional advice to a service object, and also apply a security access checking advice to that same advised service object.
In the interests of keeping this section lean and tight, let's simply apply all of the advice types that have been previously described to a single advised object... in this first instance we'll just use the default pointcut which means that every possible joinpoint will be advised, and you'll be able to see that the various advice instances are applied in order.
Please do consult the class definitions for the following
previously defined advice types to see exactly what each advice type
implementation does... we're going to be using single instances of the
ConsoleLoggingAroundAdvice
,
ConsoleLoggingBeforeAdvice
,
ConsoleLoggingAfterAdvice
, and
ConsoleLoggingThrowsAdvice
advice to advise a
single instance of the ServiceCommand
class.
You can find the following listing and executable application in
the AopQuickStart solution in the project
Spring.AopQuickStart.Step1
.
ProxyFactory factory = new ProxyFactory(new ServiceCommand()); factory.AddAdvice(new ConsoleLoggingBeforeAdvice()); factory.AddAdvice(new ConsoleLoggingAfterAdvice()); factory.AddAdvice(new ConsoleLoggingThrowsAdvice()); factory.AddAdvice(new ConsoleLoggingAroundAdvice()); ICommand command = (ICommand) factory.GetProxy(); command.Execute();
Here is the Spring.NET XML configuration for declaratively applying multiple advice.
You can find the following listing and executable application in
the AopQuickStart solution in the project
Spring.AopQuickStart.Step2
.
<object id="throwsAdvice" type="Spring.Examples.AopQuickStart.ConsoleLoggingThrowsAdvice"/> <object id="afterAdvice" type="Spring.Examples.AopQuickStart.ConsoleLoggingAfterAdvice"/> <object id="beforeAdvice" type="Spring.Examples.AopQuickStart.ConsoleLoggingBeforeAdvice"/> <object id="aroundAdvice" type="Spring.Examples.AopQuickStart.ConsoleLoggingAroundAdvice"/> <object id="myServiceObject" type="Spring.Aop.Framework.ProxyFactoryObject"> <property name="target"> <object id="myServiceObjectTarget" type="Spring.Examples.AopQuickStart.ServiceCommand"/> </property> <property name="interceptorNames"> <list> <value>throwsAdvice</value> <value>afterAdvice</value> <value>beforeAdvice</value> <value>aroundAdvice</value> </list> </property> </object>
In case it is not immediately apparent, remember that advice is just a plain old .NET object (a PONO); advice can have constructors that can take any number of parameters, and like any other .NET class, advice can have properties. What this means is that one can leverage the power of the Spring.NET IoC container to apply the IoC principle to one's advice, and in so doing reap all the benefits of Dependency Injection.
Consider the case of throws advice that needs to report (fatal) exceptions to a first line support centre. The throws advice could declare a dependency on a reporting service via a .NET property, and the Spring.NET container could dependency inject the reporting service dependency into the throws advice when it is being created; the reporting dependency might be a simple Log4NET wrapper, or a Windows EventLog wrapper, or a custom reporting exception reporting service that sends detailed emails concerning the fatal exception.
Also bear in mind the fact that Spring.NET's AOP implementation is quite independent of Spring.NET's IoC container. As you have seen, the various examples used in this have illustrated both programmatic and declarative AOP configuration (the latter being illustrated via Spring.NET's IoC XML configuration mechanism).
The preceding treatment of Spring.NET AOP has (quite intentionally) been decidedly simple. The overarching aim was to convey the concepts of Spring.NET AOP... this section of the Spring.NET AOP guide contains a number of real world examples of the application of Spring.NET AOP.
This example illustrates one of the more common usages of AOP... caching.
Lets consider the scenario where we have some static reference
data that needs to be kept around for the duration of an application.
The data will almost never change over the uptime of an application, and
it exists only in the database to satisfy referential integrity amongst
the various relations in the database schema. An example of such static
(and typically immutable) reference data would be a collection of
Country
objects (comprising a country name and a
code). What we would like to do is suck in the collection of
Country
objects and then pin them in a cache.
This saves us having to hit the back end database again and again every
time we need to reference a country in our application (for example, to
populate dropdown controls in a Windows Forms desktop
application).
The Data Access Object (DAO) that will load the collection of
Country
objects is called
AdoCountryDao
(it is an implementation of the
data-access-technology agnostic DAO interface called
ICountryDao
). The implementation of the
AdoCountryDao
is quite simple, in that every time
the FindAllCountries
instance method is called, an
instance will query the database for an
IDataReader
and hydrate zero or more
Country
objects using the returned data.
public class AdoCountryDao : ICountryDao { public IList FindAllCountries () { // implementation elided for clarity... return countries; } }
Ideally, what we would like to have happen is for the results of
the first call to the
FindAllCountries
instance method to be cached. We
would also like to do this in a non-invasive way, because caching is
something that we might want to apply at any number of points across the
codebase of our application. So, to address what we have identified as a
cross cutting concern, we can use Spring.NET AOP to
implement the caching.
The mechanism that this example is going to use to identify (or
pick out) areas in our application that we would like to apply caching
to is a .NET Attribute
. Spring.NET ships with a
number of useful custom .NET Attribute
implementations, one of which is the cunningly named
CacheAttribute
. In the specific case of this
example, we are simply going to decorate the definition of the
FindAllCountries
instance method with the
CacheAttribute
.
public class AdoCountryDao : ICountryDao { [Cache] public IList FindAllCountries () { // implementation elided for clarity... return countries; } }
The SpringAir reference application that is packaged as part of the Spring.NET distribution comes with a working example of caching applied using Spring.NET AOP (see Chapter 37, SpringAir - Reference Application).
This recipe show how easy it is to instrument the classes and objects in an application for performance monitoring. The performance monitoring implementation uses one of the (many) Windows performance counters to display and track the performance data.
This final recipe describes a simple (but really quite useful) aspect... retry logic. Using Spring.NET AOP, it is quite easy to surround an operation such as a method that opens a connection to a database with a (configurable) aspect that tries to obtain a database connection any number of times in the event of a failure.
Spring.NET AOP is an 80% AOP solution, in that it only tries to solve the 80% of those cases where AOP is a good fit in a typical enterprise application. This final section of the Spring.NET AOP guide describes where Spring.NET AOP is typically useful (the 80%), as well as where Spring.NET AOP is not a good fit (the 20%).