开源组件:Caslte DynamicProxy

492 阅读2分钟

Castle

Castle是.Net体系下一个常用的开源项目。适用于简化企业的应用程序开发。我们来介绍一下Castle DynamicProxy。

面向切面编程(AOP)是一种编程思想。设计模式的动态代理模式则是AOP实现的方式的一种。借助Castle则可以快速实现面向切面编程。

应用场景:

  1. 通过代理,我们可以在客户端执行前后,做一些我们期望的行为动作。如打日志、做缓存。
  2. 将控制逻辑,和业务代码分离开。比如我们在请求第三方的API,为了保障服务的高可用,我们期望引入一些策略,如重试、熔断、降级等。(Polly),我们可以借助Caslte通过切面的方式实现策略,将业务逻辑与控制逻辑分离。
  3. Mock框架,帮助我们模拟对象的行为。有编写单元测试习惯的同学应该都使用过Moq,他也是基于castle组件开发的,快速帮助我们Mock返回值,解决了环境依赖的问题。

我们来看一下是示例,Caslte DynamicProxy 如何快速帮我们实现动态代理。

全局的日志捕获

    public class Main
    {
        public static void Create()
        {
            var proxyGenerator = new ProxyGenerator();
            IMachine target = new Machine();
            var machine = proxyGenerator.CreateInterfaceProxyWithTarget(target,
            new IInterceptor[]{new LogInterceptor()});
            machine.Buy(100);
        }
    }
    
    public class LogInterceptor: IInterceptor
    {
        public void Intercept(IInvocation invocation)
        {
            //日志before
            object[] invocationArguments = invocation.Arguments;
            invocation.Proceed();
            //日志after
            object invocationReturnValue = invocation.ReturnValue;
        }
    }

控制逻辑与业务代码分离

借助Polly + Castle 切面的方式实现控制层面的逻辑。

    public class FeiShuRestClientPolicyInterceptor : IInterceptor
    {
        private static readonly LogWrapper Logger = new LogWrapper();
        private readonly PolicyWrap<IRestResponse> _policyWrap;

        public FeiShuRestClientPolicyInterceptor()
        {
            //http:400, code:99991663 Token 失效
            _policyWrap = Policy.Wrap(RequestExceptionRetryPolicy(),
                LimitedRetryPolicy(1, 1));
        }
        
        /// <summary>
        /// 限频重试策略
        /// </summary>
        /// <param name="sleep"></param>
        /// <param name="retryTimes"></param>
        /// <returns></returns>
        public static RetryPolicy<IRestResponse> LimitedRetryPolicy(int sleep, int retryTimes)
        {
            var retryPolicy = Policy<IRestResponse>.HandleResult(restResponse =>
                    (int)restResponse.StatusCode == 429)
                .WaitAndRetry(retryTimes, i => TimeSpan.FromSeconds(sleep));
            return retryPolicy;
        }
        
        /// <summary>
        /// 网络瞬时故障的Retry
        /// </summary>
        /// <returns></returns>
        public static RetryPolicy<IRestResponse> RequestExceptionRetryPolicy()
        {
            var retryPolicy = Policy<IRestResponse>.HandleResult(restResponse =>
                {
                    //http 网络瞬时故障
                    // ReSharper disable once ConvertIfStatementToReturnStatement
                    if (InvalidStatus(restResponse))
                    {
                        return true;
                    }

                    return false;
                })
                .WaitAndRetry(3, i => TimeSpan.FromSeconds(1));
            return retryPolicy;
        }
        
        /// <summary>
        /// 异常网络状态条件
        /// </summary>
        /// <param name="response"></param>
        /// <returns></returns>
        public static bool InvalidStatus(IRestResponse response)
        {
            return response.ResponseStatus == ResponseStatus.TimedOut
                   || response.StatusCode >= HttpStatusCode.InternalServerError;
        }

        public void Intercept(IInvocation invocation)
        {
            invocation.BeforeLog();
            _policyWrap.Execute(() =>
            {
                invocation.Proceed();
                return invocation.ReturnValue as IRestResponse;
            });

            invocation.AfterLog();
        }
    }

接口Mock

Mock数据:Demo依赖IMachin,如果我们需要测试DoSomething的逻辑,那我们需要Mock一个IMachine的对象的Buy方法。

    public interface IMachine
    {
        bool Buy(double price);

        void Purchase(int count);
    }
    
    public class Demo
    {
        private readonly IMachine _machine;

        public Demo(IMachine machine)
        {
            _machine = machine;
        }

        public void DoSomething()
        {
            if (_machine.Buy(1))
            {
                //购买成功
            }
            else
            {
                //购买失败
            }
        }
    }

Mock返回值

    private static void MockDemo(ProxyGenerator proxyGenerator)
    {
        IMachine mockMachine = proxyGenerator.CreateInterfaceProxyWithoutTarget<IMachine>(new MockInterceptor(true));
            //true
            Console.WriteLine(mockMachine.Buy(1));
    }
        
    public class MockInterceptor : IInterceptor
    {
        private readonly bool _resultValue;

        public MockInterceptor(bool resultValue)
        {
            _resultValue = resultValue;
        }

        public void Intercept(IInvocation invocation)
        {
            //设置
            invocation.ReturnValue = _resultValue;
        }
    }

Tips

Use of a single ProxyGenerator's instance: If you have a long running process (web site, windows service, etc.) and you have to create many dynamic proxies, you should make sure to reuse the same ProxyGenerator instance. If not, be aware that you will then bypass the caching mechanism. Side effects are high CPU usage and constant increase in memory consumption.

通过ProxyGenerator创建代理对象,会有缓存机制,所以我们应该使用单例的ProxyGenerator对象。否则会导致CPU占用率高,内存消耗不断增加。