用 Natasha 写个类型调用的架子

一、想法

自上篇文章,我一直琢磨整个好点的例子来展示 Natasha 动态编程能力, 于是就写了一个简单的类型调用的架子,耗时40分钟左右,  项目地址:https://github.com/NMSAzulX/TypeCaller

二、功能特点

a)、简单的注入功能

  • 支持无参构造注入

  • 支持递归注入

  • 保证原生性能

b)、方法映射与扫描

  • 仅扫描当前程序集

  • 扫描继承自 BaseRpc 类的 Rpc 路由

  • 使用类名+方法名与动态委托形成映射

  • 在当前生命周期使用静态 AOP 类包裹方法

c)、对接与调用    

  • 使用 params object[] 来对接调用参数

  • 使用 object 作为返回值

三、功能点解析

使用代码预览:

在请求达到后,Caller 根据路由执行委托,在委托中:

1、程序首先 new 了一个 RPC 实例

2、紧接着初始化实例中注入的接口

3、然后实例调用了路由中指向的方法(下文中 RPC 实例统称为”路由“)

4、最后返回结果。

注入(初始化)实现

由于需要有递归的构建,因此需要 instanceName 来记录上一次递归出来的对象名。而上一次构建的代码段则放在 script 中。 

public static (StringBuilder script, string instanceName) HandlerCtor(Type type,int deepth = 0)

返回值中的 script 为初始化代码,例如:

var instance = new XXXRpc();
instance._xxService = new DefaultXXXSerivce();

由于 service 中可能还有其他 service, 因此需要递归下去:

第二次递归

var instance1 = new DefaultXXXSerivce();
instance1._xxService = new DefaultXXXSerivce2();var instance = new XXXRpc();
instance._xxService = instance1;  

第三次递归

var instance2 = new DefaultXXXService3();var instance1 = new DefaultXXXSerivce();
instance1._xxService = new DefaultXXXSerivce2();
instance1._xxService = instance2;var instance = new XXXRpc();
instance._xxService = instance1;

注意:这里我们用的初始化方法都是针对 Readonly 字段,实际上真实脚本并不是上面那样,这里只是为了展示代码逻辑。

这段代码展示了初始化脚本对注入类型和非注入类型的实例化处理:

if (_injectionMapping.ContainsKey(type))
{//对注入的接口/抽象类等类型使用已实现的类型进行实例化ctorBuilder.Append($"var {instance} = new {_injectionMapping[type].GetDevelopName()}();");
}else
{//对非注入类型直接 newctorBuilder.Append($"var {instance} = new {type.GetDevelopName()}();");
}

下面这段代码遍历了当前类的只读字段,如果遇到了只读字段则发生以下处理:

  • 触发递归,继续生成实例化代码。

  • 给只读字段赋值

foreach (var item in fields)
{var result = HandlerCtor(item.FieldType, deepth);if (result != default){ctorBuilder.Insert(0, result.script);var readonnlyFieldScript = $"{instance}.{item.Name}".ReadonlyScript();ctorBuilder.Append($"{readonnlyFieldScript}={result.instanceName};");}
}

HelloRpc 为路由和方法的载体,继承 BaseRpc 以便被扫描

public class HelloRpc : BaseRpc
{private readonly IHelloServices _helloServices;public string GetHello(string name){return _helloServices.GetHello(name);}
}

下面便是对参数转换和方法调用的处理,需要反射的技术,如果还不清楚,可以参考我们公众号【 NCC开源社区 】痴良的反射系列文章;

NDelegate.RandomDomain(item=> { item.LogSyntaxError() //如果语法构建出错,则记录日志 .UseFileCompile(); //将结果编译到 DLL 文件中
})
.SetClass(item=>item.AllowPrivate(type)) 
.Func<object[], object>(
@$"
//实则这里是个 stringbuilder 来自于对参数的处理
//这里我简化成能看懂的代码
var name = (string)arg[0];{刚才处理的初始化字符串}// AOP Before 调用
if(Aop<{type.GetDevelopName()}>.Before.TryGetValue(""{method.Name}"", out var beforeFunc)){{ beforeFunc(instance,arg); }}//调用 路由Rpc.Method(arg)
var result = instance.{method.Name}({methodParametersScript});// AOP After调用
if(Aop<{type.GetDevelopName()}>.After.TryGetValue(""{method.Name}"", out var afterFunc)){{ afterFunc(instance,arg); }}return result;"
);

准备 Service 

DefaultTypeService 类自己就能实例化,如果这个类你懒得在代码里手动写它的实例化,可以通过  FrameworkService.AddInjection<DefaultTypeService>();  注入进去。

public class DefaultTypeService
{public virtual void Show(){Console.WriteLine("Run : In TypeService! Means : Dependency injection Succeed! ");}
}

DefaultHelloService 实现了 IHelloServices 抽象类或者接口,通过 FrameworkService.AddInjection<IHelloServices, DefaultHelloService>(); 注入进去。

public abstract class IHelloServices{protected readonly DefaultTypeService _typeService;public abstract string GetHello(string name);}
public class DefaultHelloService : IHelloServices
{public override string GetHello(string name){_typeService.Show();System.Console.WriteLine("Run : Contact 'Hello' and {Parameter}!");return "Hello " + name;}
}

用 ILSpy 查看 Natasha 生成的动态映射方法:

运行:xx.exe Hello.GetHello  "1"

四、功能扩展

上面的例子有点过于简单,这里我从几个角度来扩展一下,看看 Natasha 还能为它做些什么:

注入功能:

  • 无配置

  • 无区分生命周期

  • 无域隔离

  • 无热拔管理

生命周期以及域隔离与回收,增加了编程的维度,配置可以让注入规则更加多样化。从生命周期的维度来讲,增加该维度可以让对象的创建与回收可控,对作用域有帮助,对提升性能和减小内存开销有一定的好处。域隔离则更是让插件编程放肆起来,结合域回收与创建,我们可以实现在不重启的情况下,更换方法依赖的插件,从而改变执行结果。若使用域隔离的回收,你要搜集关于该域的所有引用,只有移除引用才能回收,从而实现热拔 。

路由映射:

  • 无热拔

  • 未支持插件程序集扫描

热拔同上不细说了,插件程序集扫描可以根据开发者加载的 DLL,扫描符合 BaseRpc 的路由类型,动态编译到路由映射字典中,实现热插。

上下文与调用链:

调用链可以满足中间件的需求,添加认证,静态资源,权限校验,监控等功能模块,另外主链与旁链的处理也是必不可少的功能,这里参照 ASP.NET CORE 的实现即可。

五、性能优化

该示例虽然已经可以满足高性能要求,但比起极致还远远不足。

注入方面

        幂等方法注入:某些类的方法满足幂等性,考虑是否可以使用单例对像与其进行映射,从而减少内存开销和对象创建的时间。 

        按需注入:虽然全网我都没听说过按需注入的功能,但想了一下可以实现,通过空引用异常或反编译我们可以对映射方法进行多次优化编译,从而达到按需注入的功能,例如:在不需要 ServiceA 的方法中,初始化代码则不会对 _aService 字段进行赋值和初始化,可能有人会说如果你检测不出来怎么办,检测不出来也不影响你使用。

        注入对象的AOP :  注入的对象可以通过代理方式实现 AOP ,参见下面的代理AOP.

        对象池:针对注入字段较多且可池化的对象,可以采用对象池进行存储,当然了对象池用处不仅仅在这里用,其他场景也会用到。

高速分发

真正的 RPC 需要对接网络层,在协议的约束下我们在拿到路由的时候可以以 比特数组 / 字符串等方式作为寻址依据,找到与其映射的方法并调用,高速映射实现的方式有多种,比如 .NET的并发字典,只读并发字典,Trie 结构,我和小曾写的查找树变种等等。

更高效的委托执行

还在调研中,如果你已经了解该技术要点,欢迎贡献,真的感激不尽。

强类型参数

我们例子中的参数使用了 Object 类型,拆装箱肯定是有性能损耗,这点可以从序列化层面去解决,在路由解析完之后,把参数部分的 byte[] span 传入动态映射的方法中,内部对其做强类型的反序列化操作,并直接传给被调用的实例方法做参数。原来的映射方法 Func<object[], object> 变成 Func<byte[], byte[]> 这里没打错,你序列化进来,再序列化出去,如果你有上下文的设计,还可以自定义一些返回值,然后与尾部的序列化方法做对接。

代理AOP

例子中的 AOP 实现是用了静态泛型类加上并发字典 ( Aop<rpc>.After["method"]()),实际上我们可以把 HelloRpc 的 AOP 进行静态类的代理, 比如动态的创建 static class StaticAopHelloRpc ,并把 Before 及 After 方法全部换成和原函数的强类型实现 StaticAopHelloRpc.Beforexxx() ,伪代码例如:

此时 AOP 的方法需要我们手写代码或者动态脚本编译进去,可以用我的 RuntimeToDynamic 库,R2D库可以让运行时的数据压入到动态域中,可以放在静态类、也可以放在普通类中等待调用,而且是强类型。(其实我原本没想把这个库推出来,但实在想不到有比这个更直接的方法了, 这只是一个建议,希望老友们有更好的方法)

代理类合并

代理类合并, 我们可能在动态构建的过程中产生很多的类,这些类在后期可以被整合与优化,减少调用路径。该优化可以先期考虑进去,这关系到你动态构建的一个习惯,如果你的逻辑不是那么强,也可以放在后期去做优化。

选其他组件

如果以上都完成了,性能就优化得差不多了,下面选一些组件,初步打通远程调用:

通信组件: 老江的 SuperSocket 高性能易用,内置了加密和协议解析等。

序列化组件:牛逼哥的 Swifter.Json 。

就此一个模棱两可的 RPC 就差不多能跑了,后续根据反馈或者需求逐一进行优化,使用 Natasha 对请求、调用、返回整个流程进行动态化管理是一件很刺激的事情,甚至需要持久化的支持。当然了 Natasha 还有很多别的用处,比如对象映射,ORM,奇奇怪怪的调用 等等,Natasha 属于 “正向编程” , 即便你没有看相关的源码,也可能写出满足你需求的框架。

六、鸣谢

Natasha 能做到以上那么多离不开黑科技的加持。

感谢 ”天天向上卡索“, 提供了 禁断低版本程序集 的编译标识,借此我开放了 Roslyn 未开放的一些标识与方法,卡索老铁为人低调谦和,名下还有很多有趣的项目,大家多多支持。

感谢 ”牛逼哥“, Readonly 初始化后赋值的方法和委托执行性能提升的信息是由他提供的。 这里我想多说几嘴,此人及其恐怖,6月份拿 Emit 实现的查找算法硬刚我的动态高速缓存,虽然不知道他写了多少代码,但我知道 Natasha 输了1项,就很恐怖,我们群里也有说,Json.net 作者”遭遇“了日本的卡哇伊,日本的卡哇伊”遭遇“了中国的牛逼哥。在看到 Swifter 性能测试结果时真的为他高兴一把,力压群雄,干得漂亮。

七、开源生态

很多情况下性能,易用性,稳定性是一起进步的,因为我们没能做到极致,这时候跟别的语言比反而显得有点急功近利。后浪们要多关注技术,多实践,别总做伸手党,就这些框架分分钟不就支棱起来么。有一部分大佬也是,愿意站在山头磨磨唧唧讲故事,车轱辘话转来转去不挑干货讲,在不就是上来否定这个否定那个,在弱势生态里,都是弱逼,别做生态的局外人,不能置身事外。

反观一下今年上半年,开源项目多起来了,质量也在慢慢提高,不得不说,部分国产库做的要比国外的强得多,这是个很好的趋势!老铁们,每天拿出一点时间来给技术,路上多积累一些灵感,该支棱就得支棱起来,相信自己,能行啊!  

如果奇迹有颜色,那一定是中国红!


https://github.com/dotnetcore

打赏一杯酒,削减三分愁。
跟着我们走,脱发包你有。

组织打赏账户为柠檬的账户,请标注「NCC」,并留下您的名字,以下地址可查看收支明细:https://github.com/dotnetcore/Home/blob/master/Statement-of-Income-and-Expense.md

OpenNCC,专注.NET技术的公众号

https://www.dotnetcore.xyz

微信ID:OpenNCC

长按左侧二维码关注

欢迎打赏组织

给予我们更多的支持

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/308674.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Natasha v4.0.0.0 动态编程新篇章

一、简介Natasha 基于 Roslyn 的 C# 动态程序集构建库&#xff0c;该库允许开发者在运行时使用 C# 代码构建域 / 程序集 / 类 / 结构体 / 枚举 / 接口 / 方法等&#xff0c;使得程序在运行的时候可以增加新的模块及功能。Natasha 集成了域管理/插件管理&#xff0c;可以实现域隔…

[RabbitMQ]RabbitMQ概念_四大核心概念

RabbitMQ RabbitMQ 的概念 RabbitMQ 是一个消息中间件&#xff1a;它接受并转发消息。你可以把它当做一个快递站点&#xff0c;当你要发送一个包裹时&#xff0c;你把你的包裹放到快递站&#xff0c;快递员最终会把你的快递送到收件人那里&#xff0c;按照这种逻辑 RabbitMQ …

.Net Core in Docker极简入门(下篇)

点击上方蓝字"小黑在哪里"关注我吧Docker-Compose代码修改yml fileup & down镜像仓库前言上一篇【.Net Core in Docker极简入门&#xff08;上篇&#xff09;】讲解了docker的一些基本命令和操作&#xff0c;并成功构建了自己的asp.net core web应用的镜像&#…

这么多Apache顶级项目,SkyWalking为何一枝独秀?

吴晟读完需要5分钟速读仅需 2 分钟吴晟Apache基金会会员&#xff0c;Apache SkyWalking创始人、项目VP和PMC成员&#xff0c;Apache孵化器PMC成员&#xff0c;Apache ShardingSphere PMC成员&#xff0c;Apache APISIX (incubating) PPMC成员&#xff0c;Apache ECharts (incub…

[RabbitMQ]工作原理_原理名词解释

RabbitMQ 核心部分 各个名词介绍 RabbitMQ工作原理 Broker&#xff1a; 接收和分发消息的应用&#xff0c;RabbitMQ Server 就是 Message Broker Virtual host&#xff1a; 出于多租户和安全因素设计的&#xff0c;把 AMQP 的基本组件划分到一个虚拟的分组中&#xff0c;类…

Istio 中的授权策略详解

本文节选自 ServiceMesher 社区出品的开源电子书《Istio Handbook——Istio 服务网格进阶实践》&#xff0c;阅读地址&#xff1a;https://www.servicemesher.com/istio-handbook/授权功能是 Istio 中安全体系的一个重要组成部分&#xff0c;它用来实现访问控制的功能&#xff…

[RabbitMQ]创建Java开发环境_消费者_生产者

我们将用 Java 编写两个程序。发送单个消息的生产者和接收消息并打印出来的消费者。我们将介绍 Java API 中的一些细节。 在下图中&#xff0c;“ P”是我们的生产者&#xff0c;“ C”是我们的消费者。中间的框是一个队列-RabbitMQ 代表使用者保留的消息缓冲区 引入依赖 <…

如何利用Gitlab-CI持续部署到远程机器?

长话短说&#xff0c;今天聊一聊使用Gitlab-CI 自动部署到远程服务器。如果看过《基于docker-compose的Gitlab CI/CD实践&排坑指南》这篇文章的朋友&#xff0c;会注意到我是在 Gitlab-Runner服务器上自动部署的站点&#xff0c;本次我们结合ssh部署到远程机器(将CI服务器和…

[RabbitMQ]工作队列原理_代码实现

Work Queues 工作队列(又称任务队列)的主要思想是避免立即执行资源密集型任务&#xff0c;而不得不等待它完成。 相反我们安排任务在之后执行。我们把任务封装为消息并将其发送到队列。在后台运行的工作进程将弹出任务并最终执行作业。当有多个工作线程时&#xff0c;这些工作…

使用ImpromptuInterface反射方便的创建自定义DfaGraphWriter

在本文中&#xff0c;我为创建的自定义的DfaGraphWriter实现奠定了基础。DfaGraphWriter是公开的&#xff0c;因此您可以如上一篇文章《将终结点图添加到你的ASP.NET Core应用程序中》中所示在应用程序中使用它&#xff0c;但它使用的所有类均已标记为internal。这使得创建自己…

[RabbitMQ]消息应答概念_消息手动应答代码

消息应答 概念 消费者完成一个任务可能需要一段时间&#xff0c;如果其中一个消费者处理一个长的任务并仅只完成了部分突然它挂掉了&#xff0c;会发生什么情况。RabbitMQ 一旦向消费者传递了一条消息&#xff0c;便立即将该消 息标记为删除。在这种情况下&#xff0c;突然有…

rust火箭基地主楼开启方法_Rust 为什么能成为 Stack Overflow 最受欢迎的语言?

每年&#xff0c;开发者问答网站 Stack Overflow 都会对程序员社区展开年度调查&#xff0c;包括他们最喜爱的技术到工作偏好的所有内容。 在2017 年和2018 年Stack Overflow 年度开发者调查中&#xff0c;Rust语言已经连续两年成为最受欢迎语言Top 1。2018 年 Stack Overflow …

[RabbitMQ]队列持久化

RabbitMQ持久化 概念 如何保障当 RabbitMQ 服务停掉以后消息生产者发送过来的消息不丢失。默认情况下 RabbitMQ 退出或由于某种原因崩溃时&#xff0c;它忽视队列和消息&#xff0c;除非告知它不要这样做。确保消息不会丢失需要做两件事&#xff1a;我们需要将队列和消息都标…

微服务认证架构如何演进来的?

【答疑解惑】| 作者 / Edison Zhou这是恰童鞋骚年的第267篇原创内容之前有同事问为何要用基于JWT令牌的认证架构&#xff0c;然后近期又有童鞋在后台留言问微服务安全认证架构的实践&#xff0c;因此我决定花两篇推文来解答一下。为了答好这个话题&#xff0c;我们先来看看微服…

maskrcnn还可以加网络吗_绿茶加蜂蜜的功效,绿茶可以加蜂蜜吗?

绿茶是我国的主要茶类之一&#xff0c;是一种天然健康的饮料&#xff0c;蜂蜜也是一种营养丰富的滋补食品&#xff0c;有些人不喜欢绿茶的苦味&#xff0c;想放点蜂蜜中和一下&#xff0c;但是不知道能不能这样做。那么绿茶能不能加蜂蜜呢?蜂蜜的主要成分是葡萄糖、果糖&#…

三分钟Docker-镜像、容器实战篇

本文主要内容&#xff1a;Docker 镜像、容器 常用命令整理使用Docker常见命令&#xff0c;搭建Consul集群通过创建自定义镜像&#xff0c;把.NetCore Api运行在Docker中1.镜像、容器命令镜像序号命令描述1docker image build基于Dockerfile创建镜像2docker image history显示镜…

手机键鼠映射软件_吃鸡,我最专业!---盖世小鸡键鼠吃鸡套装评测

Hello大家好&#xff0c;欢迎浏览这篇评测贴。首先很荣幸能够参与本期的评测&#xff0c;毕竟如此炫酷富有科技感的装备是可遇而不可求的&#xff0c;所以不论是得知入选还是收到快递开箱的时候&#xff0c;心情都是无比激动。话不多说&#xff0c;接下来就让我带你走进这个不一…

[Redis6]Redis启动_前台启动和后台启动

前台启动(不推荐) 前台启动&#xff0c;命令行窗口不能关闭&#xff0c;否则服务器停止 redis-server 关闭redis ctrlC : 关闭 后台启动&#xff08;推荐&#xff09; 备份redis.conf cd redis-6.2.6/cp redis.conf /etc/redis.confcd /etc后台启动设置daemonize no改成y…

深入剖析.NETCORE中CORS(跨站资源共享)

前言由于现代互联网的飞速发展&#xff0c;我们在开发现代 Web 应用程序中&#xff0c;经常需要考虑多种类型的客户端访问服务的情况&#xff1b;而这种情况放在15年前几乎是不可想象的&#xff0c;在那个时代&#xff0c;我们更多的是考虑怎么把网页快速友好的嵌套到服务代码中…

ai进入轮廓模式怎么退出_详解AI中扩展、扩展外观、轮廓化描边、创建轮廓

详解AI中扩展、扩展外观、轮廓化描边、创建轮廓在学习AI软件中&#xff0c;有不少同学分不清扩展、扩展外观、轮廓化描边、创建轮廓这四个概念具体的功能区别&#xff0c;今天我们具体聊一下。先说“扩展”&#xff0c;扩展是把复杂物体拆分成最基本的路径。矢量物体在组合&…