Chapter 14. Aspect Library

The Spring.NET Framework

Chapter 14. Aspect Library

14.1. Introduction

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.

14.2. Caching

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 method parameter names are exposed as variables to the key expression. If you do not specify a key, then all the parameter values will be used to cache the returned value. 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.

14.3. Exception Handling

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.

14.3.1. Language Reference

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.

14.4. Logging

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.

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).

The Spring 1.2 release will have an additional logging advice implementation that leverages the Spring Expression Language to further customize the content of the logging messages via simple configuration using similar syntax to the retry and exception handling advice.

14.5. Retry

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>

14.5.1. Language Reference

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]

14.6. Transactions

The transaction aspect is more fully described in the section on transaction management.

14.7. Parameter Validation

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.