在同一个 class 中的另一个方法中调用一个方法时,拦截器不起作用
Interceptor doesn't work when call a method inside another method in same class
我已经使用 Autofac
DynamicProxy
来实现方法缓存。但是当在同一个 class 的另一个方法中调用一个方法时,inceptor 不起作用。例如我有 Class ClassRepository
作为:
public class ClassRepository : IClassRepository
{
[Cache]
public async Task<List<string>> Method1()
{
//Do some thing
}
[Cache]
public async Task<List<string>> Method2()
{
var methodCall = await Method1()
}
}
而我的inceptor是这样的:
public class CacheInterceptor : IInterceptor
{
private readonly ICache cache;
private static ConcurrentDictionary<string, bool> InProcessKeys = new ConcurrentDictionary<string, bool>();
public CacheInterceptor(ICache cache)
{
this.cache = cache;
}
public void Intercept(IInvocation invocation)
{
ProcessInterceptAsync(invocation).Wait();
}
private async Task ProcessInterceptAsync(IInvocation invocation)
{
var proceed = invocation.CaptureProceedInfo();
var cacheAttribute = invocation.MethodInvocationTarget.GetCustomAttributes<CacheAttribute>(false).FirstOrDefault();
if (cacheAttribute == null)
{
proceed.Invoke();
return;
}
var key = GetCacheKey(invocation);
ExistKeyCheck(key);
var methodReturnType = invocation.Method.ReturnType;
dynamic cacheResult = GetCache(key, cacheAttribute, methodReturnType);
if (cacheResult != null)
{
invocation.ReturnValue = cacheResult;
InProcessKeys.Remove(key, out _);
return;
}
InProcessKeys.TryAdd(key, true);
proceed.Invoke();
var delegateType = GetDelegateType(invocation);
switch (delegateType)
{
case MethodType.Synchronous:
break;
case MethodType.AsyncAction:
await InterceptAsync((Task)invocation.ReturnValue);
break;
case MethodType.AsyncFunction:
var method = invocation.MethodInvocationTarget;
var isAsync = method.GetCustomAttribute(typeof(AsyncStateMachineAttribute)) != null;
if (isAsync && typeof(Task).IsAssignableFrom(method.ReturnType))
{
var methodResult = await InterceptAsync((dynamic)invocation.ReturnValue);
invocation.ReturnValue = methodResult;
}
break;
default:
break;
}
if (invocation.ReturnValue == null)
{
InProcessKeys.Remove(key, out _);
return;
}
await cache.SetAsync(key, invocation.ReturnValue, cacheAttribute.Duration, cacheAttribute.CacheInstance, cacheAttribute.Extend);
InProcessKeys.Remove(key, out _);
}
private dynamic GetCache(string key, CacheAttribute cacheAttribute, Type methodReturnType)
{
var finalType = methodReturnType.GetGenericArguments()[0];
var getMethod = typeof(DistributedCache).GetMethods().Where(x => x.IsGenericMethod && x.Name =="Get").FirstOrDefault().MakeGenericMethod(finalType);
var cacheResult = (dynamic)getMethod.Invoke(cache, new object[] { key, cacheAttribute.CacheInstance });
if (cacheResult is null) return null;
if (methodReturnType.GetGenericTypeDefinition() == typeof(Task<>))
return Task.FromResult(cacheResult);
else
return cacheResult;
}
private static void ExistKeyCheck(string key)
{
if (InProcessKeys.Any(x => x.Key==key))
{
Task.Delay(50).Wait();
var counter = 0;
while (InProcessKeys.Any(x => x.Key==key) && counter < 10)
{
Task.Delay(50).Wait();
counter++;
}
}
}
private static async Task InterceptAsync(Task task) => await task.ConfigureAwait(false);
private static async Task<T> InterceptAsync<T>(Task<T> task) => await task.ConfigureAwait(false);
private MethodType GetDelegateType(IInvocation invocation)
{
var returnType = invocation.Method.ReturnType;
if (returnType == typeof(Task))
return MethodType.AsyncAction;
if (returnType.IsGenericType && returnType.GetGenericTypeDefinition() == typeof(Task<>))
return MethodType.AsyncFunction;
return MethodType.Synchronous;
}
private enum MethodType
{
Synchronous,
AsyncAction,
AsyncFunction
}
private static string GetCacheKey(IInvocation invocation)
{
var key = invocation.Arguments.Length > 0 ? $"-{invocation.Arguments[0]}" : "";
var cacheKey = $"{invocation.TargetType.FullName.Replace('.', ':')}" +
$".{invocation.Method.Name}" +
$"{key}";
return cacheKey;
}
}
而我的 autofac 模块是这样的:
public class RepositoryModule : Autofac.Module
{
protected override void Load(ContainerBuilder builder)
{
builder.RegisterType<CacheInterceptor>();
var flightDataAccess = Assembly.Load("DataAccess");
builder.RegisterAssemblyTypes(flightDataAccess)
.Where(x => x.Name.EndsWith("Repository"))
.AsImplementedInterfaces()
.InstancePerLifetimeScope().InterceptedBy(typeof(CacheInterceptor)).EnableInterfaceInterceptors();
}
}
当我解析 IClassRepository 并调用 Method2 时,CacheInterceptor 为 Method2 成功执行。但在 Method2 中,我调用了 Method1,它不适用于 Method1。如果有人能提供帮助,我将不胜感激。
您的问题在这里:
public async void Intercept(IInvocation invocation)
...
invocation.Proceed();
您需要开发一个没有 async void
的解决方案。看看 the docs,它应该会给你一个很好的起点。您需要设置 ReturnValue
并调用 CaptureProceedInfo
.
过了一会儿,我解决了。起初我不得不将我的方法更改为 virtual
方法。
Note: for private methods I change them to protected virtual
最终更改为:
public class ClassRepository : IClassRepository
{
[Cache]
public virtual async Task<List<string>> Method1()
{
//Do some thing
}
[Cache]
public virtual async Task<List<string>> Method2()
{
var methodCall = await Method1()
}
}
在 Module
class 中,我将 EnableInterfaceInterceptors
更改为 EnableClassInterceptors
,最终更改为:
public class RepositoryModule : Autofac.Module
{
protected override void Load(ContainerBuilder builder)
{
builder.RegisterType<CacheInterceptor>();
var flightDataAccess = Assembly.Load("DataAccess");
builder.RegisterAssemblyTypes(flightDataAccess)
.Where(x => x.Name.EndsWith("Repository"))
.AsImplementedInterfaces()
.InstancePerLifetimeScope().InterceptedBy(typeof(CacheInterceptor)).EnableClassInterceptors();
}
}
我已经使用 Autofac
DynamicProxy
来实现方法缓存。但是当在同一个 class 的另一个方法中调用一个方法时,inceptor 不起作用。例如我有 Class ClassRepository
作为:
public class ClassRepository : IClassRepository
{
[Cache]
public async Task<List<string>> Method1()
{
//Do some thing
}
[Cache]
public async Task<List<string>> Method2()
{
var methodCall = await Method1()
}
}
而我的inceptor是这样的:
public class CacheInterceptor : IInterceptor
{
private readonly ICache cache;
private static ConcurrentDictionary<string, bool> InProcessKeys = new ConcurrentDictionary<string, bool>();
public CacheInterceptor(ICache cache)
{
this.cache = cache;
}
public void Intercept(IInvocation invocation)
{
ProcessInterceptAsync(invocation).Wait();
}
private async Task ProcessInterceptAsync(IInvocation invocation)
{
var proceed = invocation.CaptureProceedInfo();
var cacheAttribute = invocation.MethodInvocationTarget.GetCustomAttributes<CacheAttribute>(false).FirstOrDefault();
if (cacheAttribute == null)
{
proceed.Invoke();
return;
}
var key = GetCacheKey(invocation);
ExistKeyCheck(key);
var methodReturnType = invocation.Method.ReturnType;
dynamic cacheResult = GetCache(key, cacheAttribute, methodReturnType);
if (cacheResult != null)
{
invocation.ReturnValue = cacheResult;
InProcessKeys.Remove(key, out _);
return;
}
InProcessKeys.TryAdd(key, true);
proceed.Invoke();
var delegateType = GetDelegateType(invocation);
switch (delegateType)
{
case MethodType.Synchronous:
break;
case MethodType.AsyncAction:
await InterceptAsync((Task)invocation.ReturnValue);
break;
case MethodType.AsyncFunction:
var method = invocation.MethodInvocationTarget;
var isAsync = method.GetCustomAttribute(typeof(AsyncStateMachineAttribute)) != null;
if (isAsync && typeof(Task).IsAssignableFrom(method.ReturnType))
{
var methodResult = await InterceptAsync((dynamic)invocation.ReturnValue);
invocation.ReturnValue = methodResult;
}
break;
default:
break;
}
if (invocation.ReturnValue == null)
{
InProcessKeys.Remove(key, out _);
return;
}
await cache.SetAsync(key, invocation.ReturnValue, cacheAttribute.Duration, cacheAttribute.CacheInstance, cacheAttribute.Extend);
InProcessKeys.Remove(key, out _);
}
private dynamic GetCache(string key, CacheAttribute cacheAttribute, Type methodReturnType)
{
var finalType = methodReturnType.GetGenericArguments()[0];
var getMethod = typeof(DistributedCache).GetMethods().Where(x => x.IsGenericMethod && x.Name =="Get").FirstOrDefault().MakeGenericMethod(finalType);
var cacheResult = (dynamic)getMethod.Invoke(cache, new object[] { key, cacheAttribute.CacheInstance });
if (cacheResult is null) return null;
if (methodReturnType.GetGenericTypeDefinition() == typeof(Task<>))
return Task.FromResult(cacheResult);
else
return cacheResult;
}
private static void ExistKeyCheck(string key)
{
if (InProcessKeys.Any(x => x.Key==key))
{
Task.Delay(50).Wait();
var counter = 0;
while (InProcessKeys.Any(x => x.Key==key) && counter < 10)
{
Task.Delay(50).Wait();
counter++;
}
}
}
private static async Task InterceptAsync(Task task) => await task.ConfigureAwait(false);
private static async Task<T> InterceptAsync<T>(Task<T> task) => await task.ConfigureAwait(false);
private MethodType GetDelegateType(IInvocation invocation)
{
var returnType = invocation.Method.ReturnType;
if (returnType == typeof(Task))
return MethodType.AsyncAction;
if (returnType.IsGenericType && returnType.GetGenericTypeDefinition() == typeof(Task<>))
return MethodType.AsyncFunction;
return MethodType.Synchronous;
}
private enum MethodType
{
Synchronous,
AsyncAction,
AsyncFunction
}
private static string GetCacheKey(IInvocation invocation)
{
var key = invocation.Arguments.Length > 0 ? $"-{invocation.Arguments[0]}" : "";
var cacheKey = $"{invocation.TargetType.FullName.Replace('.', ':')}" +
$".{invocation.Method.Name}" +
$"{key}";
return cacheKey;
}
}
而我的 autofac 模块是这样的:
public class RepositoryModule : Autofac.Module
{
protected override void Load(ContainerBuilder builder)
{
builder.RegisterType<CacheInterceptor>();
var flightDataAccess = Assembly.Load("DataAccess");
builder.RegisterAssemblyTypes(flightDataAccess)
.Where(x => x.Name.EndsWith("Repository"))
.AsImplementedInterfaces()
.InstancePerLifetimeScope().InterceptedBy(typeof(CacheInterceptor)).EnableInterfaceInterceptors();
}
}
当我解析 IClassRepository 并调用 Method2 时,CacheInterceptor 为 Method2 成功执行。但在 Method2 中,我调用了 Method1,它不适用于 Method1。如果有人能提供帮助,我将不胜感激。
您的问题在这里:
public async void Intercept(IInvocation invocation)
...
invocation.Proceed();
您需要开发一个没有 async void
的解决方案。看看 the docs,它应该会给你一个很好的起点。您需要设置 ReturnValue
并调用 CaptureProceedInfo
.
过了一会儿,我解决了。起初我不得不将我的方法更改为 virtual
方法。
Note: for private methods I change them to
protected virtual
最终更改为:
public class ClassRepository : IClassRepository
{
[Cache]
public virtual async Task<List<string>> Method1()
{
//Do some thing
}
[Cache]
public virtual async Task<List<string>> Method2()
{
var methodCall = await Method1()
}
}
在 Module
class 中,我将 EnableInterfaceInterceptors
更改为 EnableClassInterceptors
,最终更改为:
public class RepositoryModule : Autofac.Module
{
protected override void Load(ContainerBuilder builder)
{
builder.RegisterType<CacheInterceptor>();
var flightDataAccess = Assembly.Load("DataAccess");
builder.RegisterAssemblyTypes(flightDataAccess)
.Where(x => x.Name.EndsWith("Repository"))
.AsImplementedInterfaces()
.InstancePerLifetimeScope().InterceptedBy(typeof(CacheInterceptor)).EnableClassInterceptors();
}
}