2016-08-23 133 views
0

我試圖通過使用VirtualMethodInterceptor的Unity攔截在我的應用程序中集成瞬態故障處理應用程序塊。統一攔截調用基本方法

我創建了一個調用處理程序來創建攔截方法的動作或func或任務,並將其傳遞到瞬態故障處理程序中,但現在我只是得到一個stackoverflow異常。這是有道理的,因爲故障處理程序將調用統一將攔截並傳遞到故障處理程序的方法。

我需要的是當錯誤處理程序回調或直接調用基本方法時,能夠跳過截取。一些如何能夠做到這一點,因爲它最終會調用我的類方法。

我的攔截碼

public class RetryHandler : ICallHandler 
{ 
    // store a cache of the delegates 
    private static readonly ConcurrentDictionary<MethodInfo, Func<object, object[], object>> CacheCall = 
     new ConcurrentDictionary<MethodInfo, Func<object, object[], object>>(); 

    // List of methods we need to call 
    private static readonly Action<Action> RetryActionMethod = RetryPolicy.NoRetry.ExecuteAction; 
    private static readonly Func<Func<int>, int> RetryFuncMethod = RetryPolicy.NoRetry.ExecuteAction; 
    private static readonly Func<Func<Task<int>>, Task<int>> RetryAsyncFuncMethod = RetryPolicy.NoRetry.ExecuteAsync; 
    private static readonly Func<Func<Task>, Task> RetryAsyncActionMethod = RetryPolicy.NoRetry.ExecuteAsync; 
    private static readonly ConstructorInfo RetryPolicyConstructor; 

    static RetryHandler() 
    { 
     RetryPolicyConstructor = 
      typeof (RetryPolicy).GetConstructor(new[] 
      {typeof (ITransientErrorDetectionStrategy), typeof (RetryStrategy)}); 
    } 

    public int Order { get; set; } 

    /// <summary> 
    /// Uses Expression Trees to wrap method call into retryhandler 
    /// </summary> 
    /// <param name="input"></param> 
    /// <param name="getNext"></param> 
    /// <returns></returns> 
    public IMethodReturn Invoke(IMethodInvocation input, GetNextHandlerDelegate getNext) 
    { 
     if (input.Arguments.Count == input.Inputs.Count) 
     { 
      var methodInfo = input.MethodBase as MethodInfo; 
      if (methodInfo != null) 
      { 
       var func = CacheCall.GetOrAdd(methodInfo, BuildLambda); 
       var results = func(input.Target, input.Inputs.OfType<object>().ToArray()); 
       var ex = results as Exception; 
       if (ex != null) 
       { 
        return input.CreateExceptionMethodReturn(ex); 
       } 
       return input.CreateMethodReturn(results); 
      } 
     } 
     return getNext()(input, getNext); 
    } 

    private static Func<object, object[], object> BuildLambda(MethodInfo methodInfo) 
    { 
     var retryAttribute = methodInfo.GetCustomAttributes<RetryAttribute>(true).First(); 

     // Convert parameters and source object to be able to call method 
     var target = Expression.Parameter(typeof (object), "target"); 
     var parameters = Expression.Parameter(typeof (object[]), "parameters"); 
     Expression source; 
     if (methodInfo.DeclaringType == null) 
     { 
      source = target; 
     } 
     else 
     { 
      source = Expression.Convert(target, methodInfo.DeclaringType); 
     } 

     var convertedParams = 
      methodInfo.GetParameters() 
       .Select(
        (p, i) => 
         Expression.Convert(Expression.ArrayIndex(parameters, Expression.Constant(i)), 
          p.ParameterType)).Cast<Expression>() 
       .ToArray(); 

     //!!!!! **This line of code causes the stackoverflow as this will go back through interception** 
     var innerExpression = Expression.Call(source, methodInfo, convertedParams); 

     // get what type of lambda we need to build and what method we need to call on the retry handler 
     Type returnType; 
     MethodInfo retryMethod; 
     if (methodInfo.ReturnType == typeof (void)) 
     { 
      returnType = typeof (Action); 
      retryMethod = RetryActionMethod.Method; 
     } 
     else if (methodInfo.ReturnType == typeof (Task)) 
     { 
      returnType = typeof (Func<Task>); 
      retryMethod = RetryAsyncActionMethod.Method; 
     } 
     else if (methodInfo.ReturnType.IsGenericType && 
       methodInfo.ReturnType.GetGenericTypeDefinition() == typeof (Task<>)) 
     { 
      var genericType = methodInfo.ReturnType.GetGenericArguments()[0]; 
      returnType = 
       typeof (Func<>).MakeGenericType(
        typeof (Task<>).MakeGenericType(genericType)); 
      retryMethod = RetryAsyncFuncMethod.Method.GetGenericMethodDefinition().MakeGenericMethod(genericType); 
     } 
     else 
     { 
      returnType = typeof (Func<>).MakeGenericType(methodInfo.ReturnType); 
      retryMethod = 
       RetryFuncMethod.Method.GetGenericMethodDefinition().MakeGenericMethod(methodInfo.ReturnType); 
     } 

     var innerLambda = Expression.Lambda(returnType, innerExpression); 

     var outerLambda = Expression.Lambda(
      typeof (Func<,,>).MakeGenericType(typeof (object), typeof (object[]), returnType), 
      innerLambda, 
      target, parameters); 

     // create the retry handler 
     var retryPolicy = Expression.New(RetryPolicyConstructor, 
      Expression.Invoke(retryAttribute.TransientErrorDetectionStrategy), 
      Expression.Invoke(retryAttribute.RetryStrategy)); 

     var passedInTarget = Expression.Parameter(typeof (object), "wrapperTarget"); 
     var passedInParameters = Expression.Parameter(typeof (object[]), "wrapperParamters"); 

     var retryCall = Expression.Call(retryPolicy, retryMethod, 
      Expression.Invoke(outerLambda, passedInTarget, passedInParameters)); 

     Expression resultExpression; 
     if (methodInfo.ReturnType != typeof (void)) 
     { 
      // convert to object so we can have a standard func<object, object[], object> 
      resultExpression = Expression.Convert(retryCall, typeof (object)); 
     } 
     else 
     { 
      // if void we will set the return results as null - it's what unity wants and plus we keep our signature 
      var returnTarget = Expression.Label(typeof (object)); 
      resultExpression = Expression.Block(retryCall, Expression.Label(returnTarget, Expression.Constant(null))); 
     } 


     var func = 
      Expression.Lambda<Func<object, object[], object>>(resultExpression, passedInTarget, passedInParameters) 
       .Compile(); 


     return func; 
    } 
} 

我的屬性

public abstract class RetryAttribute : HandlerAttribute 
{ 
    public RetryAttribute() 
    { 
     RetryStrategy =() => 
      Microsoft.Practices.EnterpriseLibrary.TransientFaultHandling.RetryStrategy.NoRetry; 
     TransientErrorDetectionStrategy =() => RetryPolicy.NoRetry.ErrorDetectionStrategy; 
    } 
    public Expression<Func<RetryStrategy>> RetryStrategy { get; protected set; } 

    public Expression<Func<ITransientErrorDetectionStrategy>> TransientErrorDetectionStrategy { get; protected set; } 

    public override ICallHandler CreateHandler(IUnityContainer container) 
    { 
     return new RetryHandler(); 
    } 
} 

public class SqlDatabaseeRetryAttribute : RetryAttribute 
{ 
    public SqlDatabaseeRetryAttribute() 
    { 
     RetryStrategy = 
      () => Microsoft.Practices.EnterpriseLibrary.TransientFaultHandling.RetryStrategy.DefaultExponential; 
     TransientErrorDetectionStrategy =() => new SqlDatabaseTransientErrorDetectionStrategy(); 
    } 

} 

回答

0

結束了使用反射發出調用基方法,而不是虛擬方法。

/// <summary> 
/// Creates a base. call method 
/// Needs to do this so we can skip the unity interception and call 
/// </summary> 
/// <param name="methodInfo"></param> 
/// <returns></returns> 
private static Delegate BaseMethodCall(MethodInfo methodInfo) 
{ 

    // get parameter types 
    // include the calling type to make it an open delegate 
    var paramTypes = new[] { methodInfo.DeclaringType.BaseType}.Concat(
     methodInfo.GetParameters().Select(pi => pi.ParameterType)).ToArray(); 

    var baseCall = new DynamicMethod(string.Empty, methodInfo.ReturnType, paramTypes, methodInfo.Module); 

    var il = baseCall.GetILGenerator(); 
    // add all the parameters into the stack 
    for (var i = 0; i < paramTypes.Length; i++) 
    { 
     il.Emit(OpCodes.Ldarg, i); 
    } 
    // call the method but not the virtual method 
    // this is the key to not have the virtual run 
    il.EmitCall(OpCodes.Call, methodInfo, null); 
    il.Emit(OpCodes.Ret); 

    // get the deletage type call of this method 
    var delegateType = Expression.GetDelegateType(paramTypes.Concat(new[] { methodInfo.ReturnType}).ToArray()); 

    return baseCall.CreateDelegate(delegateType); 
} 

,並更改行BuildLambda這是造成該問題

// need to create call to bypass unity interception 
var baseDelegate = BaseMethodCall(methodInfo); 
var innerExpression = Expression.Invoke(Expression.Constant(baseDelegate), new[] { source }.Concat(convertedParams));