动态代理配合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 之动态代理(微服务专题)的更多相关文章

  1. 【微服务专题之】.Net6下集成消息队列上-RabbitMQ

    ​ 微信公众号:趣编程ACE关注可了解更多的.NET日常实战开发技巧,如需源码 请公众号后台留言 源码;[如果觉得本公众号对您有帮助,欢迎关注] .Net中RabbitMQ的使用 [微服务专题之].N ...

  2. netcore 中的动态代理与RPC实现(微服务专题)

    一.关于RPC的调用 1. 调用者(客户端Client)以本地调用的方式发起调用: 2. Client stub(客户端存根)收到调用后,负责将被调用的方法名.参数等打包编码成特定格式的能进行网络传输 ...

  3. .netcore 3.1高性能微服务架构:webapi规范

    1.1 定义 1.基础接口:单一职责原则,每个接口只负责各自的业务,下接db,通用性强. 2.聚合接口:根据调用方需求聚合基础接口数据,业务性强. 1.2 协议 1. 客户端在通过 API 与后端服务 ...

  4. java架构之路-(微服务专题)初步认识微服务与nacos初步搭建

    历史演变: 以前我们都是一个war包,包含了很多很多的代码,反正我开始工作的时候做的就是这样的项目,一个金融系统,代码具体多少行记不清楚了,内部功能超多,但是实际能用到的不多,代码冗余超大,每次部署大 ...

  5. java架构之路-(微服务专题)nacos集群精讲实战

    上次回顾: 上次博客,我们主要说了微服务的发展历程和nacos集群单机的搭建,单机需要-m standalone启动,集群建议使用nginx做一下反向代理,自行保证mysql和ngxin的高可用. 本 ...

  6. java架构之路-(微服务专题)ribbon的基本使用和内部算法的自我实现

    上次回归: 上次我们主要说了,我们的注册中心nacos的使用,如我们的命名空间.分组.集群.版本等是如何使用的,如果是这样呢?我们现在有三个用户服务和三个订单服务,我们应该如何分发这些请求呢?都请求到 ...

  7. .netcore 3.1高性能微服务架构:封装调用外部服务的接口方法--HttpClient客户端思路分析

    众所周知,微服务架构是由一众微服务组成,项目中调用其他微服务接口更是常见的操作.为了便于调用外部接口,我们的常用思路一般都是封装一个外部接口的客户端,使用时候直接调用相应的方法.webservice或 ...

  8. java架构之路-(微服务专题)feign的基本使用和nacos的配置中心

    上次回归: 上次我们说了ribbon的基本使用,包括里面的内部算法,算法的细粒度配置,还有我们自己如何实现我们自己的算法,主要还是一些基本使用的知识,还不会使用ribbon的小伙伴可以回去看一下上一篇 ...

  9. .netcore 3.1高性能微服务架构:加入swagger接口文档

    本文为原创文章:首发:http://www.zyiz.net/tech/detail-108663.html swagger是什么? Swagger 是一个规范和完整的框架,用于生成.描述.调用和可视 ...

随机推荐

  1. Ubuntu下Mongo的安装和笔记

    在linux下的安装 打开https://www.mongodb.com/download-center#community选择linux然后选择自己的Version复制DOWNLOAD旁边的链接 打 ...

  2. SparkStreaming对接rabbitMQ

    /** * SparkStreaming对接rabbitmq java代码 */public class SparkConsumerRabbit { public static void main(S ...

  3. Core CLR Host 源码简单分析

    在定制 CLR Host的时候,可以通过调用如下代码,来获取当前需要被宿主的程序调用入口: hr = Host->CreateDelegate( domainId, L"Main,Ve ...

  4. NDK jni mk文件 so文件 巴啦啦 初体验

    概念JNI(Java Native Interface,Java本地接口),实现了Java和其他语言的交互(主要是C/C++),如:Java程序通过JNI调用C/C++编写的在Windows上运行的D ...

  5. PYNQ上手笔记 | ① 启动Pynq

    现在人工智能非常火爆,一般的教程都是为博硕生准备的,太难看懂了,分享一个非常适合小白入门的教程,不仅通俗易懂而且还很风趣幽默,点☞这里​​​​​​​☜进入传送门~ = = = = 我是华丽的分割线 = ...

  6. Flink 源码解析 —— 源码编译运行

    更新一篇知识星球里面的源码分析文章,去年写的,周末自己录了个视频,大家看下效果好吗?如果好的话,后面补录发在知识星球里面的其他源码解析文章. 前言 之前自己本地 clone 了 Flink 的源码,编 ...

  7. Go中的字符串使用----strings和strconv

    Go中的字符串操作 字符串是工作中最常用的,值得我们专门的练习一下.在Go中使用strings包来操作字符串,这也是内置的包哈,不像Java中要么手写,要么引入common-lang 或者 别的第三方 ...

  8. 超全的 Vue 开源项目合集,签收一下

    超全的 Vue 开源项目合集,签收一下 xiaoge2016 前端开发 1周前 作者:xiaoge2016 链接: https://my.oschina.net/u/3018050/blog/2049 ...

  9. 转载 | Sublime Text3 安装以及初次配置

    本文引自:http://blog.csdn.net/u011272513/article/details/52088800 工具:官网下载:Sublime Text3 安装:直接运行安装.http:/ ...

  10. 【0807 | Day 10】字符编码以及Python2/3编码的区别

    一.计算机基础 计算机组成:CPU.内存.硬盘 CPU:控制程序运行(从内存中取出文本编辑器的数据存入内存) 内存:运行程序 硬件:存储数据 二.文本编辑器存取文件的原理 比如计算机只能识别0和1,文 ...