Aspect-Oriented Programming (AOP) complements OOP by providing another way of thinking about program structure. Whereas OO decomposes applications into a hierarchy of objects, AOP decomposes programs into aspects or concerns. This enables the modularization of concerns such as transaction management that would otherwise cut across multiple objects (such concerns are often termed crosscutting concerns).
One of the key components of Spring.NET is the AOP framework. While the Spring.NET IoC container does not depend on AOP, meaning you don't need to use AOP if you don't want to, AOP complements Spring.NET IoC to provide a very capable middleware solution.
AOP is used in Spring.NET:
-
To provide declarative enterprise services, especially as a replacement for COM+ declarative services. The most important such service is declarative transaction management, which builds on Spring.NET's transaction abstraction. This functionality is planed for an upcoming release of Spring.NET
-
To allow users to implement custom aspects, complementing their use of OOP with AOP.
Thus you can view Spring.NET AOP as either an enabling technology that allows Spring.NET to provide declarative transaction management without COM+; or use the full power of the Spring.NET AOP framework to implement custom aspects.
For those who would like to hit the ground running and start exploring how to use Spring's AOP functionality, head on over to Chapter 34, AOP Guide.
Let us begin by defining some central AOP concepts. These terms are not Spring.NET-specific. Unfortunately, AOP terminology is not particularly intuitive. However, it would be even more confusing if Spring.NET used its own terminology.
-
Aspect: A modularization of a concern for which the implementation might otherwise cut across multiple objects. Transaction management is a good example of a crosscutting concern in enterprise applications. Aspects are implemented using Spring.NET as Advisors or interceptors.
-
Joinpoint: Point during the execution of a program, such as a method invocation or a particular exception being thrown.
-
Advice: Action taken by the AOP framework at a particular joinpoint. Different types of advice include "around," "before" and "throws" advice. Advice types are discussed below. Many AOP frameworks, including Spring.NET, model an advice as an interceptor, maintaining a chain of interceptors "around" the joinpoint.
-
Pointcut: A set of joinpoints specifying when an advice should fire. An AOP framework must allow developers to specify pointcuts: for example, using regular expressions.
-
Introduction: Adding methods or fields to an advised class. Spring.NET allows you to introduce new interfaces to any advised object. For example, you could use an introduction to make any object implement an
IAuditable
interface, to simplify the tracking of changes to an object's state. -
Target object: Object containing the joinpoint. Also referred to as advised or proxied object.
-
AOP proxy: Object created by the AOP framework, including advice. In Spring.NET, an AOP proxy is a dynamic proxy that uses IL code generated at runtime.
-
Weaving: Assembling aspects to create an advised object. This can be done at compile time (using the Gripper-Loom.NET compiler, for example), or at runtime. Spring.NET performs weaving at runtime.
Different advice types include:
-
Around advice: Advice that surrounds a joinpoint such as a method invocation. This is the most powerful kind of advice. Around advice will perform custom behaviour before and after the method invocation. They are responsible for choosing whether to proceed to the joinpoint or to shortcut executing by returning their own return value or throwing an exception.
-
Before advice: Advice that executes before a joinpoint, but which does not have the ability to prevent execution flow proceeding to the joinpoint (unless it throws an exception).
-
Throws advice: Advice to be executed if a method throws an exception. Spring.NET provides strongly typed throws advice, so you can write code that catches the exception (and subclasses) you're interested in, without needing to cast from Exception.
-
After returning advice: Advice to be executed after a joinpoint completes normally: for example, if a method returns without throwing an exception.
Spring.NET provides a full range of advice types. We recommend
that you use the least powerful advice type that can implement the
required behaviour. For example, if you need only to update a cache with
the return value of a method, you are better off implementing an after
returning advice than an around advice, although an around advice can
accomplish the same thing. Using the most specific advice type provides
a simpler programming model with less potential for errors. For example,
you don't need to invoke the proceed()
method on the
IMethodInvocation
used for around advice, and hence
can't fail to invoke it.
The pointcut concept is the key to AOP, distinguishing AOP from older technologies offering interception. Pointcuts enable advice to be targeted independently of the OO hierarchy. For example, an around advice providing declarative transaction management can be applied to a set of methods spanning multiple objects. Thus pointcuts provide the structural element of AOP.
Spring.NET AOP is implemented in pure C#. There is no need for a special compilation process - all weaving is done at runtime. Spring.NET AOP does not need to control or modify the way in which assemblies are loaded, nor does it rely on unmanaged APIs, and is thus suitable for use in any CLR environment.
Spring.NET currently supports interception of method invocations. Field interception is not implemented, although support for field interception could be added without breaking the core Spring.NET AOP APIs.
Field interception arguably violates OO encapsulation. We don't believe it is wise in application development.
Spring.NET provides classes to represent pointcuts and different advice types. Spring.NET uses the term advisor for an object representing an aspect, including both an advice and a pointcut targeting it to specific joinpoints.
Different advice types are IMethodInterceptor
(from the AOP Alliance interception API); and the advice interfaces
defined in the Spring.Aop
namespace. All advices must
implement the AopAlliance.Aop.IAdvice
tag interface.
Advices supported out the box are IMethodInterceptor
; IThrowsAdvice
; IBeforeAdvice
;
and IAfterReturningAdvice
. We'll discuss advice types
in detail below.
Spring.NET provides a .NET translation of the Java interfaces
defined by the
AOP Alliance
. Around advice must implement the AOP Alliance
AopAlliance.Interceptr.IMethodInterceptor
interface.
Whilst there is wide support for the AOP Alliance in Java, Spring.NET is
currently the only .NET AOP framework that makes use of these
interfaces. In the short term, this will provide a consistent
programming model for those doing development in both .NET and Java, and
in the longer term, we hope to see more .NET projects adopt the AOP
Alliance interfaces.
The aim of Spring.NET AOP support is not to provide a comprehensive AOP implementation on par with the functionality available in AspectJ. However, Spring.NET AOP provides an excellent solution to most problems in .NET applications that are amenable to AOP.
Thus, it is common to see Spring.NET's AOP functionality used in conjunction with a Spring.NET IoC container. AOP advice is specified using normal object definition syntax (although this allows powerful "autoproxying" capabilities); advice and pointcuts are themselves managed by Spring.NET IoC.
Spring.NET generates AOP proxies at runtime using classes from the System.Reflection.Emit namespace to create necessary IL code for the proxy class. This results in proxies that are very efficient and do not impose any restrictions on the inheritance hierarchy.
Another common approach to AOP proxy implementation in .NET is to use ContextBoundObject and the .NET remoting infrastructure as an interception mechanism. We are not very fond of ContextBoundObject approach because it requires classes that need to be proxied to inherit from the ContextBoundObject either directly or indirectly. In our opinion this an unnecessary restriction that influences how you should design your object model and also excludes applying AOP to "3rd party" classes that are not under your direct control. Context-bound proxies are also an order of magnitude slower than IL-generated proxies, due to the overhead of the context switching and .NET remoting infrastructure.
Spring.NET AOP proxies are also "smart" - in that because proxy configuration is known during proxy generation, the generated proxy can be optimized to invoke target methods via reflection only when necessary (i.e. when there are advices applied to the target method). In all other cases the target method will be called directly, thus avoiding performance hit caused by the reflective invocation.
Finally, Spring.NET AOP proxies will never return a raw reference to a target object. Whenever a target method returns a raw reference to a target object (i.e. "return this;"), AOP proxy will recognize what happened and will replace the return value with a reference to itself instead.
The current implementation of the AOP proxy generator uses object composition to delegate calls from the proxy to a target object, similar to how you would implement a classic Decorator pattern. This means that classes that need to be proxied have to implement one or more interfaces, which is in our opinion not only a less-intruding requirement than ContextBoundObject inheritance requirements, but also a good practice that should be followed anyway for the service classes that are most common targets for AOP proxies.
In a future release we will implement proxies using inheritance, which will allow you to proxy classes without interfaces as well and will remove some of the remaining raw reference issues that cannot be solved using composition-based proxies.
Let's look at how Spring.NET handles the crucial pointcut concept.
Spring.NET's pointcut model enables pointcut reuse independent of advice types. It's possible to target different advice using the same pointcut.
The Spring.Aop.IPointcut
interface is the
central interface, used to target advices to particular types and
methods. The complete interface is shown below:
public interface IPointcut { ITypeFilter TypeFilter { get; } IMethodMatcher MethodMatcher { get; } }
Splitting the IPointcut
interface into two
parts allows reuse of type and method matching parts, and fine-grained
composition operations (such as performing a "union" with another method
matcher).
The ITypeFilter
interface is used to restrict
the pointcut to a given set of target classes. If the
Matches()
method always returns true, all target
types will be matched:
public interface ITypeFilter { bool Matches(Type type); }
The IMethodMatcher
interface is normally more
important. The complete interface is shown below:
public interface IMethodMatcher { bool IsRuntime { get; } bool Matches(MethodInfo method, Type targetType); bool Matches(MethodInfo method, Type targetType, object[] args); }
The Matches(MethodInfo, Type)
method is used to
test whether this pointcut will ever match a given method on a target
type. This evaluation can be performed when an AOP proxy is created, to
avoid the need for a test on every method invocation. If the 2-argument
matches method returns true for a given method, and the
IsRuntime
property for the
IMethodMatcher
returns true, the 3-argument matches
method will be invoked on every method invocation. This enables a
pointcut to look at the arguments passed to the method invocation
immediately before the target advice is to execute.
Most IMethodMatchers
are static, meaning that
their IsRuntime
property returns false. In this case,
the 3-argument Matches
method will never be
invoked.
Whenever possible, try to make pointcuts static... this allows the AOP framework to cache the results of pointcut evaluation when an AOP proxy is created.
Spring.NET supports operations on pointcuts: notably, union and intersection.
Union means the methods that either pointcut matches.
Intersection means the methods that both pointcuts match.
Union is usually more useful.
Pointcuts can be composed using the static methods in the Spring.Aop.Support.Pointcuts class, or using the ComposablePointcut class in the same namespace.
Spring.NET provides several convenient pointcut implementations. Some can be used out of the box; others are intended to be subclassed in application-specific pointcuts.
Static pointcuts are based on method and target class, and cannot take into account the method's arguments. Static pointcuts are sufficient--and best--for most usages. It's possible for Spring.NET to evaluate a static pointcut only once, when a method is first invoked: after that, there is no need to evaluate the pointcut again with each method invocation.
Let's consider some static pointcut implementations included with Spring.NET.
One obvious way to specify static pointcuts is using regular
expressions. Several AOP frameworks besides Spring.NET make this
possible. The
Spring.Aop.Support.SdkRegularExpressionMethodPointcut
class is a generic regular expression pointcut, that uses the
regular expression classes from the .NET BCL.
Using this class, you can provide a list of pattern Strings. If any of these is a match, the pointcut will evaluate to true (so the result is effectively the union of these pointcuts.). The matching is done against the full class name so you can use this pointcut if you would like to apply advice to all the classes in a particular namespace.
The usage is shown below:
<object id="settersAndAbsquatulatePointcut" type="Spring.Aop.Support.SdkRegularExpressionMethodPointcut, Spring.Aop"> <property name="patterns"> <list> <value>.*set.*</value> <value>.*absquatulate</value> </list> </property> </object>
As a convenience, Spring provides the
RegularExpressionMethodPointcutAdvisor
class that
allows us to reference an IAdvice
instance as
well as defining the pointcut rules (remember that an
IAdvice
instance can be an interceptor, before
advice, throws advice etc.) This simplifies wiring, as the one
object serves as both pointcut and advisor, as shown below:
<object id="settersAndAbsquatulateAdvisor" type="Spring.Aop.Support.RegularExpressionMethodPointcutAdvisor, Spring.Aop"> <property name="advice"> <ref local="objectNameOfAopAllianceInterceptor"/> </property> <property name="patterns"> <list> <value>.*set.*</value> <value>.*absquatulate</value> </list> </property> </object>
The RegularExpressionMethodPointcutAdvisor
class can be used with any Advice
type.
pattern
and specify a single value instead of using the property name
patterns
and specifying a list.
You may also specify a Regex
object from
the System.Text.RegularExpressions
namespace. The
built in RegexConverter
class will perform the
conversion. See Section 6.4, “Built-in TypeConverters”
for more information on Spring's build in type converters. The Regex
object is created as any other object within the IoC container.
Using an inner-object definition for the Regex object is a handy way
to keep the definition close to the PointcutAdvisor declaration.
Note that the class
SdkRegularExpressionMethodPointcut
has a
DefaultOptions
property to set the regular
expression options if they are not explicitly specified in the
constructor.
Pointcuts can be specified by matching an attribute type that
is associated with a method. Advice associated with this pointcut
can then read the metadata associated with the attribute to
configure itself. The class
AttributeMatchMethodPointcut
provides this
functionality. Sample usage that will match all methods that have
the attribute Spring.Attributes.CacheAttribute
is
shown below.
<object id="cachePointcut" type="Spring.Aop.Support.AttributeMatchMethodPointcut, Spring.Aop"> <property name="Attribute" value="Spring.Attributes.CacheAttribute, Spring.Core"/> </object>
This can be used with a
DefaultPointcutAdvisor
as shown
below
<object id="cacheAspect" type="Spring.Aop.Support.DefaultPointcutAdvisor, Spring.Aop"> <property name="Pointcut"> <object type="Spring.Aop.Support.AttributeMatchMethodPointcut, Spring.Aop"> <property name="Attribute" value="Spring.Attributes.CacheAttribute, Spring.Core"/> </object> </property> <property name="Advice" ref="aspNetCacheAdvice"/> </object>
where aspNetCacheAdvice is an implementation
of an IMethodInterceptor
that caches method
return values. See the SDK docs for
Spring.Aop.Advice.CacheAdvice
for more
information on this particular advice.
As a convenience the class
AttributeMatchMethodPointcutAdvisor
is provided
to defining an attribute based Advisor as a somewhat shorter
alternative to using the generic DefaultPointcutAdvisor. An example
is shown below.
<object id="AspNetCacheAdvice" type="Spring.Aop.Support.AttributeMatchMethodPointcutAdvisor, Spring.Aop"> <property name="advice"> <object type="Aspect.AspNetCacheAdvice, Aspect"/> </property> <property name="attribute" value="Framework.AspNetCacheAttribute, Framework" /> </object>
Dynamic pointcuts are costlier to evaluate than static pointcuts. They take into account method arguments, as well as static information. This means that they must be evaluated with every method invocation; the result cannot be cached, as arguments will vary.
The main example is the control flow
pointcut.
Spring.NET control flow pointcuts are conceptually similar to
AspectJ cflow pointcuts, although less
powerful. (There is currently no way to specify that a pointcut
executes below another pointcut.). A control flow pointcut is
dynamic because it is evaluated against the current call stack for
each method invocation. For example, if method ClassA.A() calls
ClassB.B() then the execution of ClassB.B() has occurred in
ClassA.A()'s control flow. A control flow pointcut allows advice to
be applied to the method ClassA.A() but only when called from
ClassB.B() and not when ClassA.A() is executed from another call
stack. Control flow pointcuts are specified using the
Spring.Aop.Support.ControlFlowPointcut
class.
Note | |
---|---|
Control flow pointcuts are significantly more expensive to evaluate at runtime than even other dynamic pointcuts. |
When using control flow point cuts some attention should be
paid to the fact that at runtime the JIT compiler can inline the
methods, typically for increased performance, but with the
consequence that the method no longer appears in the current call
stack. This is because inlining takes the callee's IL code and
inserts it into the caller's IL code effectively removing the method
call. The information returned from
System.Diagnostics.StackTrace
, used in the
implementation of ControlFlowPointcut
is subject
to these optimizations and therefore a control flow pointcut will
not match if the method has been inlined.
Generally speaking, a method will be a candidate for inlining when its code is 'small', just a few lines of code (less than 32 bytes of IL). For some interesting reading on this process read David Notario's blog entries (JIT Optimizations I and JIT Optimizations II). Additionally, when an assembly is compiled with a Release configuration the assembly metadata instructs the CLR to enable JIT optimizations. When compiled with a Debug configuration the CLR will disable (some?) these optimizations. Empirically, method inlining is turned off in a Debug configuration.
The way to ensure that your control flow pointcut will not be
overlooked because of method inlining is to apply the
System.Runtime.CompilerServices.MethodImplAttribute
attribute with the value
MethodImplOptions.NoInlining
. In this (somewhat
artificial) simple example, if the code is compiled in release mode
it will not match a control flow pointcut for the method
"GetAge".
public int GetAge(IPerson person) { return person.GetAge(); }
However, applying the attributes as shown below will prevent the method from being inlined even in a release build.
[MethodImpl(MethodImplOptions.NoInlining)] public int GetAge(IPerson person) { return person.GetAge(); }
Because pointcuts in Spring.NET are .NET types, rather than language features (as in AspectJ) it is possible to declare custom pointcuts, whether static or dynamic. However, there is no support out of the box for the sophisticated pointcut expressions that can be coded in the AspectJ syntax. However, custom pointcuts in Spring.NET can be as arbitrarily complex as any object model.
Spring.NET provides useful pointcut superclasses to help you to implement your own pointcuts.
Because static pointcuts are the most common and generally useful
pointcut type, you'll probably subclass
StaticMethodMatcherPointcut
, as shown below. This
requires you to implement just one abstract method (although it is
possible to override other methods to customize behaviour):
public class TestStaticPointcut : StaticMethodMatcherPointcut { public override bool Matches(MethodInfo method, Type targetType) { // return true if custom criteria match } }
Let's now look at how Spring.NET AOP handles advice.
Spring.NET advices can be shared across all advised objects, or unique to each advised object. This corresponds to per-class or per-instance advice.
Per-class advice is used most often. It is appropriate for generic advice such as transaction advisors. These do not depend on the state of the proxied object or add new state; they merely act on the method and arguments.
Per-instance advice is appropriate for introductions, to support mixins. In this case, the advice adds state to the proxied object.
It's possible to use a mix of shared and per-instance advice in the same AOP proxy.
Spring.NET provides several advice types out of the box, and is extensible to support arbitrary advice types. Let us look at the basic concepts and standard advice types.
The most fundamental advice type in Spring.NET is interception around advice.
Spring.NET is compliant with the AOP Alliance interface for around advice using method interception. Around advice is implemented using the following interface:
public interface IMethodInterceptor : IInterceptor { object Invoke(IMethodInvocation invocation); }
The IMethodInvocation
argument to the
Invoke()
method exposes the method being invoked;
the target joinpoint; the AOP proxy; and the arguments to the method.
The Invoke()
method should return the invocation's
result: the return value of the joinpoint.
A simple IMethodInterceptor
implementation
looks as follows:
public class DebugInterceptor : IMethodInterceptor { public object Invoke(IMethodInvocation invocation) { Console.WriteLine("Before: invocation=[{0}]", invocation); object rval = invocation.Proceed(); Console.WriteLine("Invocation returned"); return rval; } }
Note the call to the IMethodInvocation's
Proceed()
method. This proceeds down the
interceptor chain towards the joinpoint. Most interceptors will invoke
this method, and return its return value. However, an
IMethodInterceptor, like any around advice, can return a different
value or throw an exception rather than invoke the
Proceed()
method. However, you don't want to do
this without good reason!
A simpler advice type is a before
advice. This does not need an
IMethodInvocation
object, since it will only be
called before entering the method.
The main advantage of a before advice is that there is no need
to invoke the Proceed()
method, and therefore no
possibility of inadvertently failing to proceed down the interceptor
chain.
The IMethodBeforeAdvice
interface is shown
below.
public interface IMethodBeforeAdvice : IBeforeAdvice { void Before(MethodInfo method, object[] args, object target); }
Note the return type is void
. Before advice
can insert custom behaviour before the joinpoint executes, but cannot
change the return value. If a before advice throws an exception, this
will abort further execution of the interceptor chain. The exception
will propagate back up the interceptor chain. If it is unchecked, or
on the signature of the invoked method, it will be passed directly to
the client; otherwise it will be wrapped in an unchecked exception by
the AOP proxy.
An example of a before advice in Spring.NET, which counts all methods that return normally:
public class CountingBeforeAdvice : IMethodBeforeAdvice { private int count; public void Before(MethodInfo method, object[] args, object target) { ++count; } public int Count { get { return count; } } }
Before advice can be used with any pointcut.
Throws advice is invoked after the return of the joinpoint if
the joinpoint threw an exception. The
Spring.Aop.IThrowsAdvice
interface does not contain
any methods: it is a tag interface identifying that the implementing
advice object implements one or more typed throws advice methods.
These throws advice methods must be of the form:
AfterThrowing([MethodInfo method, Object[] args, Object target], Exception subclass)
Throws-advice methods must be named
'AfterThrowing'
. The return value will be ignored
by the Spring.NET AOP framework, so it is typically
void
. With regard to the method arguments, only the
last argument is required. Thus there are exactly
one or four arguments, depending on whether the
advice method is interested in the method, method arguments and the
target object.
The following method snippets show examples of throws advice.
This advice will be invoked if a
RemotingException
is thrown (including
subclasses):
public class RemoteThrowsAdvice : IThrowsAdvice { public void AfterThrowing(RemotingException ex) { // Do something with remoting exception } }
The following advice is invoked if a
SqlException
is thrown. Unlike the above advice, it
declares 4 arguments, so that it has access to the invoked method,
method arguments and target object:
public class SqlExceptionThrowsAdviceWithArguments : IThrowsAdvice { public void AfterThrowing(MethodInfo method, object[] args, object target, SqlException ex) { // Do something will all arguments } }
The final example illustrates how these two methods could be
used in a single class, which handles both
RemotingException
and
SqlException
. Any number of throws advice methods
can be combined in a single class, as can be seen in the following
example.
public class CombinedThrowsAdvice : IThrowsAdvice { public void AfterThrowing(RemotingException ex) { // Do something with remoting exception } public void AfterThrowing(MethodInfo method, object[] args, object target, SqlException ex) { // Do something will all arguments } }
Finally, it is worth stating that throws advice is only applied
to the actual exception being thrown. What does this mean? Well, it
means that if you have defined some throws advice that handles
RemotingException
s, the applicable
AfterThrowing
method will only be invoked if the type of the thrown
exception is RemotingException
... if a
RemotingException
has been thrown and subsequently
wrapped inside another exception before the exception bubbles up to
the throws advice interceptor, then the throws advice that handles
RemotingException
s will never be called. Consider a business method
that is advised by throws advice that handles
RemotingException
s; if during the course of a
method invocation said business method throws a RemoteException... and
subsequently wraps said RemotingException
inside a
business-specific BadConnectionException
(see the
code snippet below) before throwing the exception, then the throws
advice will never be able to respond to the
RemotingException
... because all the throws advice
sees is a BadConnectionException
. The fact that the
RemotingException
is wrapped up inside the
BadConnectionException
is immaterial.
public void BusinessMethod() { try { // do some business operation... } catch (RemotingException ex) { throw new BadConnectionException("Couldn't connect.", ex); } }
Note | |
---|---|
Please note that throws advice can be used with any pointcut. |
An after returning advice in Spring.NET must implement the
Spring.Aop.IAfterReturningAdvice
interface, shown
below:
public interface IAfterReturningAdvice : IAdvice { void AfterReturning(object returnValue, MethodBase method, object[] args, object target); }
An after returning advice has access to the return value (which it cannot modify), invoked method, methods arguments and target.
The following after returning advice counts all successful method invocations that have not thrown exceptions:
public class CountingAfterReturningAdvice : IAfterReturningAdvice { private int count; public void AfterReturning(object returnValue, MethodBase m, object[] args, object target) { ++count; } public int Count { get { return count; } } }
This advice doesn't change the execution path. If it throws an exception, this will be thrown up the interceptor chain instead of the return value.
Note | |
---|---|
Please note that after-returning advice can be used with any pointcut. |
When multiple pieces of advice want to run on the same joinpoint the precedence is determined by having the advice implement the IOrdered interface or by specifying order information on an advisor.
Spring.NET allows you to add new methods and properties to an advised class. This would typically be done when the functionality you wish to add is a crosscutting concern and want to introduce this functionality as a change to the static structure of the class hierarchy. For example, you may want to cast objects to the introduction interface in your code. Introductions are also a means to emulate multiple inheritance.
Introduction advice is defined by using a normal interface
declaration that implements the tag interface
IAdvice
.
Note | |
---|---|
The need for implementing this marker interface will likely be removed in future versions. |
As
an example, consider the interface IAuditable
that
describes the last modified time of an object.
public interface IAuditable : IAdvice { DateTime LastModifiedDate { get; set; } }where
public interface IAdvice { }
Access to the advised object can be obtained by implementing the
interface ITargetAware
public interface ITargetAware { IAopProxy TargetProxy { set; } }
with the IAopProxy
reference providing a
layer of indirection through which the advised object can be accessed.
public interface IAopProxy { object GetProxy(); }
A simple class that demonstrates this functionality is shown below.
public interface IAuditable : IAdvice, ITargetAware { DateTime LastModifiedDate { get; set; } }
A class that implements this interface is shown below.
public class AuditableMixin : IAuditable { private DateTime date; private IAopProxy targetProxy; public AuditableMixin() { date = new DateTime(); } public DateTime LastModifiedDate { get { return date; } set { date = value; } } public IAopProxy TargetProxy { set { targetProxy = value; } } }
Introduction advice is not associated with a pointcut, since it
applies at the class and not the method level. As such, introductions
use their own subclass of the interface IAdvisor
,
namely IIntroductionAdvisor
, to specify the types
that the introduction can be applied to.
public interface IIntroductionAdvisor : IAdvisor { ITypeFilter TypeFilter { get; } Type[] Interfaces { get; } void ValidateInterfaces(); }
The TypeFilter
property returns the filter
that determines which target classes this introduction should apply
to.
The Interfaces
property returns the
interfaces introduced by this advisor.
The ValidateInterfaces()
method is used
internally to see if the introduced interfaces can be implemented by
the introduction advice.
Spring.NET provides a default implementation of this interface
(the DefaultIntroductionAdvisor
class) that should
be sufficient for the majority of situations when you need to use
introductions. The most simple implementation of an introduction
advisor is a subclass that simply passes a new instance the base
constructor. Passing a new instance is important since we want a new
instance of the mixin classed used for each advised object.
public class AuditableAdvisor : DefaultIntroductionAdvisor { public AuditableAdvisor() : base(new AuditableMixin()) { } }
Other constructors let you explicitly specify the interfaces of the class that will be introduced. See the SDK documentation for more details.
We can apply this advisor Programatically, using the
IAdvised.AddIntroduction(),
method, or (the
recommended way) in XML configuration using the
IntroductionNames
property on
ProxyFactoryObject
, which will be discussed
later.
Unlike the AOP implementation in the Spring Framework for Java, introduction advice in Spring.NET is not implemented as a specialized type of interception advice. The advantage of this approach is that introductions are not kept in the interceptor chain, which allows some significant performance optimizations. When a method is called that has no interceptors, a direct call is used instead of reflection regardless of whether the target method is on the target object itself or one of the introductions. This means that introduced methods perform the same as target object methods, which could be useful for adding introductions to fine grained objects. The disadvantage is that if the mixin functionality would benefit from having access to the calling stack, it is not available. Introductions with this functionality will be addressed in a future version of Spring.NET AOP.
In Spring.NET, an advisor is a modularization of an aspect. Advisors typically incorporate both an advice and a pointcut.
Apart from the special case of introductions, any advisor can be
used with any advice. The
Spring.Aop.Support.DefaultPointcutAdvisor
class is the
most commonly used advisor implementation. For example, it can be used
with a IMethodInterceptor
,
IBeforeAdvice
or IThrowsAdvice
and
any pointcut definition.
Other convenience implementations provided are:
AttributeMatchMethodPointcutAdvisor
shown in usage
previously in Section 13.2.3.1.2, “Attribute pointcuts” for use with
attribute based pointcuts.
RegularExpressionMethodPointcutAdvisor
that will apply
pointcuts based on the matching a regular expression to method
names.
It is possible to mix advisor and advice types in Spring.NET in the same AOP proxy. For example, you could use a interception around advice, throws advice and before advice in one proxy configuration: Spring.NET will automatically create the necessary interceptor chain.
If you're using the Spring.NET IoC container for your business
objects - generally a good idea - you will want to use one of Spring.NET's
AOP-specific IFactoryObject
implementations (remember
that a factory object introduces a layer of indirection, enabling it to
create objects of a different type - Section 5.3.9, “Setting a reference using the members of other objects and
classes.”).
The basic way to create an AOP proxy in Spring.NET is to use the
Spring.Aop.Framework.ProxyFactoryObject
class. This
gives complete control over ordering and application of the pointcuts and
advice that will apply to your business objects. However, there are
simpler options that are preferable if you don't need such control.
The ProxyFactoryObject
, like other Spring.NET
IFactoryObject
implementations, introduces a level of
indirection. If you define a ProxyFactoryObject
with
name foo
, what objects referencing
foo
see is not the
ProxyFactoryObject
instance itself, but an object
created by the ProxyFactoryObject's
implementation of
the GetObject()
method. This method will create an
AOP proxy wrapping a target object.
One of the most important benefits of using a
ProxyFactoryObject
or other IoC-aware classes that
create AOP proxies, is that it means that advice and pointcuts can also
be managed by IoC. This is a powerful feature, enabling certain
approaches that are hard to achieve with other AOP frameworks. For
example, an advice may itself reference application objects (besides the
target, which should be available in any AOP framework), benefiting from
all the pluggability provided by Dependency Injection.
Like most IFactoryObject
implementations
provided with Spring.NET, the ProxyFactoryObject
is
itself a Spring.NET configurable object. Its properties are used
to:
-
Specify the target object that is to be proxied.
-
Specify the advice that is to be applied to the proxy.
Some key properties are inherited from the
Spring.Aop.Framework.ProxyConfig
class: this class is
the superclass for all AOP proxy factories in Spring.NET. Some of the
key properties include:
-
ProxyTargetType
: a boolean value that should be set to true if the target class is to be proxied directly, as opposed to just proxying the interfaces exposed on the target class. -
Optimize
: whether to apply aggressive optimization to created proxies. Don't use this setting unless you understand how the relevant AOP proxy handles optimization. The exact meaning of this flag will differ between proxy implementations and will generally result in a trade off between proxy creation time and runtime performance. Optimizations may be ignored by certain proxy implementations and may be disabled silently based on the value of other properties such asExposeProxy
. -
IsFrozen
: whether advice changes should be disallowed once the proxy factory has been configured. The default is false. -
ExposeProxy
: whether the current proxy should be exposed via theAopContext
so that it can be accessed by the target. (It's available via theIMethodInvocation
without the need for theAopContext
.) If a target needs to obtain the proxy andExposeProxy
istrue
, the target can use theAopContext.CurrentProxy
property. -
AopProxyFactory
: the implementation ofIAopProxyFactory
to use when generating a proxy. Offers a way of customizing whether to use remoting proxies, IL generation or any other proxy strategy. The default implementation will use IL generation to create composition-based proxies.
Other properties specific to the
ProxyFactoryObject
class include:
-
ProxyInterfaces
: the array ofstring
interface names we're proxying. -
InterceptorNames
:string
array ofIAdvisor
, interceptor or other advice names to apply. Ordering is significant... first come, first served that is. The first interceptor in the list will be the first to be able to interceptor the invocation (assuming it concerns a regular MethodInterceptor or BeforeAdvice).The names are object names in the current container, including objectnames from container hierarchies. You can't mention object references here since doing so would result in the
ProxyFactoryObject
ignoring the singleton setting of the advise. -
IntroductionNames
: The names of objects in the container that will be used as introductions to the target object. If the object referred to by name does not implement theIIntroductionAdvisor
it will be passed to the default constructor ofDefaultIntroductionAdvisor
and all of the objects interfaces will be added to the target object. Objects that implement theIIntroductionAdvisor
interface will be used as is, giving you a finer level of control over what interfaces you may want to expose and the types for which they will be matched against. -
IsSingleton
: whether or not the factory should return a single proxy object, no matter how often theGetObject()
method is called. SeveralIFactoryObject
implementations offer such a method. The default value istrue
. If you would like to be able to apply advice on a per-proxy object basis, use aIsSingleton
value offalse
and aIsFrozen
value offalse
. If you want to use stateful advice--for example, for stateful mixins--use prototype advices along with aIsSingleton
value offalse
.
Let's look at a simple example of
ProxyFactoryObject
in action. This example involves:
-
A target object that will be proxied. This is the "personTarget" object definition in the example below.
-
An
IAdvisor
and anIInterceptor
used to provide advice. -
An AOP proxy object definition specifying the target object (the personTarget object) and the interfaces to proxy, along with the advices to apply.
<object id="personTarget" type="MyCompany.MyApp.Person, MyCompany"> <property name="name" value="Tony"/> <property name="age" value="51"/> </object> <object id="myCustomInterceptor" type="MyCompany.MyApp.MyCustomInterceptor, MyCompany"> <property name="customProperty" value="configuration string"/> </object> <object id="debugInterceptor" type="Spring.Aop.Advice.DebugAdvice, Spring.Aop"> </object> <object id="person" type="Spring.Aop.Framework.ProxyFactoryObject, Spring.Aop"> <property name="proxyInterfaces" value="MyCompany.MyApp.IPerson"/> <property name="target" ref="personTarget"/> <property name="interceptorNames"> <list> <value>debugInterceptor</value> <value>myCustomInterceptor</value> </list> </property> </object>
Note that the InterceptorNames
property takes a
list of strings
: the object names of the interceptor
or advisors in the current context. Advisors, interceptors, before,
after returning and throws advice objects can be used. The ordering of
advisors is significant.
You might be wondering why the list doesn't hold object
references. The reason for this is that 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's necessary to be able to obtain an instance of
the prototype from the context; holding a reference isn't
sufficient.
The "person" object definition above can be used in place of an
IPerson
implementation, as follows:
IPerson person = (IPerson) factory.GetObject("person");
Other objects in the same IoC context can express a strongly typed dependency on it, as with an ordinary .NET object:
<object id="personUser" type="MyCompany.MyApp.PersonUser, MyCompany"> <property name="person" ref="person"/> </object>
The PersonUser
class in this example would
expose a property of type IPerson
. As far as it's
concerned, the AOP proxy can be used transparently in place of a "real"
person implementation. However, its type would be a proxy type. It would
be possible to cast it to the IAdvised
interface
(discussed below).
It's possible to conceal the distinction between target and proxy
using an anonymous inline object, as follows. (for
more information on inline objects see Section 5.3.2.3, “Inline objects”.) Only the
ProxyFactoryObject
definition is different; the
advice is included only for completeness:
<object id="myCustomInterceptor" type="MyCompany.MyApp.MyCustomInterceptor, MyCompany"> <property name="customProperty" value="configuration string"/> </object> <object id="debugInterceptor" type="Spring.Aop.Advice.DebugAdvice, Spring.Aop"> </object> <object id="person" type="Spring.Aop.Framework.ProxyFactoryObject, Spring.Aop"> <property name="proxyInterfaces" value="MyCompany.MyApp.IPerson"/> <property name="target"> <!-- Instead of using a reference to target, just use an inline object --> <object type="MyCompany.MyApp.Person, MyCompany"> <property name="name" value="Tony"/> <property name="age" value="51"/> </object> </property> <property name="interceptorNames"> <list> <value>debugInterceptor</value> <value>myCustomInterceptor</value> </list> </property> </object>
This has the advantage that there's only one object of type
Person
: useful if we want to prevent users of the
application context obtaining a reference to the un-advised object, or
need to avoid any ambiguity with Spring IoC
autowiring. There's also arguably an advantage in
that the ProxyFactoryObject definition is self-contained. However, there
are times when being able to obtain the un-advised target from the
factory might actually be an advantage: for
example, in certain test scenarios.
Let's look at an example of configuring the proxy objects
retrieved from ProxyFactoryObject
.
<!-- create the object to reference --> <object id="RealObjectTarget" type="MyRealObject" singleton="false"/> <!-- create the proxied object for everyone to use--> <object id="MyObject" type="Spring.Aop.Framework.ProxyFactoryObject, Spring.Aop"> <property name="proxyInterfaces" value="MyInterface" /> <property name="isSingleton" value="false"/> <property name="targetName" value="RealObjectTarget" /> </object>
If you are using a prototype as the target you must
set the TargetName
property with the name/object id
of your object and not use the property Target
with a
reference to that object. This will then allow a new proxy to be created
around a new prototype target instance.
Consider the above Spring.Net object configuration. Notice that
the IsSingleton
property of the
ProxyFactoryObject
instance is set to false. This
means that each proxy object will be unique. Thus, you can configure
each proxy object with its' own individual advice(s) using the following
syntax
// Will return un-advised instance of proxy object MyInterface myProxyObject1 = (MyInterface)ctx.GetObject("MyObject"); // myProxyObject1 instance now has an advice attached to it. IAdvised advised = (IAdvised)myProxyObject1; advised.AddAdvice( new DebugAdvice() ); // Will return a new, un-advised instance of proxy object MyInterface myProxyObject2 = (MyInterface)ctx.GetObject("MyObject");
What if you need to proxy a class, rather than one or more interfaces?
Imagine that in our example above, there was no
IPerson
interface, rather we needed to advise a class
called Person
that didn't implement any business
interface. In this case the ProxyFactoryObject
will
proxy all public virtual methods and properties if no interfaces are
explicitly specified or if no interfaces are found to be present on the
target object. One can configure Spring.NET to force the use of class
proxies, rather than interface proxies, by setting the
ProxyTargetType
property on the
ProxyFactoryObject
above to true.
Class proxying works by generating a subclass of the target class at runtime. Spring.NET configures this generated subclass to delegate method calls to the original target: the subclass is used to implement the Decorator pattern, weaving in the advice.
Class proxying should generally be transparent to users. However,
there is an important issue to consider:
Non-virtual
methods can't be advised, as
they can't be overridden. This may be a limiting factor when
using existing code as it has been common practice not to declare
methods as virtual by default.
Especially when defining transactional proxies, if you do not make use of the transaction namespace, you may end up with many similar proxy definitions. The use of parent and child object definitions, along with inner object definitions, can result in much cleaner and more concise proxy definitions.
First a parent, template, object definition is created for the proxy:
<object id="txProxyTemplate" abstract="true" type="Spring.Transaction.Interceptor.TransactionProxyFactoryObject, Spring.Data"> <property name="PlatformTransactionManager" ref="adoTransactionManager"/> <property name="TransactionAttributes"> <name-values> <add key="*" value="PROPAGATION_REQUIRED"/> </name-values> </property> </object>
This will never be instantiated itself, so may actually be incomplete. Then each proxy which needs to be created is just a child object definition, which wraps the target of the proxy as an inner object definition, since the target will never be used on its own anyway.
<object name="testObjectManager" parent="txProxyTemplate"> <property name="Target"> <object type="Spring.Data.TestObjectManager, Spring.Data.Integration.Tests"> <property name="TestObjectDao" ref="testObjectDao"/> </object> </property> </object>
It is of course possible to override properties from the parent template, such as in this case, the transaction propagation settings:
<object name="testObjectManager" parent="txProxyTemplate"> <property name="Target"> <object type="Spring.Data.TestObjectManager, Spring.Data.Integration.Tests"> <property name="TestObjectDao" ref="testObjectDao"/> </object> </property> <property name="TransactionAttributes"> <name-values> <add key="Save*" value="PROPAGATION_REQUIRED"/> <add key="Delete*" value="PROPAGATION_REQUIRED"/> <add key="Find*" value="PROPAGATION_REQUIRED,readonly"/> </name-values> </property> </object>
Note that in the example above, we have explicitly marked the parent object definition as abstract by using the abstract attribute, as described previously, so that it may not actually ever be instantiated. Application contexts (but not simple object factories) will by default pre-instantiate all singletons. It is therefore important (at least for singleton object) that if you have a (parent) object definition which you intend to use only as a template, and this definition specifies a class, you must make sure to set the abstract attribute to true, otherwise the application context will actually try to pre-instantiate it.
Spring creates AOP proxies built at runtime through the use of the TypeBuilder API.
Two types of proxies can be created, composition based or inheritance based. If the target object implements at least one interface then a composition based proxy will be created, otherwise an inheritance based proxy will be created.
The composition based proxy is implemented by creating a type that implements all the interfaces specified on the target object. The actual class name of this dynamic type is 'GUID' like. A private field holds the target object and the dynamic type implementation will first execute any advice before or after making the target object method call on the target object.
The inheritance based mechanism creates a dynamic type where that
inherits from the target type. This lets you downcast to the target type
if needed. Please note that in both cases a target method implementation
that calls other methods on the target object will not be advised. To
force inheritance based proxies you should either set the
ProxyTargetType
to true property of a ProxyFactory or
set the XML namespace element proxy-target-type = true
when using an AOP schema based configuration.
Note | |
---|---|
An important alternative approach to inheritance based proxies is disucssed in the next section. |
In .NET 2.0 you can define the assembly level attribute, InternalsVisibleTo, to allow access of internal interfaces/classes to specified 'friend' assemblies. If you need to create an AOP proxy on an internal class/interface add the following code, [assembly: InternalsVisibleTo("Spring.Proxy")] and [assembly: InternalsVisibleTo("Spring.DynamicReflection")] to your to AssemblyInfo file.
There is an important limitation in the inheritance based proxy as described above, all methods that manipulate the state of the object should be declared as virtual. Otherwise some method invocations get directed to the private 'target' field member and others to the base class. Winform object are an example of case where this approach does not apply. To address this limitation, a new post-processing mechanism was introduced in version 1.2 that creates a proxy type without the private 'target' field. Interception advice is added directly in the method body before invoking the base class method.
To use this new inheritance based proxy described in the note above, declare an instance of the InheritanceBasedAopConfigurer, and IObjectFactoryPostProcessor, in yoru configuraiton file. Here is an example.
<object type="Spring.Aop.Framework.AutoProxy.InheritanceBasedAopConfigurer, Spring.Aop"> <property name="ObjectNames"> <list> <value>Form*</value> <value>Control*</value> </list> </property> <property name="InterceptorNames"> <list> <value>debugInterceptor</value> </list> </property> </object> <object id="debugInterceptor" type="AopPlay.DebugInterceptor, AopPlay"/>
This configuraiton style is similar to the autoproxy by name approach described here and is particuarly appropriate when you want to apply advice to WinForm classes.
It's easy to create AOP proxies Programatically using Spring.NET. This enables you to use Spring.NET AOP without dependency on Spring.NET IoC.
The following listing shows creation of a proxy for a target object, with one interceptor and one advisor. The interfaces implemented by the target object will automatically be proxied:
ProxyFactory factory = new ProxyFactory(myBusinessInterfaceImpl);
factory.AddAdvice(myMethodInterceptor);
factory.AddAdvisor(myAdvisor);
IBusinessInterface tb = (IBusinessInterface) factory.GetProxy();
The first step is to construct an object of type
Spring.Aop.Framework.ProxyFactory
. You can create this
with a target object, as in the above example, or specify the interfaces
to be proxied in an alternate constructor.
You can add interceptors or advisors, and manipulate them for the
life of the ProxyFactory.
There are also convenience methods on
ProxyFactory
(inherited from
AdvisedSupport
) allowing you to add other advice types
such as before and throws advice. AdvisedSupport
is the
superclass of both ProxyFactory
and
ProxyFactoryObject
.
Note | |
---|---|
Integrating AOP proxy creation with the IoC framework is best practice in most applications. We recommend that you externalize configuration from .NET code with AOP, as in general. |
However you create AOP proxies, you can manipulate them using the
Spring.Aop.Framework.IAdvised
interface. Any AOP proxy
can be cast to this interface, whatever other interfaces it implements.
This interface includes the following methods and properties:
public interface IAdvised { IAdvisor[] Advisors { get; } IIntroductionAdvisor[] Introductions { get; } void AddInterceptor(IInterceptor interceptor); void AddInterceptor(int pos, IInterceptor interceptor); void AddAdvisor(IAdvisor advisor); void AddAdvisor(int pos, IAdvisor advisor); void AddIntroduction(IIntroductionAdvisor advisor); void AddIntroduction(int pos, IIntroductionAdvisor advisor); int IndexOf(IAdvisor advisor); int IndexOf(IIntroductionAdvisor advisor); bool RemoveAdvisor(IAdvisor advisor); void RemoveAdvisor(int index); bool RemoveInterceptor(IInterceptor interceptor); bool RemoveIntroduction(IIntroductionAdvisor advisor); void RemoveIntroduction(int index); void ReplaceIntroduction(int index, IIntroductionAdvisor advisor); bool ReplaceAdvisor(IAdvisor a, IAdvisor b); }
The Advisors
property will return an
IAdvisor
for every advisor, interceptor or other advice
type that has been added to the factory. If you added an
IAdvisor
, the returned advisor at this index will be
the object that you added. If you added an interceptor or other advice
type, Spring.NET will have wrapped this in an advisor with a
IPointcut
that always returns true
.
Thus if you added an IMethodInterceptor
, the advisor
returned for this index will be a
DefaultPointcutAdvisor
returning your
IMethodInterceptor
and an IPointcut
that matches all types and methods.
The AddAdvisor()
methods can be used to add any
IAdvisor
. Usually this will be the generic
DefaultPointcutAdvisor
, which can be used with any
advice or pointcut (but not for introduction).
By default, it's possible to add or remove advisors or interceptors even once a proxy has been created. The only restriction is that it's impossible to add or remove an introduction advisor, as existing proxies from the factory will not show the interface change. (You can obtain a new proxy from the factory to avoid this problem.)
It's questionable whether it's advisable (no pun intended) to modify advice on a business object in production, although there are no doubt legitimate usage cases. However, it can be very useful in development: for example, in tests. I have sometimes found it very useful to be able to add test code in the form of an interceptor or other advice, getting inside a method invocation I want to test. (For example, the advice can get inside a transaction created for that method: for example, to run SQL to check that a database was correctly updated, before marking the transaction for roll back.)
Depending on how you created the proxy, you can usually set a
Frozen
flag, in which case the
IAdvised
IsFrozen
property will
return true
, and any attempts to modify advice through
addition or removal will result in an
AopConfigException
. The ability to freeze the state of
an advised object is useful in some cases: For example, to prevent calling
code removing a security interceptor.
So far we've considered explicit creation of AOP proxies using a
ProxyFactoryObject
or similar factory objects. For
applications that would like create many AOP proxies, say across all the
classes in a service layer, this approach can lead to a lengthy
configuration file. To simplify the creation of many AOP proxies Spring
provides "autoproxy" capabilities that will automatically proxy object
definitions based on higher level criteria that will group together
multiple objects as candidates to be proxied.
This functionality is built on Spring "object post-processor" infrastructure, which enables modification of any object definition as the container loads. Refer to Section 5.9.1, “Customizing objects with IObjectPostProcessors” for general information on object post-processors.
In this model, you set up some special object definitions in your
XML object definition file configuring the auto proxy infrastructure. This
allows you just to declare the targets eligible for autoproxying: you
don't need to use ProxyFactoryObject
.
-
Using an autoproxy creator that refers to specific objects in the current context.
-
A special case of autoproxy creation that deserves to be considered separately; autoproxy creation driven by source-level attributes.
Autoproxying in general has the advantage of making it impossible for callers or dependencies to obtain an un-advised object. Calling GetObject("MyBusinessObject1") on an ApplicationContext will return an AOP proxy, not the target business object. The "inline object" idiom shown earlier in Section 13.5.3, “Proxying Interfaces” also offers this benefit.)
The namespace Spring.Aop.Framework.AutoProxy
provides generic autoproxy infrastructure, should you choose to write
your own autoproxy implementations, as well as several out-of-the-box
implementations. Two implementations are provided,
ObjectNameAutoProxyCreator
and
DefaultAdvisorAutoProxyCreator
. These are discussed
in the following sections.
The ObjectNameAutoProxyCreator
automatically
creates AOP proxies for object with names matching literal values or
wildcards. The pattern matching expressions supported are of the form
"*name", "name*", and "*name*" and exact name matching, i.e. "name".
The following simple classes are used to demonstrate this autoproxy
functionality.
public enum Language { English = 1, Portuguese = 2, Italian = 3 } public interface IHelloWorldSpeaker { void SayHello(); } public class HelloWorldSpeaker : IHelloWorldSpeaker { private Language language; public Language Language { set { language = value; } get { return language; } } public void SayHello() { switch (language) { case Language.English: Console.WriteLine("Hello World!"); break; case Language.Portuguese: Console.WriteLine("Oi Mundo!"); break; case Language.Italian: Console.WriteLine("Ciao Mondo!"); break; } } } public class DebugInterceptor : IMethodInterceptor { public object Invoke(IMethodInvocation invocation) { Console.WriteLine("Before: " + invocation.Method.ToString()); object rval = invocation.Proceed(); Console.WriteLine("After: " + invocation.Method.ToString()); return rval; } }
The following XML is used to automatically create an AOP proxy and apply a Debug interceptor to object definitions whose names match "English*" and "PortugueseSpeaker".
<object id="ProxyCreator" type="Spring.Aop.Framework.AutoProxy.ObjectNameAutoProxyCreator, Spring.Aop"> <property name="ObjectNames"> <list> <value>English*</value> <value>PortugeseSpeaker</value> </list> </property> <property name="InterceptorNames"> <list> <value>debugInterceptor</value> </list> </property> </object> <object id="debugInterceptor" type="AopPlay.DebugInterceptor, AopPlay"/> <object id="EnglishSpeakerOne" type="AopPlay.HelloWorldSpeaker, AopPlay"> <property name="Language" value="English"/> </object> <object id="EnglishSpeakerTwo" type="AopPlay.HelloWorldSpeaker, AopPlay"> <property name="Language" value="English"/> </object> <object id="PortugeseSpeaker" type="AopPlay.HelloWorldSpeaker, AopPlay"> <property name="Language" value="Portuguese"/> </object> <object id="ItalianSpeakerOne" type="AopPlay.HelloWorldSpeaker, AopPlay"> <property name="Language" value="Italian"/> </object>
As with ProxyFactoryObject
, there is an
InterceptorNames property rather than a list of interceptors, to allow
correct behavior for prototype advisors. Named "interceptors" can be
advisors or any advice type.
The same advice will be applied to all matching objects. Note that if advisors are used (rather than the interceptor in the above example), the pointcuts may apply differently to different objects.
Running the following simple program demonstrates the application of the AOP interceptor.
IApplicationContext ctx = ContextRegistry.GetContext(); IDictionary speakerDictionary = ctx.GetObjectsOfType(typeof(IHelloWorldSpeaker)); foreach (DictionaryEntry entry in speakerDictionary) { string name = (string)entry.Key; IHelloWorldSpeaker worldSpeaker = (IHelloWorldSpeaker)entry.Value; Console.Write(name + " says; "); worldSpeaker.SayHello(); }
The output is shown below
ItalianSpeakerOne says; Ciao Mondo! EnglishSpeakerTwo says; Before: Void SayHello() Hello World! After: Void SayHello() PortugeseSpeaker says; Before: Void SayHello() Oi Mundo! After: Void SayHello() EnglishSpeakerOne says; Before: Void SayHello() Hello World! After: Void SayHello()
A more general and extremely powerful auto proxy creator is
DefaultAdvisorAutoProxyCreator
. This will
automatically apply eligible advisors in the current application
context, without the need to include specific object names in the
autoproxy advisor's object definition. It offers the same merit of
consistent configuration and avoidance of duplication as
ObjectNameAutoProxyCreator
.
Using this mechanism involves:
-
Specifying a
DefaultAdvisorAutoProxyCreator
object definition -
Specifying any number of Advisors in the same or related contexts. Note that these must be Advisors, not just interceptors or other advices. This is necessary because there must be a pointcut to evaluate, to check the eligibility of each advice to candidate object definitions.
The DefaultAdvisorAutoProxyCreator
will
automatically evaluate the pointcut contained in each advisor, to see
what (if any) advice it should apply to each object defined in the
application context.
This means that any number of advisors can be applied automatically to each business object. If no pointcut in any of the advisors matches any method in a business object, the object will not be proxied.
The DefaultAdvisorAutoProxyCreator
is very
useful if you want to apply the same advice consistently to many
business objects. Once the infrastructure definitions are in place,
you can simply add new business objects without including specific
proxy configuration. You can also drop in additional aspects very
easily--for example, tracing or performance monitoring aspects--with
minimal change to configuration.
The following example demonstrates the use of
DefaultAdvisorAutoProxyCreator
. Expanding on the
previous example code used to demonstrate
ObjectNameAutoProxyCreator
we will add a new class,
SpeakerDao
, that acts as a Data Access Object to
find and store IHelloWorldSpeaker
objects.
public interface ISpeakerDao { IList FindAll(); IHelloWorldSpeaker Save(IHelloWorldSpeaker speaker); } public class SpeakerDao : ISpeakerDao { public System.Collections.IList FindAll() { Console.WriteLine("Finding speakers..."); // just a demo...fake the retrieval. Thread.Sleep(10000); HelloWorldSpeaker speaker = new HelloWorldSpeaker(); speaker.Language = Language.Portuguese; IList list = new ArrayList(); list.Add(speaker); return list; } public IHelloWorldSpeaker Save(IHelloWorldSpeaker speaker) { Console.WriteLine("Saving speaker..."); // just a demo...not really saving... return speaker; } }The XML configuration specifies two Advisors, that is, the combination of advice (the behavior to add) and a pointcut (where the behavior should be applied). A
RegularExpressionMethodPointcutAdvisor
is used as a convenience to specify the pointcut as a regular expression that matches methods names. Other pointcuts of your own creation could be used, in which case a
DefaultPointcutAdvisor
would be used to define the Advisor. The object definitions for these advisors, advice, and SpeakerDao object are shown below
<object id="SpeachAdvisor" type="Spring.Aop.Support.RegularExpressionMethodPointcutAdvisor, Spring.Aop"> <property name="advice" ref="debugInterceptor"/> <property name="patterns"> <list> <value>.*Say.*</value> </list> </property> </object> <object id="AdoAdvisor" type="Spring.Aop.Support.RegularExpressionMethodPointcutAdvisor, Spring.Aop"> <property name="advice" ref="timingInterceptor"/> <property name="patterns"> <list> <value>.*Find.*</value> </list> </property> </object> // Advice <object id="debugInterceptor" type="AopPlay.DebugInterceptor, AopPlay"/> <object id="timingInterceptor" type="AopPlay.TimingInterceptor, AopPlay"/> // Speaker DAO Object - has 'FindAll' Method. <object id="speakerDao" type="AopPlay.SpeakerDao, AopPlay"/> // HelloWorldSpeaker objects as previously listed.
Adding an instance of
DefaultAdvisorAutoProxyCreator
to the configuration
file
<object id="ProxyCreator" type="Spring.Aop.Framework.AutoProxy.DefaultAdvisorAutoProxyCreator, Spring.Aop"/>
will apply the debug interceptor on all objects in the context that have a method that contains the text "Say" and apply the timing interceptor on objects in the context that have a method that contains the text "Find". Running the following code demonstrates this behavior. Note that the "Save" method of SpeakerDao does not have any advice applied to it.
IApplicationContext ctx = ContextRegistry.GetContext(); IDictionary speakerDictionary = ctx.GetObjectsOfType(typeof(IHelloWorldSpeaker)); foreach (DictionaryEntry entry in speakerDictionary) { string name = (string)entry.Key; IHelloWorldSpeaker worldSpeaker = (IHelloWorldSpeaker)entry.Value; Console.Write(name + " says; "); worldSpeaker.SayHello(); } ISpeakerDao dao = (ISpeakerDao)ctx.GetObject("speakerDao"); IList speakerList = dao.FindAll(); IHelloWorldSpeaker speaker = dao.Save(new HelloWorldSpeaker());
This produces the following output
ItalianSpeakerOne says; Before: Void SayHello() Ciao Mondo! After: Void SayHello() EnglishSpeakerTwo says; Before: Void SayHello() Hello World! After: Void SayHello() PortugeseSpeaker says; Before: Void SayHello() Oi Mundo! After: Void SayHello() EnglishSpeakerOne says; Before: Void SayHello() Hello World! After: Void SayHello() Finding speakers... Elapsed time = 00:00:10.0154745 Saving speaker...
The DefaultAdvisorAutoProxyCreator offers support for filtering
(using a naming convention so that only certain advisors are
evaluated, allowing use of multiple, differently configured,
AdvisorAutoProxyCreators in the same factory) and ordering. Advisors
can implement the Spring.Core.IOrdered
interface to
ensure correct ordering if this is an issue. The default is
unordered.
An AutoProxyCreator that identified objects to proxy by matching
a specified IPointcut
.
An AutoProxyCreator that identifies objects to proxy by matching
their Type.FullName
against a list of
patterns.
An AutoProxyCreator, that identifies objects to be proxied by checking any System.Attribute defined on a given type and that types interfaces.
The base class for AutoProxyCreator implementations that mark objects eligible for proxying based on arbitrary criteria.
This is the superclass of DefaultAdvisorAutoProxyCreator. You
can create your own autoproxy creators by subclassing this class, in
the unlikely event that advisor definitions offer insufficient
customization to the behavior of the framework
DefaultAdvisorAutoProxyCreator
.
A particularly important type of autoproxying is driven by attributes. The programming model is similar to using Enterprise Services with ServicedComponents.
In this case, you use the
DefaultAdvisorAutoProxyCreator
, in combination with
Advisors that understand attributes. The Advisor pointcut is identified
by the presence of .NET attribute in the source code and it is
configured via the data and/or methods of the attribute. This is a
powerful alternative to identifying the advisor pointcut and advice
configuration through traditional property configuration, either
programmatic or through XML based configuration.
Several of the aspect provided with Spring use attribute driven autoproxying. The most prominent example is Transaction support.
The AOP namespace allows you to define an advisor, i.e pointcut + 1 piece of advice, in a more declarative manner. Under the covers the DefaultAdvisorAutoProxyCreator is being used. Here is an example,
<objects xmlns="http://www.springframework.net" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.net/aop"> <aop:config> <aop:advisor id="getDescriptionAdvisor" pointcut-ref="getDescriptionCalls" advice-ref="getDescriptionCounter"/> </aop:config> <object id="getDescriptionCalls" type="Spring.Aop.Support.SdkRegularExpressionMethodPointcut, Spring.Aop"> <property name="patterns"> <list> <value>.*GetDescription.*</value> </list> </property> </object> <object id="getDescriptionCounter" type="Spring.Aop.Framework.CountingBeforeAdvice, Spring.Aop.Tests"/> <object name="testObject" type="Spring.Objects.TestObject, Spring.Core.Tests"/> </objects>
In this example, the TestObject, which implements the interface ITestObject, is having AOP advice applied to it. The method GetDescription() is specified as a regular expression pointcut. The aop:config tag and subsequent child tag, aop:advisor, brings together the pointcut with the advice.
In order to have Spring.NET recognise the aop namespace, you need to declare the namespace parser in the main Spring.NET configuration section. For convenience this is shown below. Please refer to the section titled context configuration for more extensive information..
<configuration> <configSections> <sectionGroup name="spring"> <section name="context" type="Spring.Context.Support.ContextHandler, Spring.Core"/> <section name="objects" type="Spring.Context.Support.DefaultSectionHandler, Spring.Core" /> <section name="parsers" type="Spring.Context.Support.NamespaceParsersSectionHandler, Spring.Core"/> </sectionGroup> </configSections> <spring> <parsers> <parser type="Spring.Aop.Config.AopNamespaceParser, Spring.Aop" /> </parsers> <context> <resource uri="config://spring/objects"/> </context> <objects xmlns="http://www.springframework.net"> ... </objects> </spring> </configuration>
Spring.NET offers the concept of a
TargetSource, expressed in the
Spring.Aop.ITargetSource
interface. This interface is
responsible for returning the "target object" implementing the joinpoint.
The TargetSource
implementation is asked for a target
instance each time the AOP proxy handles a method invocation.
Developers using Spring.NET AOP don't normally need to work directly with TargetSources, but this provides a powerful means of supporting pooling, hot swappable and other sophisticated targets. For example, a pooling TargetSource can return a different target instance for each invocation, using a pool to manage instances.
If you do not specify a TargetSource, a default implementation is used that wraps a local object. The same target is returned for each invocation (as you would expect).
Let's look at the standard target sources provided with Spring.NET, and how you can use them.
When using a custom target source, your target will usually need to be a prototype rather than a singleton object definition. This allows Spring.NET to create a new target instance when required.
The
org.Spring.NETframework.aop.target.HotSwappableTargetSource
exists to allow the target of an AOP proxy to be switched while allowing
callers to keep their references to it.
Changing the target source's target takes effect immediately. The
HotSwappableTargetSource
is thread safe.
You can change the target via the swap()
method
on HotSwappableTargetSource as follows:
HotSwappableTargetSource swapper = (HotSwappableTargetSource) objectFactory.GetObject("swapper"); object oldTarget = swapper.swap(newTarget);
The XML definitions required look as follows:
<object id="initialTarget" type="MyCompany.OldTarget, MyCompany"> </object> <object id="swapper" type="Spring.Aop.Target.HotSwappableTargetSource, Spring.Aop"> <constructor-arg><ref local="initialTarget"/></constructor-arg> </object> <object id="swappable" type="Spring.Aop.Framework.ProxyFactoryObject, Spring.Aop" > <property name="targetSource"> <ref local="swapper"/> </property> </object>
The above swap()
call changes the target of the
swappable object. Clients who hold a reference to that object will be
unaware of the change, but will immediately start hitting the new
target.
Although this example doesn't add any advice--and it's not
necessary to add advice to use a TargetSource
--of
course any TargetSource
can be used in conjunction
with arbitrary advice.
Using a pooling target source provides a programming model in which a pool of identical instances is maintained, with method invocations going to free objects in the pool.
A crucial difference between Spring.NET pooling and pooling in .NET Enterprise Services pooling is that Spring.NET pooling can be applied to any PONO. (Plain old .NET object). As with Spring.NET in general, this service can be applied in a non-invasive way.
Spring.NET provides out-of-the-box support using a pooling
implementation based on Jakarta Commons Pool 1.1, which provides a
fairly efficient pooling implementation. It's also possible to subclass
Spring.Aop.Target.AbstractPoolingTargetSource
to
support any other pooling API.
Sample configuration is shown below:
<object id="businessObjectTarget" type="MyCompany.MyBusinessObject, MyCompany" singleton="false"> ... properties omitted </object> <object id="poolTargetSource" type="Spring.Aop.Target.SimplePoolTargetSource, Spring.Aop"> <property name="targetObjectName" value="businessObjectTarget"/> <property name="maxSize" value="25"/> </object> <object id="businessObject" type="Spring.Aop.Framework.ProxyFactoryObject, Spring.Aop"> <property name="targetSource" ref="poolTargetSource"/> <property name="interceptorNames" value="myInterceptor"/> </object>
Note that the target object--"businessObjectTarget" in the
example--must be a prototype. This allows the
PoolingTargetSource
implementation to create new
instances of the target to grow the pool as necessary. See the SDK
documentation for AbstractPoolingTargetSource
and the
concrete subclass you wish to use for information about it's properties:
maxSize is the most basic, and always guaranteed to be present.
In this case, "myInterceptor" is the name of an interceptor that would need to be defined in the same IoC context. However, it isn't necessary to specify interceptors to use pooling. If you want only pooling, and no other advice, don't set the interceptorNames property at all.
It's possible to configure Spring.NET so as to be able to cast any
pooled object to the Spring.Aop.Target.PoolingConfig
interface, which exposes information about the configuration and current
size of the pool through an introduction. You'll need to define an
advisor like this:
<object id="poolConfigAdvisor" type="Spring.Object.Factory.Config.MethodInvokingFactoryObject, Spring.Aop"> <property name="target" ref="poolTargetSource" /> <property name="targetMethod" value="getPoolingConfigMixin" /> </object>
This advisor is obtained by calling a convenience method on the
AbstractPoolingTargetSource
class, hence the use of
MethodInvokingFactoryObject
. This advisor's name
('poolConfigAdvisor'
here) must be in the list of
interceptor names in the ProxyFactoryObject
exposing
the pooled object.
The cast will look as follows:
PoolingConfig conf = (PoolingConfig) objectFactory.GetObject("businessObject"); Console.WriteLine("Max pool size is " + conf.getMaxSize());
Pooling stateless service objects is not usually necessary. We don't believe it should be the default choice, as most stateless objects are naturally threadsafe, and instance pooling is problematic if resources are cached.
Simpler pooling is available using autoproxying. It's possible to set the TargetSources used by any autoproxy creator.
Setting up a "prototype" target source is similar to a pooling TargetSource. In this case, a new instance of the target will be created on every method invocation. Although the cost of creating a new object may not be high, the cost of wiring up the new object (satisfying its IoC dependencies) may be more expensive. Thus you shouldn't use this approach without very good reason.
To do this, you could modify the
poolTargetSource
definition shown above as follows.
(the name of the definition has also been changed, for clarity.)
<object id="prototypeTargetSource" type="Spring.Aop.Target.PrototypeTargetSource, Spring.Aop"> <property name="targetObjectName" value="businessObject" /> </object>
There is only one property: the name of the target object. Inheritance is used in the TargetSource implementations to ensure consistent naming. As with the pooling target source, the target object must be a prototype object definition, the singleton property of the target should be set to false.
ThreadLocal target sources are useful if you need an object to be created for each incoming request (per thread that is). The concept of a ThreadLocal provides a facility to transparently store resource alongside a thread. Setting up a ThreadLocalTargetSource is pretty much the same as was explained for the other types of target source:
<object id="threadlocalTargetSource" type="Spring.Aop.Target.ThreadLocalTargetSource, Spring.Aop"> <property name="targetObjectName" value="businessObject" /> </object>
Spring.NET AOP is designed to be extensible. While the interception implementation strategy is presently used internally, it is possible to support arbitrary advice types in addition to interception around, before, throws, and after returning advice, which are supported out of the box.
The Spring.Aop.Framework.Adapter
package is an
SPI (Service Provider Interface) package allowing support for new custom
advice types to be added without changing the core framework. The only
constraint on a custom Advice type is that it must implement the
AopAlliance.Aop.IAdvice
tag interface.
Please refer to the Spring.Aop.Framework.Adapter
namespace documentation for further information.
The Spring.NET team recommends the excellent AspectJ in Action by Ramnivas Laddad (Manning, 2003) for an introduction to AOP.
If you are interested in more advanced capabilities of Spring.NET AOP, take a look at the test suite as it illustrates advanced features not discussed in this document.