0

我正在尝试使用 VirtualMethodInterceptor 通过 Unity Interception 将瞬态故障处理应用程序块集成到我的应用程序中。

我创建了一个调用处理程序来创建被拦截方法的操作或函数或任务,并将其传递给瞬态故障处理程序,但现在我得到了一个 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();
    }

}
4

1 回答 1

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));
于 2016-08-23T19:09:26.923 回答