diff --git a/doc/reference/src/aop-aspect-library.xml b/doc/reference/src/aop-aspect-library.xml index 8a277c32..674056ca 100644 --- a/doc/reference/src/aop-aspect-library.xml +++ b/doc/reference/src/aop-aspect-library.xml @@ -1,733 +1,755 @@ - - - Aspect Library - - - 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. - - - - 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="CachePriority.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. - - - - 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. - - - - 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. - - - 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 - - - - 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. - - - - - 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. - - - - 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> - - - 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] - - - - - Transactions - - The transaction aspect is more fully described in the section on - transaction management. - - - - 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 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 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. - + + + Aspect Library + + + 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. + + + + 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="CachePriority.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. + + + + 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. + + + + 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 ExceptionHandlerAdvice to use an instance of + IExceptionHandler, for example to access more sophisticated logging + functionality than is available with the current log action in the DSL, + 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. + + + 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 + + + + 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. + + + + + 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. + + + + 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> + + + 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] + + + + + Transactions + + The transaction aspect is more fully described in the section on + transaction management. + + + + 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 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 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. + \ No newline at end of file diff --git a/src/Spring/Spring.Aop/Aspects/Exceptions/ExceptionHandlerAdvice.cs b/src/Spring/Spring.Aop/Aspects/Exceptions/ExceptionHandlerAdvice.cs index 0f0db9d1..f089ebb3 100644 --- a/src/Spring/Spring.Aop/Aspects/Exceptions/ExceptionHandlerAdvice.cs +++ b/src/Spring/Spring.Aop/Aspects/Exceptions/ExceptionHandlerAdvice.cs @@ -79,7 +79,7 @@ namespace Spring.Aspects.Exceptions private string onExceptionNameRegex = @"^(on\s+exception\s+name)\s+(.*?)\s+(log|translate|wrap|replace|return|swallow)\s*(.*?)$"; private string onExceptionRegex = @"^(on\s+exception\s+)(\(.*?\))\s+(log|translate|wrap|replace|return|swallow)\s*(.*?)$"; - + #endregion #region Properties @@ -241,6 +241,11 @@ namespace Spring.Aspects.Exceptions } newExceptionHandlers.Add(handler); } + IExceptionHandler handlerObject = o as IExceptionHandler; + if (handlerObject != null) + { + newExceptionHandlers.Add(handlerObject); + } } //TODO sync. diff --git a/test/Spring/Spring.Aop.Tests/Aspects/Exception/ExceptionHandlerAspectIntegrationTests.cs b/test/Spring/Spring.Aop.Tests/Aspects/Exception/ExceptionHandlerAspectIntegrationTests.cs index 66120da3..ba6fbed6 100644 --- a/test/Spring/Spring.Aop.Tests/Aspects/Exception/ExceptionHandlerAspectIntegrationTests.cs +++ b/test/Spring/Spring.Aop.Tests/Aspects/Exception/ExceptionHandlerAspectIntegrationTests.cs @@ -54,8 +54,6 @@ namespace Spring.Aspects.Exceptions [Test] public void LoggingTest() { - - LogExceptionHandler logHandler = new LogExceptionHandler(); string testText = @"#log.Debug('Hello World, exception message = ' + #e.Message + ', target method = ' + #method.Name)"; logHandler.SourceExceptionNames.Add("ArithmeticException"); @@ -86,7 +84,6 @@ namespace Spring.Aspects.Exceptions ExecuteLoggingHandler(logHandlerText); } - [Test] public void LoggingTestWithConstraintExpression() { @@ -95,6 +92,17 @@ namespace Spring.Aspects.Exceptions ExecuteLoggingHandler(logHandlerText); } + [Test] + public void LoggingTestWithConstraintExpressionWithExceptionHandler() + { + LogExceptionHandler exHandler = new LogExceptionHandler(); + exHandler.ConstraintExpressionText = "#e is T(System.ArithmeticException)"; + exHandler.LogName = "Cms.Session.ExceptionHandler"; + exHandler.ActionExpressionText = "#log.Fatal('Request Timeout occured', #e)"; + + ExecuteLoggingHandler(exHandler); + } + [Test] [ExpectedException(typeof(ArgumentException))] public void LoggingTestWithBadString() @@ -142,6 +150,20 @@ namespace Spring.Aspects.Exceptions } } + private void ExecuteLoggingHandler(IExceptionHandler handler) + { + ITestObject to = CreateTestObjectProxy(handler); + + try + { + to.Exceptional(new ArithmeticException()); + } + catch (ArithmeticException) + { + //TODO assert logging occured. + } + } + [Test] @@ -151,6 +173,25 @@ namespace Spring.Aspects.Exceptions "on exception name ArithmeticException translate new System.InvalidOperationException('My Message, Method Name ' + #method.Name, #e)"; ITestObject to = CreateTestObjectProxy(translationHandlerText); + AssertTranslation(to); + } + + + [Test] + public void TranslateWithExceptionHandlerInstance() + { + TranslationExceptionHandler exHandler = new TranslationExceptionHandler(); + IList exceptionNames = new ArrayList(); + exceptionNames.Add("ArithmeticException"); + exHandler.SourceExceptionNames = exceptionNames; + exHandler.ActionExpressionText = + "new System.InvalidOperationException('My Message, Method Name ' + #method.Name, #e)"; + ITestObject to = CreateTestObjectProxy(exHandler); + AssertTranslation(to); + } + + private static void AssertTranslation(ITestObject to) + { try { to.Exceptional(new ArithmeticException("Bad Math")); @@ -160,7 +201,8 @@ namespace Spring.Aspects.Exceptions { Assert.IsInstanceOfType(typeof(ArithmeticException), e.InnerException, "Inner exception."); Assert.AreEqual("My Message, Method Name Exceptional", e.Message); - } catch (Exception e) + } + catch (Exception e) { Assert.IsInstanceOfType(typeof(InvalidOperationException), e, "wrong exception type thrown."); } @@ -339,8 +381,18 @@ namespace Spring.Aspects.Exceptions private ITestObject CreateTestObjectProxy(string logHandlerText) { exceptionHandlerAdvice.ExceptionHandlers.Add(logHandlerText); - exceptionHandlerAdvice.AfterPropertiesSet(); + return CreateProxy(); + } + private ITestObject CreateTestObjectProxy(IExceptionHandler exceptionHander) + { + exceptionHandlerAdvice.ExceptionHandlers.Add(exceptionHander); + return CreateProxy(); + } + + private ITestObject CreateProxy() + { + exceptionHandlerAdvice.AfterPropertiesSet(); ProxyFactory pf = new ProxyFactory(new TestObject()); pf.AddAdvice(exceptionHandlerAdvice); return (ITestObject)pf.GetProxy();