Spring provides several aspects in the distribution. The most popular of which is transactional advice, located in the Spring.Data module. However, the aspects that are documented in this section are those contained within the Spring.Aop module itself. The aspects in within Spring.Aop.dll are Caching, Exception Handling, Logging, Retry, and Parameter Validation. Other traditional advice types such as validation, security, and thread management, will be included in a future release.
Caching the return value of a method or the value of a method parameter is a common approach to increase application performance. Application performance is increased with effective use of caching since layers in the application that are closer to the user can return information within their own layer as compared to making more expensive calls to retrieve that information from a lower, and more slow, layer such as a database or a web service. Caching also can help in terms of application scalability, which is generally the more important concern.
The caching support in Spring.NET consists of base cache interfaces that can be used to specify a specific storage implementation of the cache and also an aspect that determines where to apply the caching functionality and its configuration.
The base cache interface that any cache implementation should
implement is Spring.Caching.ICache
located in
Spring.Core
. Two implementations are provided,
Spring.Caching.AspNetCache
located in
Spring.Web
which stores cache entries within an ASP.NET
cache and a simple implementation,
Spring.Caching.NonExpiringCache
that stores cache
entries in memory and never expires these entries. Custom implementations
based on 3rd party implementations, such as Oracle Coherence, or
memcached, can be used by implementing the ICache
interface.
The cache aspect is
Spring.Aspects.Cache.CacheAspect
located in
Spring.Aop
. It consists of three pieces of
functionality, the ability to cache return values, method parameters, and
explicit eviction of an item from the cache. The aspect currently relies
on using attributes to specify the pointcut as well as the behavior, much
like the transactional aspect. Future versions will allow for external
configuration of the behavior so you can apply caching to a code base
without needing to use attributes in the code.
The following attributes are available
CacheResult
- used to cache the return
value
CacheResultItems
- used when returning a
collection as a return value
CacheParameter
- used to cache a method
parameter
InvalidateCache
- used to indicate one or
more cache items should be invalidated.
Each CacheResult
,
CacheResultItems
, and CacheParameter
attributes define the following properties.
CacheName
- the name of the cache
implementation to use
Key
- a string representing a Spring
Expression Language (SpEL) expression used as the key in the
cache.
Condition
- a SpEL expression that should be
evaluated in order to determine whether the item should be
cached.
TimeToLive
- The amount of time an object
should remain in the cache (in seconds).
The InvalidateCache
attribute has properties for
the CacheName, the Key as well as the Condition, with the same meanings as
listed previously.
Each ICache
implementation will have properties
that are specific to a caching technology. In the case of
AspNetCache
, the two important properties to configure
are:
SlidingExperation
- If this property value is
set to true, every time the marked object is accessed it's TimeToLive
value is reset to its original value
Priority
- the cache item priority
controlling how likely an object is to be removed from an associated
cache when the cache is being purged.
TimeToLive
- The amount of time an object
should remain in the cache (in seconds).
The values of the Priority enumeration are
Low
- low likelihood of deletion when cache
is purged.
Normal
- default priority for deletion when
cache is purged.
High
- high likelihood of deletion when cache
is purged.
NotRemovable
- cache item not deleted when
cache is purged.
An important element of the applying these attributes is the use of the expression language that allows for calling context information to drive the caching actions. Here is an example taken from the Spring Air sample application of the AirportDao implementation that implements an interface with the method GetAirport(long id).
[CacheResult("AspNetCache", "'Airport.Id=' + #id", TimeToLive = "0:1:0")] public Airport GetAirport(long id) { // implementation not shown... }
The first parameter is the cache name. The second string parameter is the cache key and is a string expression that incorporates the argument passed into the method, the id. The cache key value cannot be null or an empty string (""). The method parameter names are exposed as variables to the key expression. The expression may also call out to other objects in the Spring container allowing for a more complex key algorithm to be encapsulated. The end result is that the Airport object is cached by id for 60 seconds in a cache named AspNetCache. The TimetoLive property could also have been specified on the configuration of the AspNetCache object.
The configuration to enable the caching aspect is shown below
<object" id="CacheAspect" type="Spring.Aspects.Cache.CacheAspect, Spring.Aop"/> <object id="AspNetCache" type="Spring.Caching.AspNetCache, Spring.Web"> <property name="SlidingExpiration" value="true"/> <property name="Priority" value="Low"/> <property name="TimeToLive" value="00:02:00"/> </object> <!-- Apply aspects to DAOs --> <object type="Spring.Aop.Framework.AutoProxy.ObjectNameAutoProxyCreator, Spring.Aop"> <property name="ObjectNames"> <list> <value>*Dao</value> </list> </property> <property name="InterceptorNames"> <list> <value>CacheAspect</value> </list> </property> </object>
in this example an ObjectNameAutoProxyCreator
was
used to apply the cache aspect to objects that have Dao in their name. The
AspNetCache setting for TimeToLive will override the TimeToLive value set
at the method level via the attribute.
In some cases existing code can be easily adopted to a simple error handling strategy that can perform one of the following actions
translations - either wrap the thrown exception inside a new one or replace it with a new exception type (no inner exception is set).
return value - the exception is ignored and a return value for the method is provided instead
swallow - the exception is ignored.
execute - Execute an abritrary Spring Expression Language (SpEL expression)
The applicability of general exception handling advice depends greatly on how tangled the code is regarding access to local variables that may form part of the exception. Once you get familiar with the feature set of Spring declarative exception handling advice you should evaluate where it may be effectively applied in your code base. It is worth noting that you can still chain together multiple pieces of exception handling advice allowing you to mix the declarative approach shown in this section with the traditional inheritance based approach, i.e. implementing IThrowsAdvice or IMethodInterceptor.
Declarative exception handling is expressed in the form of a mini-language relevant to the domain at hand, exception handling. This could be referred to as a Domain Specific Language (DSL). Here is a simple example, which should hopefully be self explanatory.
<object name="exceptionHandlingAdvice" type="Spring.Aspects.Exceptions.ExceptionHandlerAdvice, Spring.Aop"> <property name="exceptionHandlers"> <list> <value>on exception name ArithmeticException wrap System.InvalidOperationException</value> </list> </property> </object>
What this is instructing the advice to do is the following bit of code when an ArithmeticException is thrown, throw new System.InvalidOperationException("Wrapped ArithmeticException", e), where e is the original ArithmeticException. The default message, "Wrapped ArithmethicException" is automatically appended. You may however specify the message used in the newly thrown exception as shown below
on exception name ArithmeticException wrap System.InvalidOperationException 'My Message'
Similarly, if you would rather replace the exception, that is do not nest one inside the other, you can use the following syntax
on exception name ArithmeticException replace System.InvalidOperationException or on exception name ArithmeticException replace System.InvalidOperationException 'My Message'
Both wrap and replace are special cases of the more general translate action. An example of a translate expression is shown below
on exception name ArithmeticException translate new System.InvalidOperationException('My Message, Method Name ' + #method.Name, #e)
What we see here after the translate keyword is text that will be passed into Spring's expression language (SpEL) for evaluation. Refer to the chapter on the expression language for more details. One important feature of the expression evaluation is the availability of variables relating to the calling context when the exception was thrown. These are
method - the MethodInfo object corresponding to the method that threw the exception
args - the argument array to the method that threw the exception, signature is object[]
target - the AOP target object instance.
e - the thrown exception
You can invoke methods on these variables, prefixed by a '#' in the expression. This gives you the flexibility to call special purpose constructors that can have any piece of information accessible via the above variables, or even other external data through the use of SpEL's ability to reference objects within the Spring container.
You may also choose to 'swallow' the exception or to return a specific return value, for example
on exception name ArithmeticException swallow or on exception name ArithmeticException return 12
You may also simply log the exception
on exception name ArithmeticException,ArgumentException log 'My Message, Method Name ' + #method.Name
Here we see that a comma delimited list of exception names can be specified.
The logging is performed using the Commons.Logging library that provides an abstraction over the underlying logging implementation. Logging is currently at the debug level with a logger name of "LogExceptionHandler" The ability to specify these values will be a future enhancement and likely via a syntax resembling a constructor for the action, i.e. log(Debug,"LoggerName").
Multiple exception handling statements can be specified within the list shown above. The processing flow is on exception, the name of the exception listed in the statement is compared to the thrown exception to see if there is a match. A comma separated list of exceptions can be used to group together the same action taken for different exception names. If the action to take is logging, then the logging action is performed and the search for other matching exception names continues. For all other actions, namely translate, wrap, replace, swallow, return, once an exception handler is matched, those in the chain are no longer evaluated. Note, do not confuse this handler chain with the general advice AOP advice chain. For translate, wrap, and replace actions a SpEL expression is created and used to instantiate a new exception (in addition to any other processing that may occur when evaluating the expression) which is then thrown.
The exception handling DSL also supports the ability to provide a SpEL boolean expression to determine if the advice will apply instead of just filtering by the expression name. For example, the following is the equivalent to the first example based on exception names but compares the specific type of the exception thrown
on exception (#e is T(System.ArithmeticException)) wrap System.InvalidOperationException
The syntax use is 'on exception (SpEL boolean expression)' and
inside the expression you have access to the variables of the calling
context listed before, i.e. method, args, target, and e. This can be
useful to implement a small amount of conditional logic, such as checking
for a specific error number in an exception, i.e. (#e is
T(System.Data.SqlException) && #e.Errors[0].Number in
{156,170,207,208})
, to catch and translate bad grammar codes in
a SqlException.
While the examples given above are toy examples, they could just as easily be changed to convert your application specific exceptions. If you find yourself pushing the limits of using SpEL expressions, you will likely be better off creating your own custom aspect class instead of a scripting approach.
You can also configure the each of the Handlers individually based on the action keyword. For example, to configure the logging properties on the LogExceptionHandler.
<object name="logExceptionHandler" type="Spring.Aspects.Exceptions.LogExceptionHandler, Spring.Aop"> <property name="LogName" value="Cms.Session.ExceptionHandler" /> <property name="LogLevel" value="Debug"/> <property name="LogMessageOnly" value="true"/> </object> <object name="exceptionHandlingAdvice" type="Spring.Aspects.Exceptions.ExceptionHandlerAdvice, Spring.Aop"> <property name="ExceptionHandlerDictionary"> <dictionary> <entry key="log" ref="logExceptionHandler"/> </dictionary> </property> <property name="ExceptionHandlers"> <list> <value>on exception name ArithmeticException,ArgumentException log 'My Message, Method Name ' + #method.Name</value> </list> </property> </object>
You can also configure ExceptionHandlerAdvice
to
use an instance of IExceptionHandler
by specifing it as
an entry in the ExceptionHandlers list. This gives you complete control
over all properties of the handler but you must set
ConstraintExpressionText and ActionExpressionText which are normally
parsed for you from the string. To use the case of configuring the
LogExceptionHandler, this approach also lets you specify advanced logging
functionality, but at a cost of some additional complexity. For example
setting the logging level and pass the exception into the logging
subsystem
<object name="exceptionHandlingAdvice" type="Spring.Aspects.Exceptions.ExceptionHandlerAdvice, Spring.Aop"> <property name="exceptionHandlers"> <list> <object type="Spring.Aspects.Exceptions.LogExceptionHandler"> <property name="LogName" value="Cms.Session.ExceptionHandler" /> <property name="ConstraintExpressionText" value="#e is T(System.Threading.ThreadAbortException)" /> <property name="ActionExpressionText" value="#log.Fatal('Request Timeout occured', #e)" /> </object> </list> </property> </object>
The configuration of the logger name, level, and weather or not to pass the thrown exception as the second argument to the log method will be supported in the DSL style in a future release.
The general syntax of the language is
on exception name [ExceptionName1,ExceptionName2,...]
[action] [SpEL expression]
or
on exception (SpEL boolean expression) [action] [SpEL
expression]
The exception names are required as well as the action. The valid actions are
log
translate
wrap
replace
return
swallow
execute
The form of the expression depends on the action. For logging, the entire string is taken as the SpEL expression to log. Translate expects an exception to be returned from evaluation the SpEL expression. Wrap and replace are shorthand for the translate action. For wrap and replace you specify the exception name and the message to pass into the standard exception constructors (string, exception) and (string). The exception name can be a partial or fully qualified name. Spring will attempt to resolve the typename across all referenced assemblies. You may also register type aliases for use with SpEL in the standard manner with Spring.NET and those will be accessible from within the exception handling expression.
The logging advice lets you log the information on method entry, exit and thrown exception (if any). The implementation is based on the logging library, Common.Logging, that provides portability across different logging libraries. There are a number of configuration options available, listed below
LogUniqueIdentifier
LogExecutionTime
LogMethodArguments
LogReturnValue
Separator
LogLevel
You declare the logging advice in IoC container with the following
XML fragment. Alternatively, you can use the class
SimpleLoggingAdvice
programatically.
<object name="loggingAdvice" type="Spring.Aspects.Logging.SimpleLoggingAdvice, Spring.Aop"> <property name="LogUniqueIdentifier" value="true"/> <property name="LogExecutionTime" value="true"/> <property name="LogMethodArguments" value="true"/> <property name="LogReturnValue" value="true"/> <property name="Separator" value=";"/> <property name="LogLevel" value="Info"/> <property name="HideProxyTypeNames" value="true"/> <property name="UseDynamicLogger" value="true"/> </object>
The default values for LogUniqueIdentifier
,
LogExecutionTime
, LogMethodArguments
and LogReturnValue
are false. The default separator
value is ", " and the default log level is Common.Logging's
LogLevel.Trace
.
You can set the name of the logger with the property
LoggerName, for example "DataAccessLayer" for a
logging advice that would be applied across the all the classes in the
data access layer. That works well when using a 'category' style of
logging. If you do not set the LoggerName property,
then the type name of the logging advice is used as the logging name.
Another approach to logging is to log based on the type of the object
being called, the target type. Since often this is a proxy class with a
relatively meaningless name, the property
HideProxyTypeNames can be set to true to show the
true target type and not the proxy type. The
UseDynamicLogger
property determines which
ILog
instance should be used to write log messages for
a particular method invocation: a dynamic one for the Type getting called,
or a static one for the Type of the trace interceptor. The default is to
use a static logger.
To further extend the functionality of the
SimpleLoggingAdvice
you can subclass
SimpleLoggingAdvice
and override the methods
string GetEntryMessage(IMethodInvocation invocation,
string idString)
string GetExceptionMessage(IMethodInvocation
invocation, Exception e, TimeSpan executionTimeSpan, string
idString)
string GetExitMessage(IMethodInvocation invocation,
object returnValue, TimeSpan executionTimeSpan, string
idString)
The default implementation to calculate a unique identifier is to
use a GUID. You can alter this behavior by overriding the method
string CreateUniqueIdentifier()
. The
SimpleLoggingAdvice
class inherits from
AbstractLoggingAdvice
, which has the abstract method
object InvokeUnderLog(IMethodInvocation invocation, ILog
log)
and you can also override the method ILog
GetLoggerForInvocation(IMethodInvocation invocation)
to
customize the logger instance used for logging. Refer to the SDK
documentation for more details on subclassing
AbstractLoggingAdvice
.
As an example of the Logging advice's output, adding the advice to the method
public int Bark(string message, int[] luckyNumbers) { return 4; }
And calling Bark("hello", new int[]{1, 2, 3} ), results in the following output
Entering Bark, 5d2bad47-62cd-435b-8de7-91f12b7f433e, message=hello; luckyNumbers=System.Int32[] Exiting Bark, 5d2bad47-62cd-435b-8de7-91f12b7f433e, 30453.125 ms, return=4
The method parameters values are obtained using the ToString() method. If you would like to have an alternate implementation, say to view some values in an array, override the method string GetMethodArgumentAsString(IMethodInvocation invocation).
When making a distributed call it is often a common requirement to be able to retry the method invocation if there was an exception. Typically the exception will be due to a communication issue that is intermittent and retrying over a period of time will likely result in a successful invocation. When applying retry advice it is important to know if making two calls to the remote service will cause side effects. Generally speaking, the method being invoked should be idempotent, that is, it is safe to call multiple times.
The retry advice is specified using a little language, i.e a DSL. A simple example is shown below
on exception name ArithmeticException retry 3x delay 1s
The meaning is: when an exception that has 'ArithmeticException' in its type name is thrown, retry the invocation up to 3 times and delay for 1 second between each retry event.
You can also provide a SpEL (Spring Expression Language) expression that calculates the time interval to sleep between each retry event. The syntax for this is shown below
on exception name ArithmeticException retry 3x rate (1*#n + 0.5)
As with the exception handling advice, you may also specify a boolean SpEL that must evaluate to true in order for the advice to apply. For example
on exception (#e is T(System.ArithmeticException)) retry 3x delay 1s on exception (#e is T(System.ArithmeticException)) retry 3x rate (1*#n + 0.5)
The time specified after the delay keyword is converted to a TimeSpan object using Spring's TimeSpanConverter. This supports setting the time as an integer + time unit. Time units are (d, h, m, s, ms) representing (days, hours, minutes, seconds, and milliseconds). For example; 1d = 1day, 5h = 5 hours etc. You can not specify a string such as '1d 5h'. The value that is calculated from the expression after the rate keyword is interpreted as a number of seconds. The power of using SpEL for the rate expression is that you can easily specify some exponential retry rate (a bigger delay for each retry attempt) or call out to a custom function developed for this purpose.
When using a SpEL expression for the filter condition or for the rate expression, the following variable are available
method - the MethodInfo object corresponding to the method that threw the exception
args - the argument array to the method that threw the exception, signature is object[]
target - the AOP target object instance.
e - the thrown exception
You declare the advice in IoC container with the
following XML fragment. Alternatively, you can use the
RetryAdvice
class programatically.
<object name="exceptionHandlingAdvice" type="Spring.Aspects.RetryAdvice, Spring.Aop"> <property name="retryExpression" value="on exception name ArithmeticException retry 3x delay 1s"/> </object>
The general syntax of the language is
on exception name [ExceptionName1,ExceptionName2,...]
retry [number of times]x [delay|rate] [delay time|SpEL rate
expression]
or
on exception (SpEL boolean expression) retry [number of
times]x [delay|rate] [delay time|SpELrate expression]
The transaction aspect is more fully described in the section on transaction management.
Spring provides a UI-agnostic validation framework in which you can declare validation rules, both progammatically and declaratively, and have those rules evaluated against an arbitrary .NET object. Spring provides additional support for the rendering of validation errors within Spring's ASP.NET framework. (See the section on ASP.NET usage tips for more information.) However, validation is not confined to the UI tier. It is a common task that occurs across most, if not all, applications layers. Validation that is performed in the UI layer is often repeated in the service layer, in order to be proactive in case non UI-based clients invoke the service layer. Validation rules completely different from those used in the UI layer may also be used on the server side.
To address some of the common needs for validation on the server
side, Spring provides parameter validation advice so that applies Spring's
validation rules to the method parameters. The class
ParameterValidationAdvice
is used in conjunction with
the Validated
attribute to specify which validation
rules are applied to method parameters. For example, to apply parameter
validation to the method SuggestFlights in the BookingAgent class used in
the SpringAir sample application, you
would apply the Validated
attribute to the method
parameters as shown below.
public FlightSuggestions SuggestFlights( [Validated("tripValidator")] Trip trip) { // unmodified implementation goes here }
The Validated
attribute takes a string name that
specifies the name of the validation rule, i.e. the name of the IValidator
object in the Spring application context. The Validated
attribute is located in the namespace Spring.Validation
of the Spring.Core
assembly.
The configuration of the advice is to simply define the an instance
of the ParameterValidationAdvice
class and apply the
advice, for example based on object names using an
ObjectNameAutoProxyCreator
, as shown below,
<object id="validationAdvice" type="Spring.Aspects.Validation.ParameterValidationAdvice, Spring.Aop"/> <object type="Spring.Aop.Framework.AutoProxy.ObjectNameAutoProxyCreator, Spring.Aop"> <property name="ObjectNames"> <list> <value>bookingAgent</value> </list> </property> <property name="InterceptorNames"> <list> <value>validationAdvice</value> </list> </property> </object>
When the advised method is invoked first the validation of each
method parameter is performed. If all validation succeeds, then the method
body is executed. If validation fails an exception of the type
ValidationException
is thrown and you can retrieve
errors information from its property ValidationErrors
.
See the SDK documentation for details.