动态代理配合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. 关于Hack术语方面

    1.肉鸡       所谓“肉鸡”是一种很形象的比喻,比喻那些可以随意被我们控制的电脑,对方可以是WINDOWS系统,也可以是UNIX/LINUX系统,可以是普通的个人电脑,也可以是大型的服务器,我们 ...

  2. manjaro配置记录

    一.换源 官方镜像源(包括 core, extra, community, multilib ) sudo pacman-mirrors -i -c China -m rank //更新镜像排名 su ...

  3. spark 源码分析之九--Spark RPC剖析之StreamManager和RpcHandler

    StreamManager StreamManager类说明 StreamManager 官方说明如下: The StreamManager is used to fetch individual c ...

  4. .NET Core 3.0之深入源码理解HttpClientFactory(二)

      写在前面 上一篇文章讨论了通过在ConfigureServices中调用services.AddHttpClient()方法,并基于此进一步探讨了DefaultHttpClientFactory是 ...

  5. java连接mysql数据库jdbc

    jdbc.driver = com.mysql.jdbc.Driverjdbc.url = jdbc:mysql://localhost:3306/数据库名jdbc.username = rootjd ...

  6. 原生应用使用cordova并与h5应用分离

    个人原创地址:https://www.jianshu.com/p/1ad536e76640 1.需求与使用场景   打开一个新页面,要求能够加载本地zip格式的h5应用,该应用使用了某些原生能力:能够 ...

  7. css3系列之transform详解translate

    translate translate这个参数的,是transform 身上的,那么它有什么用呢? 其实他的作用很简单,就是平移,参考自己的位置来平移 translate() translateX() ...

  8. (转)Linux LVM逻辑卷配置过程详解(创建、扩展、缩减、删除、卸载、快照创建)

    一.预备知识 LVM全称为Logical Volume Manager 逻辑卷管理器,LVM是Linux环境中对磁盘分区进行管理的一种机制,是建立在硬盘和分区之上.文件系统之下的一个逻辑层,可提高磁盘 ...

  9. 喜大普奔 | 微信小程序支持PC端打开了

    微信小程序可以在PC端打开啦 微信PC版发布了v2.7.0测试版,其中一个重磅的功能就是:支持打开聊天中分享的小程序 咖啡君这么喜欢尝鲜的人自然是在第一时间下载进行了体验 安装成功,会有功能更新说明 ...

  10. SQL Labs刷题补坑记录(less1-less30)

    补坑加1,这几天快速刷一下sqllabs 来巩固下sql注入基础吧,也算是把很久以前没刷的过一遍,do it! 第一部分: LESS1: 直接报错,有回显的注入, http://localhost/s ...