netcore 之动态代理(微服务专题)
动态代理配合rpc技术调用远程服务,不用关注细节的实现,让程序就像在本地调用以用。
因此动态代理在微服务系统中是不可或缺的一个技术。网上看到大部分案例都是通过反射自己实现,且相当复杂。编写和调试相当不易,我这里提供里一种简便的方式来实现动态代理。
1、创建我们的空白.netcore项目
通过vs2017轻易的创建出一个.netcore项目
2、编写Startup.cs文件
默认Startup 没有构造函数,自行添加构造函数,带有 IConfiguration 参数的,用于获取项目配置,但我们的示例中未使用配置
- public Startup(IConfiguration configuration)
- {
- Configuration = configuration;
- //注册编码提供程序
- Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);
- }
在ConfigureServices 配置日志模块,目前core更新的很快,日志的配置方式和原来又很大出入,最新的配置方式如下
- // For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940
- public void ConfigureServices(IServiceCollection services )
- {
- services.AddLogging(loggingBuilder=> {
- loggingBuilder.AddConfiguration(Configuration.GetSection("Logging"));
- loggingBuilder.AddConsole();
- loggingBuilder.AddDebug();
- });
- }
添加路由过滤器,监控一个地址,用于调用我们的测试代码。netcore默认没有UTF8的编码方式,所以要先解决UTF8编码问题,否则将在输出中文时候乱码。
这里注意 Map 内部传递了参数 applicationBuilder ,千万不要 使用Configure(IApplicationBuilder app, IHostingEnvironment env ) 中的app参数,否则每次请求api/health时候都将调用这个中间件(app.Run会短路期后边所有的中间件),
- app.Map("/api/health", (applicationBuilder) =>
- {
- applicationBuilder.Run(context =>
- {
- return context.Response.WriteAsync(uName.Result, Encoding.UTF8);
- });
- });
3、代理
netcore 已经为我们完成了一些工作,提供了DispatchProxy 这个类
- #region 程序集 System.Reflection.DispatchProxy, Version=4.0.4.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a
- // C:\Program Files\dotnet\sdk\NuGetFallbackFolder\microsoft.netcore.app\2.2.0\ref\netcoreapp2.2\System.Reflection.DispatchProxy.dll
- #endregion
- namespace System.Reflection
- {
- //
- public abstract class DispatchProxy
- {
- //
- protected DispatchProxy();
- //
- // 类型参数:
- // T:
- //
- // TProxy:
- public static T Create<T, TProxy>() where TProxy : DispatchProxy;
- //
- // 参数:
- // targetMethod:
- //
- // args:
- protected abstract object Invoke(MethodInfo targetMethod, object[] args);
- }
- }
这个类提供了一个实例方法,一个静态方法:
Invoke(MethodInfo targetMethod, object[] args) (注解1)
Create<T, TProxy>()(注解2)
Create 创建代理的实例对象,实例对象在调用方法时候会自动执行Invoke
首先我们创建一个动态代理类 ProxyDecorator<T>:DispatchProxy 需要继承DispatchProxy 。
在DispatchProxy 的静态方法 Create<T, TProxy>()中要求 TProxy : DispatchProxy
ProxyDecorator 重写 DispatchProxy的虚方法invoke
我们可以在ProxyDecorator 类中添加一些其他方法,比如:异常处理,MethodInfo执行前后的处理。
重点要讲一下
ProxyDecorator 中需要传递一个 T类型的变量 decorated 。
因为 DispatchProxy.Create<T, ProxyDecorator<T>>(); 会创建一个新的T的实例对象 ,这个对象是代理对象实例,我们将 decorated 绑定到这个代理实例上
接下来这个代理实例在执行T类型的任何方法时候都会用到 decorated,因为 [decorated] 会被传给 targetMethod.Invoke(decorated, args) (targetMethod 来自注解1) ,invoke相当于执行
这样说的不是很明白,我们直接看代码
- using System;
- using System.Collections.Generic;
- using System.Linq;
- using System.Linq.Expressions;
- using System.Reflection;
- using System.Text;
- using System.Threading.Tasks;
- namespace TestWFW
- {
- public class ProxyDecorator<T> : DispatchProxy
- {
- //关键词 RealProxy
- private T decorated;
- private event Action<MethodInfo, object[]> _afterAction; //动作之后执行
- private event Action<MethodInfo, object[]> _beforeAction; //动作之前执行
- //其他自定义属性,事件和方法
- public ProxyDecorator()
- {
- }
- /// <summary>
- /// 创建代理实例
- /// </summary>
- /// <param name="decorated">代理的接口类型</param>
- /// <returns></returns>
- public T Create(T decorated)
- {
- object proxy = Create<T, ProxyDecorator<T>>(); //调用DispatchProxy 的Create 创建一个新的T
- ((ProxyDecorator<T>)proxy).decorated = decorated; //这里必须这样赋值,会自动未proxy 添加一个新的属性
- //其他的请如法炮制
- return (T)proxy;
- }
- /// <summary>
- /// 创建代理实例
- /// </summary>
- /// <param name="decorated">代理的接口类型</param>
- /// <param name="beforeAction">方法执行前执行的事件</param>
- /// <param name="afterAction">方法执行后执行的事件</param>
- /// <returns></returns>
- public T Create(T decorated, Action<MethodInfo, object[]> beforeAction, Action<MethodInfo, object[]> afterAction)
- {
- object proxy = Create<T, ProxyDecorator<T>>(); //调用DispatchProxy 的Create 创建一个新的T
- ((ProxyDecorator<T>)proxy).decorated = decorated;
- ((ProxyDecorator<T>)proxy)._afterAction = afterAction;
- ((ProxyDecorator<T>)proxy)._beforeAction = beforeAction;
- //((GenericDecorator<T>)proxy)._loggingScheduler = TaskScheduler.FromCurrentSynchronizationContext();
- return (T)proxy;
- }
- protected override object Invoke(MethodInfo targetMethod, object[] args)
- {
- if (targetMethod == null) throw new Exception("无效的方法");
- try
- {
- //_beforeAction 事件
- if (_beforeAction != null)
- {
- this._beforeAction(targetMethod, args);
- }
- object result = targetMethod.Invoke(decorated, args);
- System.Diagnostics.Debug.WriteLine(result); //打印输出面板
- var resultTask = result as Task;
- if (resultTask != null)
- {
- resultTask.ContinueWith(task => //ContinueWith 创建一个延续,该延续接收调用方提供的状态信息并执行 当目标系统 tasks。
- {
- if (task.Exception != null)
- {
- LogException(task.Exception.InnerException ?? task.Exception, targetMethod);
- }
- else
- {
- object taskResult = null;
- if (task.GetType().GetTypeInfo().IsGenericType &&
- task.GetType().GetGenericTypeDefinition() == typeof(Task<>))
- {
- var property = task.GetType().GetTypeInfo().GetProperties().FirstOrDefault(p => p.Name == "Result");
- if (property != null)
- {
- taskResult = property.GetValue(task);
- }
- }
- if (_afterAction != null)
- {
- this._afterAction(targetMethod, args);
- }
- }
- });
- }
- else
- {
- try
- {
- // _afterAction 事件
- if (_afterAction != null)
- {
- this._afterAction(targetMethod, args);
- }
- }
- catch (Exception ex)
- {
- //Do not stop method execution if exception
- LogException(ex);
- }
- }
- return result;
- }
- catch (Exception ex)
- {
- if (ex is TargetInvocationException)
- {
- LogException(ex.InnerException ?? ex, targetMethod);
- throw ex.InnerException ?? ex;
- }
- else
- {
- throw ex;
- }
- }
- }
- /// <summary>
- /// aop异常的处理
- /// </summary>
- /// <param name="exception"></param>
- /// <param name="methodInfo"></param>
- private void LogException(Exception exception, MethodInfo methodInfo = null)
- {
- try
- {
- var errorMessage = new StringBuilder();
- errorMessage.AppendLine($"Class {decorated.GetType().FullName}");
- errorMessage.AppendLine($"Method {methodInfo?.Name} threw exception");
- errorMessage.AppendLine(exception.Message);
- //_logError?.Invoke(errorMessage.ToString()); 记录到文件系统
- }
- catch (Exception)
- {
- // ignored
- //Method should return original exception
- }
- }
- }
- }
代码比较简单,相信大家都看的懂,关键的代码和核心的系统代码已经加粗和加加粗+红 标注出来了
DispatchProxy<T> 类中有两种创建代理实例的方法
我们看一下第一种比较简单的创建方法
- // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
- public void Configure(IApplicationBuilder app, IHostingEnvironment env )
- {
- if (env.IsDevelopment())
- {
- app.UseDeveloperExceptionPage();
- }
- // 添加健康检查路由地址
- app.Map("/api/health", (applicationBuilder) =>
- {
- applicationBuilder.Run(context =>
- {
- IUserService userService = new UserService();
- //执行代理
- var serviceProxy = new ProxyDecorator<IUserService>();
- IUserService user = serviceProxy.Create(userService); //
- Task<string> uName = user.GetUserName();
- context.Response.ContentType = "text/plain;charset=utf-8";
- return context.Response.WriteAsync(uName.Result, Encoding.UTF8);
- });
- });
- }
代码中 UserService 和 IUserService 是一个class和interface 自己实现即可 ,随便写一个接口,并用类实现,任何一个方法就行,下面只是演示调用方法时候会执行什么,这个方法本身在岩石中并不重要。
由于ProxyDecorator 中并未注入相关事件,所以我们在调用 user.GetUserName(222) 时候看不到任何特别的输出。下面我们演示一个相对复杂的调用方式。
- // 添加健康检查路由地址
- app.Map("/api/health2", (applicationBuilder) =>
- {
- applicationBuilder.Run(context =>
- {
- IUserService userService = new UserService();
- //执行代理
- var serviceProxy = new ProxyDecorator<IUserService>();
- IUserService user = serviceProxy.Create(userService, beforeEvent, afterEvent); //
- Task<string> uName = user.GetUserName();
- context.Response.ContentType = "text/plain;charset=utf-8";
- return context.Response.WriteAsync(uName.Result, Encoding.UTF8);
- });
- });
- IUserService user = serviceProxy.Create(userService, beforeEvent, afterEvent); 在创建代理实例时候传递了beforEvent 和 afterEvent,这两个事件处理
函数是我们在业务中定义的
void beforeEvent(MethodInfo methodInfo, object[] arges)
{
System.Diagnostics.Debug.WriteLine("方法执行前");
}
void afterEvent(MethodInfo methodInfo, object[] arges)
{
System.Diagnostics.Debug.WriteLine("执行后的事件");
}
在代理实例执行接口的任何方法的时候都会执行 beforeEvent,和 afterEvent 这两个事件(请参考Invoke(MethodInfo targetMethod, object[] args) 方法的实现)
在我们的示例中将会在vs的 输出 面板中看到
方法执行前
“方法输出的值”
执行后事件
我们看下运行效果图:
这是 user.GetUserName(222) 的运行结果
控制台输出结果
netcore 之动态代理(微服务专题)的更多相关文章
- 【微服务专题之】.Net6下集成消息队列上-RabbitMQ
微信公众号:趣编程ACE关注可了解更多的.NET日常实战开发技巧,如需源码 请公众号后台留言 源码;[如果觉得本公众号对您有帮助,欢迎关注] .Net中RabbitMQ的使用 [微服务专题之].N ...
- netcore 中的动态代理与RPC实现(微服务专题)
一.关于RPC的调用 1. 调用者(客户端Client)以本地调用的方式发起调用: 2. Client stub(客户端存根)收到调用后,负责将被调用的方法名.参数等打包编码成特定格式的能进行网络传输 ...
- .netcore 3.1高性能微服务架构:webapi规范
1.1 定义 1.基础接口:单一职责原则,每个接口只负责各自的业务,下接db,通用性强. 2.聚合接口:根据调用方需求聚合基础接口数据,业务性强. 1.2 协议 1. 客户端在通过 API 与后端服务 ...
- java架构之路-(微服务专题)初步认识微服务与nacos初步搭建
历史演变: 以前我们都是一个war包,包含了很多很多的代码,反正我开始工作的时候做的就是这样的项目,一个金融系统,代码具体多少行记不清楚了,内部功能超多,但是实际能用到的不多,代码冗余超大,每次部署大 ...
- java架构之路-(微服务专题)nacos集群精讲实战
上次回顾: 上次博客,我们主要说了微服务的发展历程和nacos集群单机的搭建,单机需要-m standalone启动,集群建议使用nginx做一下反向代理,自行保证mysql和ngxin的高可用. 本 ...
- java架构之路-(微服务专题)ribbon的基本使用和内部算法的自我实现
上次回归: 上次我们主要说了,我们的注册中心nacos的使用,如我们的命名空间.分组.集群.版本等是如何使用的,如果是这样呢?我们现在有三个用户服务和三个订单服务,我们应该如何分发这些请求呢?都请求到 ...
- .netcore 3.1高性能微服务架构:封装调用外部服务的接口方法--HttpClient客户端思路分析
众所周知,微服务架构是由一众微服务组成,项目中调用其他微服务接口更是常见的操作.为了便于调用外部接口,我们的常用思路一般都是封装一个外部接口的客户端,使用时候直接调用相应的方法.webservice或 ...
- java架构之路-(微服务专题)feign的基本使用和nacos的配置中心
上次回归: 上次我们说了ribbon的基本使用,包括里面的内部算法,算法的细粒度配置,还有我们自己如何实现我们自己的算法,主要还是一些基本使用的知识,还不会使用ribbon的小伙伴可以回去看一下上一篇 ...
- .netcore 3.1高性能微服务架构:加入swagger接口文档
本文为原创文章:首发:http://www.zyiz.net/tech/detail-108663.html swagger是什么? Swagger 是一个规范和完整的框架,用于生成.描述.调用和可视 ...
随机推荐
- js,ts操作dom总结
以上面为例: js获取placeholder节点 : document.getElementsByClassName("newTicket")[0].getAttributeNod ...
- .NET Core CSharp初级篇 1-8泛型、逆变与协变
.NET Core CSharp初级篇 1-8 本节内容为泛型 为什么需要泛型 泛型是一个非常有趣的东西,他的出现对于减少代码复用率有了很大的帮助.比如说遇到两个模块的功能非常相似,只是一个是处理in ...
- C语言编程入门之--第三章编写第一个C语言程序
第三章 编写第一个C语言程序 导读:一般学一门计算机语言的第一堂上机课(“上机”顾名思义,上了计算机),就是往屏幕输出“hello world”,本章也不例外. 1.1 Hello,World! 这一 ...
- 洛谷P1003 题解
题面 思路一:纯模拟.(暴力不是满分) 思路: 1.定义一个二维数组. 2.根据每个数据给二维数组赋值. 3.最后输出那个坐标的值. 思路二(正规思路): 逆序找,因为后来的地毯会覆盖之前的,一发现有 ...
- 【JDK】JDK源码分析-TreeMap(2)
前文「JDK源码分析-TreeMap(1)」分析了 TreeMap 的一些方法,本文分析其中的增删方法.这也是红黑树插入和删除节点的操作,由于相对复杂,因此单独进行分析. 插入操作 该操作其实就是红黑 ...
- 【Java】Exception thrown by the agent : java.rmi.server.ExportException: Port already in use: 1099
详细信息如下: Error: Exception thrown by the agent : java.rmi.server.ExportException: Port already in use: ...
- 泥瓦匠 5 年 Java 的成长感悟(下)
继续<泥瓦匠 5 年 Java 的成长感悟(上)>,大致包括下面几点: 学技术的心态 学技术的学法 工作的心态 工作的硬技能 工作的软实力 听点雷子的民谣,我就安静地感概感概.上次说写的, ...
- poj 1068 模拟
题目链接 大概题意就是告诉你有个n个小括号,每一个")"左边有多少个"("都告诉你了,然后让你求出每一对括号之间有多少对括号(包含自己本身). 思路: 我先计算 ...
- 1. 源码分析---SOFARPC可扩展的机制SPI
这几天离职在家,正好没事可以疯狂的输出一下,本来想写DUBBO的源码解析的,但是发现写DUBBO源码的太多了,所以找一个写的不那么多的框架,所以就选中SOFARPC这个框架了. SOFARPC是蚂蚁金 ...
- 使用 OpenSSL 为 Nginx 创建自签名证书 并开启客户端身份验证
本文章默认读者了解Openssl,CA,网站证书相关知识,直接实战!配置完成后,浏览器会显示"安全的HTTPS"连接.不会像其他文章那样,是红色警告的证书提示. 准备环境 笔者使用 ...