/interceptor/EasyCaching.Interceptor.Castle/EasyCachingInterceptor.cs

https://github.com/dotnetcore/EasyCaching · C# · 282 lines · 194 code · 26 blank · 62 comment · 29 complexity · 57d2e6d3c4c7f02114ad45ae6c810734 MD5 · raw file

  1. namespace EasyCaching.Interceptor.Castle
  2. {
  3. using EasyCaching.Core;
  4. using EasyCaching.Core.Configurations;
  5. using EasyCaching.Core.Interceptor;
  6. using global::Castle.DynamicProxy;
  7. using Microsoft.Extensions.Logging;
  8. using Microsoft.Extensions.Options;
  9. using System;
  10. using System.Collections.Concurrent;
  11. using System.Linq;
  12. using System.Reflection;
  13. using System.Threading.Tasks;
  14. /// <summary>
  15. /// Easycaching interceptor.
  16. /// </summary>
  17. public class EasyCachingInterceptor : IInterceptor
  18. {
  19. /// <summary>
  20. /// The key generator.
  21. /// </summary>
  22. private readonly IEasyCachingKeyGenerator _keyGenerator;
  23. /// <summary>
  24. /// The cache provider factory.
  25. /// </summary>
  26. private readonly IEasyCachingProviderFactory _cacheProviderFactory;
  27. /// <summary>
  28. /// The hybrid caching provider.
  29. /// </summary>
  30. public IHybridCachingProvider _hybridCachingProvider;
  31. /// <summary>
  32. /// Get or set the options
  33. /// </summary>
  34. public IOptions<EasyCachingInterceptorOptions> _options { get; set; }
  35. /// <summary>
  36. /// logger
  37. /// </summary>
  38. public ILogger<EasyCachingInterceptor> _logger;
  39. /// <summary>
  40. /// The typeof task result method.
  41. /// </summary>
  42. private static readonly ConcurrentDictionary<Type, MethodInfo>
  43. TypeofTaskResultMethod = new ConcurrentDictionary<Type, MethodInfo>();
  44. /// <summary>
  45. /// The typeof task result method.
  46. /// </summary>
  47. private static readonly ConcurrentDictionary<MethodInfo, object[]>
  48. MethodAttributes = new ConcurrentDictionary<MethodInfo, object[]>();
  49. /// <summary>
  50. /// Initializes a new instance of the <see cref="T:EasyCaching.Interceptor.Castle.EasyCachingInterceptor"/> class.
  51. /// </summary>
  52. /// <param name="cacheProviderFactory">Cache provider factory.</param>
  53. /// <param name="keyGenerator">Key generator.</param>
  54. /// <param name="options">Options.</param>
  55. /// <param name="logger">Logger.</param>
  56. /// <param name="hybridCachingProvider">Hybrid caching provider.</param>
  57. public EasyCachingInterceptor(
  58. IEasyCachingProviderFactory cacheProviderFactory
  59. , IEasyCachingKeyGenerator keyGenerator
  60. , IOptions<EasyCachingInterceptorOptions> options
  61. , ILogger<EasyCachingInterceptor> logger = null
  62. , IHybridCachingProvider hybridCachingProvider = null)
  63. {
  64. _options = options;
  65. _cacheProviderFactory = cacheProviderFactory;
  66. _keyGenerator = keyGenerator;
  67. _logger = logger;
  68. _hybridCachingProvider = hybridCachingProvider;
  69. }
  70. /// <summary>
  71. /// Intercept the specified invocation.
  72. /// </summary>
  73. /// <returns>The intercept.</returns>
  74. /// <param name="invocation">Invocation.</param>
  75. public void Intercept(IInvocation invocation)
  76. {
  77. //Process any early evictions
  78. ProcessEvict(invocation, true);
  79. //Process any cache interceptor
  80. ProceedAble(invocation);
  81. // Process any put requests
  82. ProcessPut(invocation);
  83. // Process any late evictions
  84. ProcessEvict(invocation, false);
  85. }
  86. private object[] GetMethodAttributes(MethodInfo mi)
  87. {
  88. return MethodAttributes.GetOrAdd(mi, mi.GetCustomAttributes(true));
  89. }
  90. /// <summary>
  91. /// Proceeds the able.
  92. /// </summary>
  93. /// <param name="invocation">Invocation.</param>
  94. private void ProceedAble(IInvocation invocation)
  95. {
  96. var serviceMethod = invocation.Method ?? invocation.MethodInvocationTarget;
  97. if (GetMethodAttributes(serviceMethod).FirstOrDefault(x => typeof(EasyCachingAbleAttribute).IsAssignableFrom(x.GetType())) is EasyCachingAbleAttribute attribute)
  98. {
  99. var returnType = serviceMethod.IsReturnTask()
  100. ? serviceMethod.ReturnType.GetGenericArguments().First()
  101. : serviceMethod.ReturnType;
  102. var cacheKey = _keyGenerator.GetCacheKey(serviceMethod, invocation.Arguments, attribute.CacheKeyPrefix);
  103. object cacheValue = null;
  104. var isAvailable = true;
  105. try
  106. {
  107. if (attribute.IsHybridProvider)
  108. {
  109. cacheValue = _hybridCachingProvider.GetAsync(cacheKey, returnType).GetAwaiter().GetResult();
  110. }
  111. else
  112. {
  113. var _cacheProvider = _cacheProviderFactory.GetCachingProvider(attribute.CacheProviderName ?? _options.Value.CacheProviderName);
  114. cacheValue = _cacheProvider.GetAsync(cacheKey, returnType).GetAwaiter().GetResult();
  115. }
  116. }
  117. catch (Exception ex)
  118. {
  119. if (!attribute.IsHighAvailability)
  120. {
  121. throw;
  122. }
  123. else
  124. {
  125. isAvailable = false;
  126. _logger?.LogError(new EventId(), ex, $"Cache provider get error.");
  127. }
  128. }
  129. if (cacheValue != null)
  130. {
  131. if (serviceMethod.IsReturnTask())
  132. {
  133. invocation.ReturnValue =
  134. TypeofTaskResultMethod.GetOrAdd(returnType, t => typeof(Task).GetMethods().First(p => p.Name == "FromResult" && p.ContainsGenericParameters).MakeGenericMethod(returnType)).Invoke(null, new object[] { cacheValue });
  135. }
  136. else
  137. {
  138. invocation.ReturnValue = cacheValue;
  139. }
  140. }
  141. else
  142. {
  143. // Invoke the method if we don't have a cache hit
  144. invocation.Proceed();
  145. if (!string.IsNullOrWhiteSpace(cacheKey) && invocation.ReturnValue != null && isAvailable)
  146. {
  147. // get the result
  148. var returnValue = serviceMethod.IsReturnTask()
  149. ? invocation.UnwrapAsyncReturnValue().Result
  150. : invocation.ReturnValue;
  151. // should we do something when method return null?
  152. // 1. cached a null value for a short time
  153. // 2. do nothing
  154. if (returnValue != null)
  155. {
  156. if (attribute.IsHybridProvider)
  157. {
  158. _hybridCachingProvider.Set(cacheKey, returnValue, TimeSpan.FromSeconds(attribute.Expiration));
  159. }
  160. else
  161. {
  162. var _cacheProvider = _cacheProviderFactory.GetCachingProvider(attribute.CacheProviderName ?? _options.Value.CacheProviderName);
  163. _cacheProvider.Set(cacheKey, returnValue, TimeSpan.FromSeconds(attribute.Expiration));
  164. }
  165. }
  166. }
  167. }
  168. }
  169. else
  170. {
  171. // Invoke the method if we don't have EasyCachingAbleAttribute
  172. invocation.Proceed();
  173. }
  174. }
  175. /// <summary>
  176. /// Processes the put.
  177. /// </summary>
  178. /// <param name="invocation">Invocation.</param>
  179. private void ProcessPut(IInvocation invocation)
  180. {
  181. var serviceMethod = invocation.Method ?? invocation.MethodInvocationTarget;
  182. if (GetMethodAttributes(serviceMethod).FirstOrDefault(x => typeof(EasyCachingPutAttribute).IsAssignableFrom(x.GetType())) is EasyCachingPutAttribute attribute && invocation.ReturnValue != null)
  183. {
  184. var cacheKey = _keyGenerator.GetCacheKey(serviceMethod, invocation.Arguments, attribute.CacheKeyPrefix);
  185. try
  186. {
  187. var returnValue = serviceMethod.IsReturnTask()
  188. ? invocation.UnwrapAsyncReturnValue().Result
  189. : invocation.ReturnValue;
  190. if (attribute.IsHybridProvider)
  191. {
  192. _hybridCachingProvider.Set(cacheKey, returnValue, TimeSpan.FromSeconds(attribute.Expiration));
  193. }
  194. else
  195. {
  196. var _cacheProvider = _cacheProviderFactory.GetCachingProvider(attribute.CacheProviderName ?? _options.Value.CacheProviderName);
  197. _cacheProvider.Set(cacheKey, returnValue, TimeSpan.FromSeconds(attribute.Expiration));
  198. }
  199. }
  200. catch (Exception ex)
  201. {
  202. if (!attribute.IsHighAvailability) throw;
  203. else _logger?.LogError(new EventId(), ex, $"Cache provider set error.");
  204. }
  205. }
  206. }
  207. /// <summary>
  208. /// Processes the evict.
  209. /// </summary>
  210. /// <param name="invocation">Invocation.</param>
  211. /// <param name="isBefore">If set to <c>true</c> is before.</param>
  212. private void ProcessEvict(IInvocation invocation, bool isBefore)
  213. {
  214. var serviceMethod = invocation.Method ?? invocation.MethodInvocationTarget;
  215. if (GetMethodAttributes(serviceMethod).FirstOrDefault(x => typeof(EasyCachingEvictAttribute).IsAssignableFrom(x.GetType())) is EasyCachingEvictAttribute attribute && attribute.IsBefore == isBefore)
  216. {
  217. try
  218. {
  219. if (attribute.IsAll)
  220. {
  221. //If is all , clear all cached items which cachekey start with the prefix.
  222. var cacheKeyPrefix = _keyGenerator.GetCacheKeyPrefix(serviceMethod, attribute.CacheKeyPrefix);
  223. if (attribute.IsHybridProvider)
  224. {
  225. _hybridCachingProvider.RemoveByPrefix(cacheKeyPrefix);
  226. }
  227. else
  228. {
  229. var _cacheProvider = _cacheProviderFactory.GetCachingProvider(attribute.CacheProviderName ?? _options.Value.CacheProviderName);
  230. _cacheProvider.RemoveByPrefix(cacheKeyPrefix);
  231. }
  232. }
  233. else
  234. {
  235. //If not all , just remove the cached item by its cachekey.
  236. var cacheKey = _keyGenerator.GetCacheKey(serviceMethod, invocation.Arguments, attribute.CacheKeyPrefix);
  237. if (attribute.IsHybridProvider)
  238. {
  239. _hybridCachingProvider.Remove(cacheKey);
  240. }
  241. else
  242. {
  243. var _cacheProvider = _cacheProviderFactory.GetCachingProvider(attribute.CacheProviderName ?? _options.Value.CacheProviderName);
  244. _cacheProvider.Remove(cacheKey);
  245. }
  246. }
  247. }
  248. catch (Exception ex)
  249. {
  250. if (!attribute.IsHighAvailability) throw;
  251. else _logger?.LogError(new EventId(), ex, $"Cache provider remove error.");
  252. }
  253. }
  254. }
  255. }
  256. }