惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

GbyAI
GbyAI
V
Visual Studio Blog
美团技术团队
L
LINUX DO - 最新话题
Last Week in AI
Last Week in AI
雷峰网
雷峰网
博客园_首页
腾讯CDC
博客园 - Franky
大猫的无限游戏
大猫的无限游戏
cs.CV updates on arXiv.org
cs.CV updates on arXiv.org
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
人人都是产品经理
人人都是产品经理
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
J
Java Code Geeks
W
WeLiveSecurity
Apple Machine Learning Research
Apple Machine Learning Research
Spread Privacy
Spread Privacy
博客园 - 聂微东
量子位
Recent Announcements
Recent Announcements
S
Schneier on Security
O
OpenAI News
PCI Perspectives
PCI Perspectives
H
Heimdal Security Blog
T
Tailwind CSS Blog
S
Security Affairs
Y
Y Combinator Blog
P
Privacy International News Feed
Hacker News: Ask HN
Hacker News: Ask HN
Stack Overflow Blog
Stack Overflow Blog
Application and Cybersecurity Blog
Application and Cybersecurity Blog
U
Unit 42
Webroot Blog
Webroot Blog
Vercel News
Vercel News
Simon Willison's Weblog
Simon Willison's Weblog
B
Blog RSS Feed
S
Secure Thoughts
Microsoft Azure Blog
Microsoft Azure Blog
N
Netflix TechBlog - Medium
V
V2EX
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
D
DataBreaches.Net
月光博客
月光博客
博客园 - 三生石上(FineUI控件)
L
LangChain Blog
S
Security @ Cisco Blogs
The Hacker News
The Hacker News
The GitHub Blog
The GitHub Blog
C
CERT Recently Published Vulnerability Notes

元视角

.NET 生态下的 Agent 框架选型:从 ReAct 到原生推理 - 元视角 从「能用」到「好用」:LLM 流式响应实现方式的探索之路 - 元视角 当我用 2000 条聊天记录,让 AI 为我画一幅自画像 - 元视角 基于 Supabase 的 AI 应用开发探索 - 元视角 微博 × MCP:社交媒体新玩法解锁 - 元视角 四点钟海棠花未眠 - 元视角 Semantic Kernel × MCP:智能体的上下文增强探索 - 元视角 基于 K-Means 聚类分析实现人脸照片的快速分类 - 元视角 容器技术驱动下的代码沙箱实践与思考 - 元视角 温故而知新:后端通用查询方案的再思考 - 元视角 浅议 CancellationToken 在前后端协同取消场景中的应用 - 元视角 Semantic Kernel 视角下的 Text2SQL 实践与思考 - 元视角 关于 ChatGPT 的流式传输,你需要知道的一切 - 元视角 RAG 的是与非、Rewrite 和 Rerank - 元视角 使用 EFCore 和 PostgreSQL 实现向量存储及检索 - 元视角 基于 LLaMA 和 LangChain 实践本地 AI 知识库 - 元视角 使用 llama.cpp 在本地部署 AI 大模型的一次尝试 - 元视角 如何为 Git 配置多个 SSH Key - 元视角 C# 使用 LibUsbDotNet 实现 USB 设备检测 - 元视角 基于 C# 实现样式与数据分离的打印方案 - 元视角 基于 SVG 的图形交互方案实践 - 元视角 前端视频播放技术概览 - 元视角 温故而知新,再话 Python 动态导入 - 元视角 后 GPT 时代,NLP 不存在了? - 元视角 视频是不能 P 的系列:使用 Milvus 实现海量人脸快速检索 - 元视角 GDI+下字体大小自适应方案初探 - 元视角 小爱音箱集成 ChatGPT 的不完全教程 - 元视角 程序员视角下的三体世界随想 - 元视角 关于 Docker 容器配置信息的渐进式思考 - 元视角 在 Docker 容器内集成 Crontab 定时任务 - 元视角 为你的服务器集成 LDAP 认证 - 元视角 似花还似非花 - 元视角 视频是不能 P 的系列:使用 Dlib 实现人脸识别 - 元视角 浅议分布式链路追踪与日志的整合 - 元视角 关于 Git 大文件上传这件小事 - 元视角 .NET 进程内队列 Channel 的入门与应用 - 元视角 使用 Fody 实现 .NET 的静态编织 - 元视角 .NET Core + ELK 搭建可视化日志分析平台(下) - 元视角 聊一聊前端图片懒加载背后的故事 - 元视角 支持外部链接跳转的 Vue Router 扩展实现 - 元视角 视频是不能 P 的系列:OpenCV 和 Dlib 实现表情包 - 元视角 不得不说的 ASP.NET Core 集成测试 - 元视角 再议 DDD 视角下的 EFCore 与 领域事件 - 元视角 Vue.js 前端项目容器化部署实践极简教程 - 元视角 再见,人间四月天 - 元视角 Python 图像风格化迁移助力画家梦想 - 元视角 利用 ASP.NET Core 中的标头传播实现分布式链路追踪 - 元视角 利用 gRPC 实现文件的上传与下载 - 元视角 七种武器:延迟队列的原理和实现总结 - 元视角 gRPC 流式传输极简入门指南 - 元视角 Envoy 集成 Jaeger 实现分布式链路追踪 - 元视角 浅议非典型 Web 应用场景下的身份认证 - 元视角 gRPC 借助 Any 类型实现接口的泛化调用 - 元视角 分布式丛林探险系列之 Redis 集群模式 - 元视角 分布式丛林探险系列之 Redis 主从复制模式 - 元视角 通过 Python 预测 2021 年双十一交易额 - 元视角 gRPC 搭配 Swagger 实现微服务文档化 - 元视角 SSL/TLS 加密传输与数字证书的前世今生 - 元视角 使用 Python 自动识别防疫健康码 - 元视角 你不可不知的容器编排进阶技巧 - 元视角 ASP.NET Core 搭载 Envoy 实现 gRPC 服务代理 - 元视角 再话 AOP,从简化缓存操作说起 - 元视角 ASP.NET Core 搭载 Envoy 实现微服务身份认证(JWT) - 元视角 ASP.NET Core 搭载 Envoy 实现微服务的监控预警 - 元视角 ASP.NET Core 搭载 Envoy 实现微服务的反向代理 - 元视角 ASP.NET Core gRPC 打通前端世界的尝试 - 元视角 EFCore 实体命名约定库:EFCore.NamingConventions - 元视角 ASP.NET Core gRPC 集成 Polly 实现优雅重试 - 元视角 ASP.NET Core gRPC 健康检查的探索与实现 - 元视角 ASP.NET Core gRPC 拦截器的使用技巧分享 - 元视角 SnowNLP 使用自定义语料进行模型训练 - 元视角 使用 HttpMessageHandler 实现 HttpClient 请求管道自定义 - 元视角 ABP vNext 的实体与服务扩展技巧分享 - 元视角 ABP vNext 对接 Ant Design Vue 实现分页查询 - 元视角 源代码探案系列之 .NET Core 跨域中间件 CORS - 元视角 源代码探案系列之 .NET Core 限流中间件 AspNetCoreRateLimit - 元视角 源代码探案系列之 .NET Core 并发限制中间件 ConcurrencyLimiter - 元视角 通过 EmbededFileProvider 实现 Blazor 的静态文件访问 - 元视角 低代码,想说爱你不容易 - 元视角 记一次失败的 ThoughtWorks 面试经历 - 元视角 从 C# 1.0 到 C# 9.0,历代 C# 语言特性一览 - 元视角 通过 Python 分析 2020 年全年微博热搜数据 - 元视角 基于 Python 和 Selenium 实现 CSDN 一键三连自动化 - 元视角 使用多线程为你的 Python 爬虫提速的 N 种姿势,你会几种? - 元视角 实现网页长截图的常见思路总结 - 元视角 温故而知新,由 ADO.NET 与 Dapper 所联想到的 - 元视角 视频是不能 P 的系列:OpenCV 人脸检测 - 元视角 作为技术宅的我,是这样追鬼滅の刃的 - 元视角 使用 Python 抽取《半泽直树》原著小说人物关系 - 元视角 厉害了!打工人用 Python 分析西安市职位信息 - 元视角 使用 dotTrace 对 .NET 应用进行性能分析与优化 - 元视角 一道 HashSet 面试题引发的蝴蝶效应 - 元视角 基于选项模式实现.NET Core 的配置热更新 - 元视角 Dapper.Contrib 在 Oracle 环境下引发 ORA-00928 异常问题的解决 - 元视角 .NET Core 中对象池(Object Pool)的使用 - 元视角 利用 MySQL 的 Binlog 实现数据同步与订阅(下):EventBus 篇 - 元视角 利用 MySQL 的 Binlog 实现数据同步与订阅(中):RabbitMQ 篇 - 元视角 利用 MySQL 的 Binlog 实现数据同步与订阅(上):基础篇 - 元视角 记一次从已损坏的 Git 仓库中找回代码的经历 - 元视角 .NET Core 原生 DI 扩展之属性注入实现 - 元视角
又见 AOP 之基于 RealProxy 实现 WCF 动态代理 - 元视角
飞鸿踏雪 · 2019-05-11 · via 元视角

最近一直在研究 Mongodb 和 ElasticSearch 之间同步数据的问题,苦于到目前为止,并没有取得任何实质性的进展。偶尔“趁得浮生半日闲暇”,看一看 Web API 设计方面的书籍,和前辈交流下项目中的历史遗留问题,最为直观的感受就是,这个世界上任何方案的最终落地,都经过理想和现实的无数次挣扎,比如我们希望迁移项目到.NET Core 平台上,初步分析大概有将近 1000 多个无法兼容的地方,维持现状固然可以保证整个项目的稳定,可如果真到了不得不升级的地步,面临的问题可能会越来越多,所谓“凡事预则立,不预则废”,早一点准备总是好的。既然说到里历史问题,那么,今天这篇文章就来说一说,基于 RealProxy 实现 WCF 动态代理。

故事背景

在我们的业务系统中,对内是使用 WCF 来进行相互通信的,而对外则是使用 Web API 来进行数据交换。关于 RPC 还是 REST 的争论由来已有,严格地来说,两者没有绝对的高下之分,从风格上而言,RPC 倾向于让接口映射到一个方法上,而 REST 则倾向于让接口映射到一个资源上。从我们实际的使用情况来看,REST 在系统中应用得并不是很完美,因为大多数情况下,我们实现的仅仅是 HTTP+JSON 这样一种协议组合,因此业务系统中存在着大量的 WCF 接口供系统内部调用。

内部服务调用示意图 内部服务调用示意图

最早的时候,是通过 T4 模板来生成针对某个接口的代理类,而代理类中通常封装了 ChannelFactory 的创建、释放等等 WCF 相关的代码,实际应用中还会对 WCF 接口的异常进行捕获、记录日志、统计调用时间等,因此早期的 T4 模板实际上承担了生成代理类的职责。虽然业务的不断推进,接口中加入的新方法越来越多,导致具体业务类中的代码越来越多,动辄出现单个文件中代码行数达 3000 行以上,与此同时,每当 WCF 接口中增加了新方法,就不得不在其相关的代理类中增加代理方法。坦白地讲,就是增加一个看起来差不多的方法,因为你依然要处理 ChannelFactory 的创建、释放、异常处理、日志记录等等的工作。

其实,WCF 可以直接生成客户端代码,因为每个 WCF 的服务都可以以 WebService 服务的形式暴露出来,而只要是 WebService,总可以通过 WSDL 生成一个代理类。不过这显然不利于团队间的协作,更不利于服务终结点配置的集中化,更失去了异常处理、日志记录等等这些“通用”工作的可能性。T4 应该可以基于“工作”,可显然大家觉得手写比生成要来得更容易些,所以,这个故事最终演变成这样一个局面,我们不得不通过局部类(Partial Class)的方式来开辟新的类文件。

系统中充斥着大量类似的代码 系统中充斥着大量类似的代码

那么,说了这么多,从一个历史遗留问题入手,它真正的痛点在哪里呢?在我看来,主要有两点:第一,是手写代理类的“此恨绵绵无绝期”,明明就是对接口的简单封装,看起来是增加一个代理方法,其实最多就是复制黏贴,因为代理方法的核心代码就是调用接口,而剩下的都是重复的“服务型”代码;第二,是异常处理、日志记录的“哀鸿遍野”,同核心代码交织在一起,一遍又一遍的“重复”,为什么不考虑让它统一地去处理呢?难道每个人都抄着同一段代码,这样就实现了某种意义上的复用吗?

RealProxy 介绍

既然像我这样懒惰的人,不愿意像别人一样手写代理类,那么我的思路又是什么呢?显然,从这篇文章的题目,你就可以看出,我这里要说的是动态代理,原来的代理类同样属于代理,它是在编译时期间生成了一个代理类,我们以为在调用这个代理类,可其实真正去工作的是 ChannelFactory,这种方式称之为“静态代理”。如果你了解过设计模式,应该会知道相对应的代理模式,这里不再展开开来讲这这个设计模式,可以明确的是,动态代理就是在运行时期间动态创建一个代理对象的实例,它可以完全模拟被代理对象的行为,而我们的目的,就是要和手写的代理类永远地说再见!

好了,下面隆重介绍本文的主角——RealProxy。相信大家一定听说过 AOP,即所谓的面向切面编程。它可以让我们在某一个所针对的横切面编程,并讲这种功能应用到所有相同的横切面上。譬如对方法级别的横切面增加拦截器,那么所有的方法都可以在执行前后具备相同的逻辑,典型的如日志记录、指定操作前的检验等等。而 RealProxy 类恰恰提供最基本的代理功能,它是一个抽象类,必须通过重写其 Invoke()方法并添加新功能来继承,该类位于 System.Runtime.Remoting.Proxies 命名空间中,通过重写 Invoke()方法,我们就可以在被代理对象调用前后插入相关逻辑,而通过 GetTransparentProxy()方法,则可以返回实际的代理对象。所以,通过这个原理,我们就可以在运行时期间,动态创建出指定类型的实例。这里,我们从一个简单的例子来开始,以帮助大家更好的理解 RealProxy。

public interface ICalculator
{
    double Add(double n1, double n2);
    double Subtract(double n1, double n2);
    double Multiply(double n1, double n2);
    double Divide(double n1, double n2);
}

public class CalculatorService : ICalculator
{
    public double Add(double n1, double n2)
    {
        return n1 + n2;
    }

    public double Subtract(double n1, double n2)
    {
        return n1 - n2;
    }

    public double Multiply(double n1, double n2)
    {
        return n1 * n2;
    }

    public double Divide(double n1, double n2)
    {
        return n1 / n2;
    }
}

首先,我们定义一个简单的接口 ICalculator,它含有加、减、乘、除四种基本运算,我们希望记录每个方法调用的参数、结果和执行时间,因此通过 RealProxy 对现有类型 CalculatorService 进行代理,并动态地创建代理对象来供调用方使用,下面给出关键代码:

public class CalculatorServiceProxy : RealProxy
    {
        private Server.Service.ICalculator _calculator;

        public CalculatorServiceProxy(Server.Service.ICalculator calculator) :
            base(typeof(Server.Service.ICalculator))
        {
            _calculator = calculator;
        }

        public override IMessage Invoke(IMessage message)
        {
            var methodCall = message as IMethodCallMessage;
            var methodInfo = methodCall.MethodBase as MethodInfo;
            var startTime = DateTime.Now;
            var serviceName = _calculator.GetType().Name;
            var methodName = methodInfo.Name;

            try
            {
                Console.WriteLine("调用{0}服务的{1}方法开始...", serviceName, methodName);
                var argsInfo = new Dictionary<string, object>();
                for (int i = 0; i < methodCall.ArgCount; i++)
                {
                    argsInfo.Add(methodCall.GetArgName(i), methodCall.Args[i]);
                }
                Console.WriteLine("当前传入参数:{0}", JsonConvert.SerializeObject(argsInfo));
                var result = methodInfo.Invoke(_calculator, methodCall.InArgs);
                if (result != null)
                    Console.WriteLine("当前返回值:{0}", JsonConvert.SerializeObject(result));
                return new ReturnMessage(result, null, 0, methodCall.LogicalCallContext, methodCall);
            }
            catch (Exception ex)
            {
                Console.WriteLine(
                    "调用{0}服务的{1}方法失败,失败原因:{2}", 
                    serviceName, methodName, ex.Message
                );
                throw ex;
            }
            finally
            {
                Console.WriteLine(
                    "调用{0}服务的{1}方法结束,共耗时{2}秒", 
                    serviceName, methodName, DateTime.Now.Subtract(startTime).TotalSeconds
                );
                Console.WriteLine("----------------------------------");
            }
        }
    }

可以注意到,最核心的代码是在 Invoke()方法中,在这里我们增加了我们想要的功能,但这些功能丝毫不会影响到 CalculatorService,当我们通过构造函数给 RealProxy 传入被代理对象后,它就会对被代理对象的特定方法进行拦截,这里实际上就是加、减、乘、除四个方法。OK,到现在为止,这些都是我们的想像而已,具体我们实现执行结果来看。

var serviceProxy = new CalculatorServiceProxy(new CalculatorService());
var calculator = (ICalculator)serviceProxy.GetTransparentProxy();
calculator.Add(12, 24);
calculator.Subtract(36, 10);
calculator.Multiply(12, 35);
calculator.Divide(36, 12);

现在,我们可以说,刚刚所说的一切都是真的,因为我们真的创建了一个 ICalculator 接口的实例,它真的记录了每个方法调用的参数、结果和执行时间。

RealPrxoy牛刀小试 RealPrxoy牛刀小试

WCF 动态代理

现在,我们来考虑 WCF,WCF 需要通过 ChannelFactory 来创建和释放,而这恰恰是代理类所做的事情,就像下面的代码一样,我们通常会把所有的 WCF 集中配置在一个地方,并通过构造 Binding 和终结点地址来创建一个 WCF 服务,在调用服务的过程中,会对调用时间、异常信息等进行记录,这其实和我举的第一个例子完全一致,那么我们能不能用 RealProxy 来实现这些功能呢?


public class ServiceInfo<TService>
{
    private readonly ChannelFactory _channelFactory;
    public ServiceInfo(ChannelFactory channelFactory)
    {
        _channelFactory = channelFactory;
    }

    public TService Service { get; set; }

    public void Close()
    {
        if (_channelFactory != null)
            _channelFactory.Close();
    }
    
}

private ServiceInfo<TService> FindService()
{
    ChannelFactory<TService> channelFactory = new ChannelFactory<TService>(_binding, _endpointAddress);
    var serviceInfo = new ServiceInfo<TService>(channelFactory);
    serviceInfo.Service = channelFactory.CreateChannel();
    return serviceInfo;
}

顺着这样的思路,如果我们可以把 ChannelFactory 注入到 RealProxy 中,就可以在接口调用过程中记录相关信息,这样我们就可以关注调用本身,因为所有的我们不想写的代码,现在全部都由代理类接管了,更重要的是,所有通过这种方式调用的 WCF 服务,都可以以一种统一而简洁的方式去处理,永远不用担心因为某个人忘记写代理方法而出现问题,下面给出整个实现的关键代码:

public class DynamicServiceProxy<TService> : RealProxy
{
    private readonly Binding _binding;
    private readonly EndpointAddress _endpointAddress;

    public DynamicServiceProxy(Binding binding, EndpointAddress endpointAddress)
        : base(typeof(TService))
    {
        _binding = binding;
        _endpointAddress = endpointAddress;
    }

    public DynamicServiceProxy(Binding binding, string serviceUrl)
        : this(binding, new EndpointAddress(serviceUrl))
    {

    }

    public override IMessage Invoke(IMessage message)
    {
        var serviceInfo = FindService();
        var methodCall = message as IMethodCallMessage;
        var methodInfo = methodCall.MethodBase as MethodInfo;
        var startTime = DateTime.Now;
        var serviceName = serviceInfo.Service.GetType().Name;
        var methodName = methodInfo.Name;

        try
        {
            Console.WriteLine("RealProxy调用{0}服务{1}方法开始...", serviceName, methodName);
            var argsInfo = new Dictionary<string, object>();
            for (int i = 0; i < methodCall.ArgCount; i++)
            {
                argsInfo.Add(methodCall.GetArgName(i), methodCall.Args[i]);
            }
            Console.WriteLine("RealProxy当前传入参数:{0}", JsonConvert.SerializeObject(argsInfo));
            var result = methodInfo.Invoke(serviceInfo.Service, methodCall.InArgs);
            if (result != null)
                Console.WriteLine("RealProxy当前返回值:{0}", JsonConvert.SerializeObject(result));
            return new ReturnMessage(result, null, 0, methodCall.LogicalCallContext, methodCall);
        }
        catch (Exception ex)
        {
            Console.WriteLine(
                "RealProxy调用{0}服务{1}方法失败,失败原因:{2}", 
                serviceName, methodName, ex.Message
            );
            throw ex;
        }
        finally
        {
            serviceInfo.Close();
            Console.WriteLine(
                "调用{0}服务{1}方法结束,共耗时{2}秒", 
                serviceName, methodName, DateTime.Now.Subtract(startTime).TotalSeconds
            );
            Console.WriteLine("----------------------------------");
        }
    }
}

对于 WCF 服务端的实现,我们依然使用 ICalculator 这个接口,需要注意的是为其添加[ServiceContract]和[OperationContract]标签,在这个例子中,我们共有 CalculatorService 和 MessageService 两个服务,为了简化这个实例,我们采用 BasicHttpBinding 的方式进行绑定,并为其指定各自的终结点地址。可以注意到,现在我们的动态代理实现了和原来代理类一样的效果。

var binding = new BasicHttpBinding();
var serviceUrl = "http://localhost:8502/Calculator.svc";
var calculator = ServiceProxyFactory.CreatePorxy<Server.Service.ICalculator>(binding, serviceUrl);

通过RealPrxoy动态代理WCF服务 通过RealPrxoy动态代理WCF服务

在调用 WCF 的时候,因为超时、网络等原因造成的调用异常,此时,我们可以为 WCF 添加异常处理相关的标签,而相应地,我们可以在异常中对异常的种类进行判断和处理,以便于及时地关闭 ChannelFactory,因为如果它不能正确地关闭,会导致后续的通信出现问题,而这恰好是当初的代理类想要解决的问题,考虑到创建 ChannelFactory 是需要付出一定的性能代价的,因此,可以适当地考虑对 ChannelFactory 进行缓存,而这恰好是原来业务中的一个盲点。

Castle.DynamicProxy

通过 RealProxy,我们已经实现了 WCF 服务的动态代理,这里介绍第二种方式,即 Castle.DynamicProxy,Castle 和 AspectCore、Unity 等项目一样,提供了 AOP 相关的能力,可以让我们对接口、虚方法、类等进行拦截。Castle 中的动态代理使用的是透明代理,而.NET Remoting 的动态代理必须继承自 MarshalByRefObject。博主暂时没有搞清楚,这两种是否属于同一种技术上的实现,作为延伸,我们来一起看看如何使用 Castle 中的 DynamicProxy 实现类似的功能,首先我们定义一个拦截器,它需要实现 IInterceptor 接口中的 Intercept()方法:

public void Intercept(IInvocation invocation)
{
    var serviceInfo = FindService();
    var methodInfo = invocation.Method;
    var startTime = DateTime.Now;
    var serviceName = serviceInfo.Service.GetType().Name;
    var methodName = methodInfo.Name;

    try
    {
        Console.WriteLine("CastleProxy调用{0}服务{1}方法开始...", serviceName, methodName);
        var argsInfo = new Dictionary<string, object>();
        var parameters = methodInfo.GetParameters();
        for (int i = 0; i < invocation.Arguments.Length; i++)
        {
            argsInfo.Add(parameters[i].Name, invocation.Arguments[i]);
        }
        Console.WriteLine("当前传入参数:{0}", JsonConvert.SerializeObject(argsInfo));
        var result = methodInfo.Invoke(serviceInfo.Service, invocation.Arguments);
        if (result != null) { 
        Console.WriteLine("当前返回值:{0}", JsonConvert.SerializeObject(result));
            invocation.ReturnValue = result;
        }
    }
    catch (Exception ex)
    {
        Console.WriteLine(
            "CastleProxy调用{0}服务{1}方法失败,失败原因:{2}", 
            serviceName, methodName, ex.Message
        );
        throw ex;
    }
    finally
    {
        serviceInfo.Close();
        Console.WriteLine(
            "CastleProxy调用{0}服务{1}方法结束,共耗时{2}秒", 
            serviceName, methodName, DateTime.Now.Subtract(startTime).TotalSeconds
        );
        Console.WriteLine("----------------------------------");
    }
}

接下来,我们通过 ProxyGenerator 来生成新的代理类,我们需要告诉 ProxyGenerator 要创建的类型是什么,是一个接口还是类,以及要应用哪一个拦截器。这里我们用到的方法是 CreateInterfaceWithoutTarget(),它在这里的作用就是动态创建 ICalculator 接口的代理类。而通过查看 Castle 的 API,我们会发现它可以在以下几种情况下创建某个类型的实例。首先是 CreateInterfaceWithoutTarget()这个方法,当你希望创建一个接口的代理而又不想提供具体的实现时可以使用。其次是 CreateInterfaceProxyWithTarget()这个方法,当你希望创建一个接口的代理同时又有提供具体实现时使用可以使用。接下来,是 CreateInterfaceProxyWithTargetInterface()这个方法,它的命名看起来让人感到迷惑,甚至在某种角度来看,它和 CreateInterfaceProxyWithTarget()这个方法还有点相似,其实。这两者最大的不同就是:后者允许你将调用目标替换为目标接口的不同实现。这种在实际场景中使用得不多,从 Castle 官方的使用场景来看,唯一用到这种技术的是 Castle.Facilities,它可以和 Windsor 这样的容器整合在一起使用,这个时候调用者就可以把 WCF 服务当作一个普通接口来使用,果然,大家都想到这一点,英雄所见略同啊,哈哈。好了,下面我们来看具体的代码实现:

ProxyGenerator generator = new ProxyGenerator();
var interceptor = new CastleServicePorxy<ICalculator>(binding, serviceUrl);
var calculator = (ICalculator)generator.CreateInterfaceProxyWithoutTarget(typeof(ICalculator),interceptor);

迁移至.NET Core

其实,我对 WCF 是不太感冒的,因为第一个字母 W 表明,它是一个只能运行在 Windows 平台的产物,现在依然有大量的 Web Service 存在,如果可以让我像使用普通接口一样使用 WCF 接口,我还是非常愿意去使用它的,毕竟系统中有大量依赖 WCF 的东西。可话又说回来,现在到.NET Core 这个版本,微软并没有把 WCF 的服务端移植到.NET Core 上,仅仅是提供了客户端调用的支持,或许还是因为 WCF 里有太多平台相关的东西吧!如果希望自己的.NET 应用可以跨平台,越早摆脱这些 Windows 平台东西越好,譬如 IIS、SQLServer 等等。不过我这里想说的是,RealProxy 在.NET Core 中有类似的实现,我们可以用下面这种方式来进行迁移,当然,如果你直接 Castle 就更没有问题啦!

public class InvokeSerice
{
    public static T Proxy<T>()
    {
        return DispatchProxy.Create<T, InvokeProxy<T>>();
    }
}

public class InvokeProxy<T> : DispatchProxy
{
    private Type type = null;
    public InvokeProxy()
    {
        type = typeof(T);
    }

    protected override object Invoke(MethodInfo targetMethod, object[] args)
    {
        //TODO: 在这里实现拦截逻辑
    }
}

本文小结

这篇博客再次让大家领略了 AOP 的魅力,通过动态代理来创建相关的服务接口,让我们逐渐摆脱了手写代理类的深渊。本文主要分享了两种动态代理的实现方式,一种是基于.NET Remoting 的 RealProxy,一种是基于 Castle 的 DynamicProxy。两种方式在使用上是非常相近的,通过这种方式。我们实现了 WCF 服务创建细节的隐藏,调用者不再需要去关心 ChannelFactory 相关的底层细节,可以像使用普通接口一样调用 WCF 服务,并且可以用一种统一的方式去记录调用相关的细节、对异常进行处理等等。早期的 T4 模板本质上是一种静态代理的方式,其缺点是难以适应快速迭代的变化,必须人手编写代理方法,而通过动态代理,这一切只需要写一次就好了,从而做到了真正意义上的“一次编写,到处运行”,这就是所谓的面向横切面编程的思路。关于 Castle 动态代理更多的应用场景,以及 Castle.Facilities 相关的内容,大家可以从各自的文档中去了解,以上就是这篇博客的全部内容了。