diff --git a/src/Spring/Spring.Aop/Aspects/Cache/CacheResultAdvice.cs b/src/Spring/Spring.Aop/Aspects/Cache/CacheResultAdvice.cs
index a6883095..efe272a5 100644
--- a/src/Spring/Spring.Aop/Aspects/Cache/CacheResultAdvice.cs
+++ b/src/Spring/Spring.Aop/Aspects/Cache/CacheResultAdvice.cs
@@ -1,19 +1,19 @@
#region License
-/*
- * Copyright © 2002-2006 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * 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.
+/*
+ * Copyright © 2002-2006 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * 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
@@ -84,8 +84,26 @@ namespace Spring.Aspects.Cache
#endregion
+ ///
+ /// Inner class to help cache null values.
+ ///
+ [Serializable] public sealed class NullValueMarkerType
+ {
+ /// true when other object is of same type.
+ public override bool Equals(object obj)
+ {
+ return obj is NullValueMarkerType;
+ }
+
+ /// 13
+ public override int GetHashCode()
+ {
+ return 13;
+ }
+ }
+
// NullValue
- private static readonly object NullValue = new object();
+ private static readonly object NullValue = new NullValueMarkerType();
///
/// Applies caching around a method invocation.
@@ -170,6 +188,26 @@ namespace Spring.Aspects.Cache
ICache cache = GetCache(resultInfo.CacheName);
returnValue = cache.Get(resultKey);
cacheHit = (returnValue != null);
+
+ if (NullValue.Equals(returnValue))
+ {
+ returnValue = null;
+ }
+
+ Type returnType = invocation.Method.ReturnType;
+ if (returnValue != null && !returnType.IsInstanceOfType(returnValue))
+ {
+ #region Instrumentation
+ if (isLogDebugEnabled)
+ {
+ logger.Debug(String.Format("Object for key [{0}] was of type [{1}] which is not compatible with return type [{2}]. Proceeding...", resultKey, returnValue.GetType(), returnType));
+ }
+ #endregion
+
+ cacheHit = false;
+ returnValue = null;
+ }
+
if (!cacheHit)
{
#region Instrumentation
@@ -201,7 +239,7 @@ namespace Spring.Aspects.Cache
#endregion
}
- return (returnValue == NullValue) ? null : returnValue;
+ return returnValue;
}
cacheHit = false;
diff --git a/test/Spring/Spring.Aop.Tests/Aspects/Cache/CacheAspectIntegrationTests.cs b/test/Spring/Spring.Aop.Tests/Aspects/Cache/CacheAspectIntegrationTests.cs
index 188751fd..31a38122 100644
--- a/test/Spring/Spring.Aop.Tests/Aspects/Cache/CacheAspectIntegrationTests.cs
+++ b/test/Spring/Spring.Aop.Tests/Aspects/Cache/CacheAspectIntegrationTests.cs
@@ -62,14 +62,14 @@ namespace Spring.Aspects.Cache
ProxyFactory pf = new ProxyFactory(new InventorStore());
pf.AddAdvisors(cacheAspect);
- IInventorStore store = (IInventorStore) pf.GetProxy();
+ IInventorStore store = (IInventorStore)pf.GetProxy();
Assert.AreEqual(0, cache.Count);
-
+
IList inventors = store.GetAll();
Assert.AreEqual(2, cache.Count);
- store.Delete((Inventor) inventors[0]);
+ store.Delete((Inventor)inventors[0]);
Assert.AreEqual(1, cache.Count);
Inventor tesla = store.Load("Nikola Tesla");
@@ -83,6 +83,24 @@ namespace Spring.Aspects.Cache
Assert.AreEqual(0, cache.Count);
}
+ [Test]
+ public void TestCacheResultDoesNotReturnInvalidType()
+ {
+ ICache cache = new NonExpiringCache();
+ context.ObjectFactory.RegisterSingleton("inventors", cache);
+
+ ProxyFactory pf = new ProxyFactory(new InventorStore());
+ pf.AddAdvisors(cacheAspect);
+
+ IInventorStore store = (IInventorStore)pf.GetProxy();
+
+ cache.Insert("Nikola Tesla", "WrongTypeForMethodSignature");
+
+ Inventor value = store.Load("Nikola Tesla");
+
+ Assert.That(value, Is.AssignableTo(typeof(Inventor)), "CacheAspect returned a cached type that is incompatible with the method signature return type");
+ }
+
///
/// http://jira.springframework.org/browse/SPRNET-1226
///
diff --git a/test/Spring/Spring.Aop.Tests/Aspects/Cache/CacheResultAdviceTests.cs b/test/Spring/Spring.Aop.Tests/Aspects/Cache/CacheResultAdviceTests.cs
index c0277455..30e32113 100644
--- a/test/Spring/Spring.Aop.Tests/Aspects/Cache/CacheResultAdviceTests.cs
+++ b/test/Spring/Spring.Aop.Tests/Aspects/Cache/CacheResultAdviceTests.cs
@@ -21,7 +21,9 @@
#region Imports
using System.Collections;
+using System.IO;
using System.Reflection;
+using System.Runtime.Serialization.Formatters.Binary;
using AopAlliance.Intercept;
using DotNetMock.Dynamic;
using NUnit.Framework;
@@ -46,6 +48,7 @@ namespace Spring.Aspects.Cache
private CacheResultAdvice advice;
private ICache resultCache;
private ICache itemCache;
+ private ICache binaryFormatterCache;
private CacheResultTarget cacheResultTarget = new CacheResultTarget();
[SetUp]
@@ -59,6 +62,7 @@ namespace Spring.Aspects.Cache
resultCache = new NonExpiringCache();
itemCache = new NonExpiringCache();
+ binaryFormatterCache = new BinaryFormatterCache();
}
///
@@ -86,6 +90,36 @@ namespace Spring.Aspects.Cache
mockContext.Verify();
}
+ [Test]
+ public void CacheResultOfMethodThatReturnsNullWithSerializingCache()
+ {
+ MethodInfo method = new VoidMethod(cacheResultTarget.ReturnsNothing).Method;
+ object expectedReturnValue = null;
+
+ ExpectAttributeRetrieval(method);
+ ExpectCacheKeyGeneration(method, null);
+ ExpectCacheInstanceRetrieval("results", binaryFormatterCache);
+ ExpectCallToProceed(expectedReturnValue);
+
+ // check that the null retVal is cached as well - it might be
+ // the result of an expensive webservice/database call etc.
+ object returnValue = advice.Invoke((IMethodInvocation) mockInvocation.Object);
+ Assert.AreEqual(expectedReturnValue, returnValue);
+ Assert.AreEqual(1, binaryFormatterCache.Count);
+
+ // and again, but without Proceed()...
+ ExpectAttributeRetrieval(method);
+ ExpectCacheKeyGeneration(method, null);
+ ExpectCacheInstanceRetrieval("results", binaryFormatterCache);
+
+ // cached value should be returned
+ object cachedValue = advice.Invoke((IMethodInvocation)mockInvocation.Object);
+ Assert.IsNull(cachedValue, "Should recognize cached value as null-value marker.");
+
+ mockInvocation.Verify();
+ mockContext.Verify();
+ }
+
[Test]
public void CacheResultOfMethodThatReturnsObject()
{
@@ -571,4 +605,33 @@ namespace Spring.Aspects.Cache
}
#endregion
+
+ class BinaryFormatterCache : NonExpiringCache
+ {
+ protected override void DoInsert(object key, object value, System.TimeSpan timeToLive)
+ {
+ BinaryFormatter fmt = new BinaryFormatter();
+ using (MemoryStream stream = new MemoryStream())
+ {
+ fmt.Serialize(stream, value);
+ stream.Seek(0, SeekOrigin.Begin);
+ value = stream.ToArray();
+ }
+
+ base.DoInsert(key, value, timeToLive);
+ }
+
+ public override object Get(object key)
+ {
+ byte[] bytes = (byte[]) base.Get(key);
+
+ if (bytes == null)
+ {
+ return null;
+ }
+
+ BinaryFormatter fmt = new BinaryFormatter();
+ return fmt.Deserialize(new MemoryStream(bytes));
+ }
+ }
}