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 是一个规范和完整的框架,用于生成.描述.调用和可视 ...
随机推荐
- 阅读nopcommerce startup源码
创建一个asp.net core项目,可以到到startup类有两个方法 // This method gets called by the runtime. Use this method to a ...
- poj 1205 :Water Treatment Plants (DP+高精度)
题意:有n个城市,它们由一个污水处理系统连接着,每个城市可以选择 1.将左边城市过来的污水和右边城市过来的污水连同本身的污水排到河里 >V< 2.将左边来的污水连同自己的污水排到右边 ...
- 重启iis的命令是什么?三种简单的重启方式
第一种.界面操作 打开“控制面板”->“管理工具”->“服务”.找到“IIS Admin Service” 右键点击“重新启动” 弹出 “停止其它服务” 窗口,点击“是”. 第二种.Net ...
- Broadcast 使用详解
极力推荐文章:欢迎收藏 Android 干货分享 阅读五分钟,每日十点,和您一起终身学习,这里是程序员Android 本篇文章主要介绍 Android 开发中的部分知识点,通过阅读本篇文章,您将收获以 ...
- 对数变换(一些基本的灰度变换函数)基本原理及Python实现
1. 基本原理 变换形式如下 $$T(r) = c\lg(r+1)$$ c为常数 由于对数函数的导数随自变量的增大而减小,对数变换将输入窄范围的低灰度值扩展为范围宽的灰度值和宽范围的高灰度值压缩为映射 ...
- 【算法】【排序】【插入类】希尔排序 ShellSort
#include<stdio.h> #include <time.h> #include<stdlib.h> int main(){ ]; //设立随机数 sran ...
- Spring Boot 修改静态资源一定要重启项目才会生效吗?未必!
回顾热部署 Spring Boot 中的热部署相信大家都用过吧,只需要添加 spring-boot-devtools 依赖就可以轻松实现热部署.Spring Boot 中热部署最最关键的原理就是两个不 ...
- Selenium+java - Ajax浮动框处理
Ajax浮动框 我们常遇到的某些网站首页输入框,点击后显示的浮动下拉热点,如下图: 实际案例 模拟场景如下: hao123首页搜索输入框,单击搜索框,点击浮动框中的哪吒票房破30亿,单击后选项的文字内 ...
- node命令行工具之实现项目工程自动初始化的标准流程
一.目的 传统的前端项目初始流程一般是这样: 可以看出,传统的初始化步骤,花费的时间并不少.而且,人工操作的情况下,总有改漏的情况出现.这个缺点有时很致命. 甚至有马大哈,没有更新项目仓库地址,导致提 ...
- android ——网络编程
一.WebView 这个View就是一个浏览器,用于展示网页的. 布局文件: <LinearLayout xmlns:android="http://schemas.android.c ...