fixed SPRNET-1259

This commit is contained in:
eeichinger
2009-10-14 18:09:22 +00:00
parent f00f3afcc3
commit 0e716ecfa0
7 changed files with 335 additions and 243 deletions

View File

@@ -22,7 +22,7 @@
using System;
using System.Reflection;
using Common.Logging;
using Spring.Caching;
using Spring.Context;
using Spring.Expressions;
@@ -37,10 +37,23 @@ namespace Spring.Aspects.Cache
/// access to common functionality, such as obtaining a cache instance.
/// </summary>
/// <author>Aleksandar Seovic</author>
public class BaseCacheAdvice : IApplicationContextAware
public abstract class BaseCacheAdvice : IApplicationContextAware
{
/// <summary>
/// Shared logger instance
/// </summary>
protected readonly ILog logger;
private IApplicationContext applicationContext;
/// <summary>
/// Create a new default instance.
/// </summary>
protected BaseCacheAdvice()
{
logger = LogManager.GetLogger(this.GetType());
}
/// <summary>
/// Sets the <see cref="Spring.Context.IApplicationContext"/> that this
/// object runs in.
@@ -138,8 +151,8 @@ namespace Spring.Aspects.Cache
/// <summary>
/// Retrieves custom attribute for the specified attribute type.
/// </summary>
/// <param name="method">
/// Method to get attribute from.
/// <param name="attributeProvider">
/// Method/Parameter to get attribute from.
/// </param>
/// <param name="attributeType">
/// Attribute type.
@@ -147,14 +160,32 @@ namespace Spring.Aspects.Cache
/// <returns>
/// Attribute instance if one is found, <c>null</c> otherwise.
/// </returns>
protected static object GetCustomAttribute(MethodInfo method, Type attributeType)
protected object GetCustomAttribute(ICustomAttributeProvider attributeProvider, Type attributeType)
{
object[] attributes = method.GetCustomAttributes(attributeType, false);
object[] attributes = attributeProvider.GetCustomAttributes(attributeType, false);
if (attributes.Length > 0)
{
return attributes[0];
}
return null;
}
/// <summary>
/// Retrieves custom attribute for the specified attribute type.
/// </summary>
/// <param name="attributeProvider">
/// Method/Parameter to get attribute from.
/// </param>
/// <param name="attributeType">
/// Attribute type.
/// </param>
/// <returns>
/// Attribute instance if one is found, <c>null</c> otherwise.
/// </returns>
protected object[] GetCustomAttributes(ICustomAttributeProvider attributeProvider, Type attributeType)
{
object[] attributes = attributeProvider.GetCustomAttributes(attributeType, false);
return attributes;
}
}
}

View File

@@ -20,6 +20,8 @@
#region Imports
using System;
using System.Collections;
using System.Reflection;
using Common.Logging;
using Spring.Aop;
@@ -52,8 +54,19 @@ namespace Spring.Aspects.Cache
/// <author>Aleksandar Seovic</author>
public class CacheParameterAdvice : BaseCacheAdvice, IAfterReturningAdvice
{
// shared logger instance
private static readonly ILog logger = LogManager.GetLogger(typeof(CacheParameterAdvice));
private class CacheParameterInfo
{
public readonly ParameterInfo[] Parameters;
public readonly CacheParameterAttribute[][] CacheParameterAttributes;
public CacheParameterInfo(ParameterInfo[] parameters, CacheParameterAttribute[][] cacheParameterAttributes)
{
Parameters = parameters;
CacheParameterAttributes = cacheParameterAttributes;
}
}
private readonly Hashtable _cacheParameterInfoCache = new Hashtable();
/// <summary>
/// Executes after target <paramref name="method"/>
@@ -78,35 +91,54 @@ namespace Spring.Aspects.Cache
/// <seealso cref="AopAlliance.Intercept.IMethodInterceptor.Invoke"/>
public void AfterReturning(object returnValue, MethodInfo method, object[] arguments, object target)
{
ParameterInfo[] parameters = method.GetParameters();
for (int i = 0; i < parameters.Length; i++)
bool isLogDebugEnabled = logger.IsDebugEnabled;
CacheParameterInfo cpi = GetCacheParameterInfo(method);
CacheParameterAttribute[][] cacheParameterAttributes = cpi.CacheParameterAttributes;
for (int i = 0; i < cacheParameterAttributes.Length; i++)
{
ParameterInfo p = parameters[i];
CacheParameterAttribute[] paramInfoArray =
(CacheParameterAttribute[])p.GetCustomAttributes(typeof(CacheParameterAttribute), false);
bool isLogDebugEnabled = logger.IsDebugEnabled;
foreach (CacheParameterAttribute paramInfo in paramInfoArray)
foreach (CacheParameterAttribute paramInfo in cacheParameterAttributes[i])
{
if (EvalCondition(paramInfo.Condition, paramInfo.ConditionExpression, arguments[i], null))
{
ICache cache = GetCache(paramInfo.CacheName);
AssertUtils.ArgumentNotNull(cache, "CacheName",
"Parameter cache with the specified name [" + paramInfo.CacheName +
"] does not exist.");
if (cache == null)
{
throw new ArgumentNullException("CacheName", string.Format("Parameter cache with the specified name [{0}] does not exist.", paramInfo.CacheName));
}
object key = paramInfo.KeyExpression.GetValue(arguments[i]);
#region Instrumentation
#region Instrumentation
if (isLogDebugEnabled)
{
logger.Debug("Caching parameter for key [" + key + "].");
logger.Debug(string.Format("Caching parameter for key [{0}].", key));
}
#endregion
cache.Insert(key, arguments[i], paramInfo.TimeToLiveTimeSpan);
}
}
}
}
private CacheParameterInfo GetCacheParameterInfo(MethodInfo method)
{
CacheParameterInfo cpi = (CacheParameterInfo) _cacheParameterInfoCache[method];
if (cpi == null)
{
ParameterInfo[] parameters = method.GetParameters();
CacheParameterAttribute[][] parameterInfos = new CacheParameterAttribute[parameters.Length][];
for (int i = 0; i < parameters.Length; i++)
{
ParameterInfo p = parameters[i];
CacheParameterAttribute[] paramInfoArray = (CacheParameterAttribute[])GetCustomAttributes(p, typeof(CacheParameterAttribute));
parameterInfos[i] = paramInfoArray;
}
cpi = new CacheParameterInfo(parameters, parameterInfos);
_cacheParameterInfoCache[method] = cpi;
}
return cpi;
}
}
}

View File

@@ -1,5 +1,5 @@
#region License
#region License
/*
* Copyright <20> 2002-2006 the original author or authors.
*
@@ -14,213 +14,236 @@
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#endregion
#region Imports
using System.Collections;
using System.Reflection;
using AopAlliance.Intercept;
using Common.Logging;
using Spring.Caching;
using Spring.Expressions;
using Spring.Util;
#endregion
namespace Spring.Aspects.Cache
{
/// <summary>
/// Implementation of a result caching advice.
/// </summary>
/// <remarks>
/// <p>
/// This advice can be used to cache the return value of the method.
/// </p>
/// <p>
/// Parameters that determine where, how and for how long the return value
/// will be cached are retrieved from the <see cref="CacheResultAttribute"/> and/or
/// <see cref="CacheResultItemsAttribute"/> that are defined on the pointcut.
/// </p>
/// </remarks>
/// <seealso cref="CacheResultAttribute"/>
/// <seealso cref="CacheResultItemsAttribute"/>
/// <author>Aleksandar Seovic</author>
public class CacheResultAdvice : BaseCacheAdvice, IMethodInterceptor
{
// shared logger instance
private static readonly ILog logger = LogManager.GetLogger(typeof (CacheResultAdvice));
// NullValue
private static readonly object NullValue = new object();
/// <summary>
/// Applies caching around a method invocation.
/// </summary>
/// <remarks>
/// <p>
/// This method tries to retrieve an object from the cache, using the supplied
/// <paramref name="invocation"/> to generate a cache key. If an object is found
/// in the cache, the cached value is returned and the method call does not
/// proceed any further down the invocation chain.
/// </p>
/// <p>
/// If object does not exist in the cache, the advised method is called (using
/// <see cref="AopAlliance.Intercept.IJoinpoint.Proceed()"/>)
/// and any return value is cached for the next method invocation.
/// </p>
/// </remarks>
/// <param name="invocation">
/// The method invocation that is being intercepted.
/// </param>
/// <returns>
/// A cached object or the result of the
/// <see cref="AopAlliance.Intercept.IJoinpoint.Proceed()"/> call.
/// </returns>
/// <exception cref="System.Exception">
/// If any of the interceptors in the chain or the target object itself
/// throws an exception.
/// </exception>
public object Invoke(IMethodInvocation invocation)
{
CacheResultAttribute resultInfo =
(CacheResultAttribute) GetCustomAttribute(invocation.Method, typeof (CacheResultAttribute));
CacheResultItemsAttribute[] itemInfoArray =
(CacheResultItemsAttribute[])invocation.Method.GetCustomAttributes(typeof(CacheResultItemsAttribute), false);
bool cacheHit = false;
object returnValue = GetReturnValue(invocation, resultInfo, out cacheHit);
if (!cacheHit && itemInfoArray.Length > 0 && returnValue is IEnumerable)
{
CacheResultItems((IEnumerable)returnValue, itemInfoArray);
}
return returnValue;
}
/// <summary>
/// Obtains return value either from cache or by invoking target method
/// and caches it if necessary.
/// </summary>
/// <param name="invocation">
/// The method invocation that is being intercepted.
/// </param>
/// <param name="resultInfo">
/// Attribute specifying where and how to cache return value. Can be <c>null</c>,
/// in which case no caching of the result as a whole will be performed
/// (if the result is collection, individual items could still be cached separately).
/// </param>
/// <param name="cacheHit">
/// Returns <c>true</c> if the return value was found in cache, <c>false</c> otherwise.
/// </param>
/// <returns>
/// Return value for the specified <paramref name="invocation"/>.
/// </returns>
private object GetReturnValue(IMethodInvocation invocation, CacheResultAttribute resultInfo, out bool cacheHit)
{
if (resultInfo != null)
{
object returnValue = null;
bool isLogDebugEnabled = logger.IsDebugEnabled;
*/
#endregion
#region Imports
using System.Collections;
using System.Reflection;
using AopAlliance.Intercept;
using Common.Logging;
using Spring.Caching;
using Spring.Expressions;
using Spring.Util;
#endregion
namespace Spring.Aspects.Cache
{
/// <summary>
/// Implementation of a result caching advice.
/// </summary>
/// <remarks>
/// <p>
/// This advice can be used to cache the return value of the method.
/// </p>
/// <p>
/// Parameters that determine where, how and for how long the return value
/// will be cached are retrieved from the <see cref="CacheResultAttribute"/> and/or
/// <see cref="CacheResultItemsAttribute"/> that are defined on the pointcut.
/// </p>
/// </remarks>
/// <seealso cref="CacheResultAttribute"/>
/// <seealso cref="CacheResultItemsAttribute"/>
/// <author>Aleksandar Seovic</author>
public class CacheResultAdvice : BaseCacheAdvice, IMethodInterceptor
{
// NullValue
private static readonly object NullValue = new object();
private class CacheResultInfo
{
public readonly CacheResultAttribute ResultInfo;
public readonly CacheResultItemsAttribute[] ItemInfoArray;
public CacheResultInfo(CacheResultAttribute resultInfo, CacheResultItemsAttribute[] itemInfoArray)
{
ResultInfo = resultInfo;
ItemInfoArray = itemInfoArray;
}
}
private readonly Hashtable _cacheResultAttributeCache = new Hashtable();
/// <summary>
/// Applies caching around a method invocation.
/// </summary>
/// <remarks>
/// <p>
/// This method tries to retrieve an object from the cache, using the supplied
/// <paramref name="invocation"/> to generate a cache key. If an object is found
/// in the cache, the cached value is returned and the method call does not
/// proceed any further down the invocation chain.
/// </p>
/// <p>
/// If object does not exist in the cache, the advised method is called (using
/// <see cref="AopAlliance.Intercept.IJoinpoint.Proceed()"/>)
/// and any return value is cached for the next method invocation.
/// </p>
/// </remarks>
/// <param name="invocation">
/// The method invocation that is being intercepted.
/// </param>
/// <returns>
/// A cached object or the result of the
/// <see cref="AopAlliance.Intercept.IJoinpoint.Proceed()"/> call.
/// </returns>
/// <exception cref="System.Exception">
/// If any of the interceptors in the chain or the target object itself
/// throws an exception.
/// </exception>
public object Invoke(IMethodInvocation invocation)
{
CacheResultInfo cacheResultInfo = GetCacheResultInfo(invocation.Method);
bool cacheHit = false;
object returnValue = GetReturnValue(invocation, cacheResultInfo.ResultInfo, out cacheHit);
if (!cacheHit && cacheResultInfo.ItemInfoArray.Length > 0 && returnValue is IEnumerable)
{
CacheResultItems((IEnumerable)returnValue, cacheResultInfo.ItemInfoArray);
}
return returnValue;
}
/// <summary>
/// Obtains return value either from cache or by invoking target method
/// and caches it if necessary.
/// </summary>
/// <param name="invocation">
/// The method invocation that is being intercepted.
/// </param>
/// <param name="resultInfo">
/// Attribute specifying where and how to cache return value. Can be <c>null</c>,
/// in which case no caching of the result as a whole will be performed
/// (if the result is collection, individual items could still be cached separately).
/// </param>
/// <param name="cacheHit">
/// Returns <c>true</c> if the return value was found in cache, <c>false</c> otherwise.
/// </param>
/// <returns>
/// Return value for the specified <paramref name="invocation"/>.
/// </returns>
private object GetReturnValue(IMethodInvocation invocation, CacheResultAttribute resultInfo, out bool cacheHit)
{
if (resultInfo != null)
{
object returnValue = null;
bool isLogDebugEnabled = logger.IsDebugEnabled;
IDictionary vars = PrepareVariables(invocation.Method, invocation.Arguments);
AssertUtils.ArgumentNotNull(resultInfo.KeyExpression, "KeyExpression",
"The cache attribute is missing the key definition.");
object resultKey = resultInfo.KeyExpression.GetValue(null, vars);
ICache cache = GetCache(resultInfo.CacheName);
AssertUtils.ArgumentNotNull(cache, "CacheName",
"Result cache with the specified name [" + resultInfo.CacheName +
"] does not exist.");
returnValue = cache.Get(resultKey);
cacheHit = (returnValue != null);
if (!cacheHit)
{
#region Instrumentation
if (isLogDebugEnabled)
{
logger.Debug("Object for key [" + resultKey + "] was not found in cache. Proceeding...");
}
#endregion
returnValue = invocation.Proceed();
if (EvalCondition(resultInfo.Condition, resultInfo.ConditionExpression, returnValue, vars))
{
#region Instrumentation
if (isLogDebugEnabled)
{
logger.Debug("Caching object for key [" + resultKey + "].");
}
#endregion
cache.Insert(resultKey, (returnValue==null)?NullValue:returnValue, resultInfo.TimeToLiveTimeSpan);
}
}
else
{
#region Instrumentation
if (isLogDebugEnabled)
{
logger.Debug("Cache hit for [" + resultKey + "]. Aborting invocation...");
}
#endregion
}
return (returnValue==NullValue)?null:returnValue;
}
cacheHit = false;
return invocation.Proceed();
}
/// <summary>
/// Caches each item from the collection returned by target method.
/// </summary>
/// <param name="items">
/// A collection of items to cache.
/// </param>
/// <param name="itemInfoArray">
/// Attributes specifying where and how to cache each item from the collection.
/// </param>
private void CacheResultItems(IEnumerable items, CacheResultItemsAttribute[] itemInfoArray)
{
foreach (CacheResultItemsAttribute itemInfo in itemInfoArray)
{
ICache cache = GetCache(itemInfo.CacheName);
AssertUtils.ArgumentNotNull(cache, "CacheName",
"Result item cache with the specified name [" + itemInfo.CacheName +
"The cache attribute is missing the key definition.");
object resultKey = resultInfo.KeyExpression.GetValue(null, vars);
ICache cache = GetCache(resultInfo.CacheName);
AssertUtils.ArgumentNotNull(cache, "CacheName",
"Result cache with the specified name [" + resultInfo.CacheName +
"] does not exist.");
returnValue = cache.Get(resultKey);
cacheHit = (returnValue != null);
if (!cacheHit)
{
#region Instrumentation
if (isLogDebugEnabled)
{
logger.Debug("Object for key [" + resultKey + "] was not found in cache. Proceeding...");
}
#endregion
returnValue = invocation.Proceed();
if (EvalCondition(resultInfo.Condition, resultInfo.ConditionExpression, returnValue, vars))
{
#region Instrumentation
if (isLogDebugEnabled)
{
logger.Debug("Caching object for key [" + resultKey + "].");
}
#endregion
cache.Insert(resultKey, (returnValue == null) ? NullValue : returnValue, resultInfo.TimeToLiveTimeSpan);
}
}
else
{
#region Instrumentation
if (isLogDebugEnabled)
{
logger.Debug("Cache hit for [" + resultKey + "]. Aborting invocation...");
}
#endregion
}
return (returnValue == NullValue) ? null : returnValue;
}
cacheHit = false;
return invocation.Proceed();
}
/// <summary>
/// Caches each item from the collection returned by target method.
/// </summary>
/// <param name="items">
/// A collection of items to cache.
/// </param>
/// <param name="itemInfoArray">
/// Attributes specifying where and how to cache each item from the collection.
/// </param>
private void CacheResultItems(IEnumerable items, CacheResultItemsAttribute[] itemInfoArray)
{
foreach (CacheResultItemsAttribute itemInfo in itemInfoArray)
{
ICache cache = GetCache(itemInfo.CacheName);
AssertUtils.ArgumentNotNull(cache, "CacheName",
"Result item cache with the specified name [" + itemInfo.CacheName +
"] does not exist.");
AssertUtils.ArgumentNotNull(itemInfo.KeyExpression, "KeyExpression",
"The cache attribute is missing the key definition.");
bool isDebugEnabled = logger.IsDebugEnabled;
foreach (object item in items)
{
if (EvalCondition(itemInfo.Condition, itemInfo.ConditionExpression, item, null))
{
object itemKey = itemInfo.KeyExpression.GetValue(item);
#region Instrumentation
if (isDebugEnabled)
{
logger.Debug("Caching collection item for key [" + itemKey + "].");
}
#endregion
cache.Insert(itemKey, (item==null?NullValue:item), itemInfo.TimeToLiveTimeSpan);
}
}
}
}
}
"The cache attribute is missing the key definition.");
bool isDebugEnabled = logger.IsDebugEnabled;
foreach (object item in items)
{
if (EvalCondition(itemInfo.Condition, itemInfo.ConditionExpression, item, null))
{
object itemKey = itemInfo.KeyExpression.GetValue(item);
#region Instrumentation
if (isDebugEnabled)
{
logger.Debug("Caching collection item for key [" + itemKey + "].");
}
#endregion
cache.Insert(itemKey, (item == null ? NullValue : item), itemInfo.TimeToLiveTimeSpan);
}
}
}
}
private CacheResultInfo GetCacheResultInfo(MethodInfo method)
{
CacheResultInfo cacheResultInfo = (CacheResultInfo)_cacheResultAttributeCache[method];
// no need for locking here - last one wins
if (cacheResultInfo == null)
{
CacheResultAttribute resultInfo = (CacheResultAttribute)GetCustomAttribute(method, typeof(CacheResultAttribute));
CacheResultItemsAttribute[] itemInfoArray = (CacheResultItemsAttribute[])GetCustomAttributes(method, typeof(CacheResultItemsAttribute));
cacheResultInfo = new CacheResultInfo(resultInfo, itemInfoArray);
_cacheResultAttributeCache[method] = cacheResultInfo;
}
return cacheResultInfo;
}
}
}

View File

@@ -55,8 +55,7 @@ namespace Spring.Aspects.Cache
/// <author>Aleksandar Seovic</author>
public class InvalidateCacheAdvice : BaseCacheAdvice, IAfterReturningAdvice
{
// shared logger instance
//private static readonly ILog logger = LogManager.GetLogger(typeof(InvalidateCacheAdvice));
private readonly Hashtable _invalidateCacheAttributeCache = new Hashtable();
/// <summary>
/// Executes after <paramref name="target"/> <paramref name="method"/>
@@ -81,8 +80,7 @@ namespace Spring.Aspects.Cache
/// <seealso cref="AopAlliance.Intercept.IMethodInterceptor.Invoke"/>
public void AfterReturning(object returnValue, MethodInfo method, object[] arguments, object target)
{
InvalidateCacheAttribute[] cacheInfoArray =
(InvalidateCacheAttribute[]) method.GetCustomAttributes(typeof(InvalidateCacheAttribute), false);
InvalidateCacheAttribute[] cacheInfoArray = GetInvalidateCacheInfo(method);
if (cacheInfoArray.Length > 0)
{
@@ -115,6 +113,17 @@ namespace Spring.Aspects.Cache
}
}
}
}
private InvalidateCacheAttribute[] GetInvalidateCacheInfo(MethodInfo method)
{
InvalidateCacheAttribute[] cacheInfoArray = (InvalidateCacheAttribute[]) _invalidateCacheAttributeCache[method];
if (cacheInfoArray == null)
{
cacheInfoArray = (InvalidateCacheAttribute[])GetCustomAttributes(method, typeof(InvalidateCacheAttribute));
_invalidateCacheAttributeCache[method] = cacheInfoArray;
}
return cacheInfoArray;
}
}
}

View File

@@ -170,6 +170,5 @@ namespace Spring.Caching
get { return timeToLiveTimeSpan; }
}
#endregion
}
#endregion
}

View File

@@ -77,7 +77,7 @@ namespace Spring.Transaction.Interceptor
/// <summary>
/// Cache of <see cref="ITransactionAttribute"/>s, keyed by method and target class.
/// </summary>
private IDictionary _transactionAttibuteCache = new Hashtable();
private readonly IDictionary _transactionAttibuteCache = new Hashtable();
/// <summary>
/// Creates a new instance of the
@@ -135,11 +135,10 @@ namespace Spring.Transaction.Interceptor
public ITransactionAttribute ReturnTransactionAttribute(MethodInfo method, Type targetType)
{
object cacheKey = getCacheKey(method, targetType);
object cached = null;
lock (_transactionAttibuteCache)
lock (_transactionAttibuteCache)
{
cached = _transactionAttibuteCache[cacheKey];
object cached = _transactionAttibuteCache[cacheKey];
if (cached != null)
{
@@ -228,7 +227,7 @@ namespace Spring.Transaction.Interceptor
private object getCacheKey(MethodBase method, Type targetType)
{
return targetType + String.Empty + method.GetHashCode();
return string.Intern(targetType.AssemblyQualifiedName + "." + method);
}
private ITransactionAttribute computeTransactionAttribute(MethodInfo method, Type targetType)

View File

@@ -375,14 +375,13 @@ namespace Spring.Aspects.Cache
private void ExpectAttributeRetrieval( MethodInfo method )
{
mockInvocation.ExpectAndReturn( "Method", method );
mockInvocation.ExpectAndReturn( "Method", method );
mockInvocation.SetValue( "Method", method );
}
private void ExpectCacheKeyGeneration( MethodInfo method, params object[] arguments )
{
mockInvocation.ExpectAndReturn( "Method", method );
mockInvocation.ExpectAndReturn( "Arguments", arguments );
// mockInvocation.ExpectAndReturn( "Method", method );
mockInvocation.SetValue( "Arguments", arguments );
}
private void ExpectCacheInstanceRetrieval( string cacheName, ICache cache )