.NET静态代码织入——肉夹馍(Rougamo)

肉夹馍是什么

肉夹馍通过静态代码织入方式实现AOP的组件。.NET常用的AOP有Castle DynamicProxy、AspectCore等,以上两种AOP组件都是通过运行时生成一个代理类执行AOP代码的,肉夹馍则是在代码编译时直接修改原始方法IL代码,在原始方法内织入AOP代码的。.NET静态AOP的组件或许有人使用过PostSharp,这是一个功能完善且强大的静态代码织入组件,Postsharp有社区版,但可惜的是社区版不支持异步方法,肉夹馍的实现方式与Postsharp类似,同时也支持了异步方法,如果你仅仅使用了Postsharp方法层级的AOP代码织入功能,可以尝试使用肉夹馍来替代Postsharp。

快速开始

# 添加NuGet引用
dotnet add package Rougamo.Fody
// 1.定义类继承MoAttribute,在该类中定义你在方法执行各阶段需要织入的代码
public class LoggingAttribute : MoAttribute
{public override void OnEntry(MethodContext context){// 从context对象中能取到包括入参、类实例、方法描述等信息Log.Info("方法执行前");}public override void OnException(MethodContext context){Log.Error("方法执行异常", context.Exception);}public override void OnExit(MethodContext context){Log.Info("方法退出时,不论方法执行成功还是异常,都会执行");}public override void OnSuccess(MethodContext context){Log.Info("方法执行成功后");}
}// 2.在需要织入代码的方法上应用LoggingAttribute
public class Service
{[Logging]public static int Sync(Model model){// ...}[Logging]private async Task<Data> Async(int id){// ...}
}

通过实现空接口的方式进行代码织入

在上面的示例中,我们通过在方法上应用Attribute进行AOP,这种方式目标明确但有些AOP代码我们可能希望应用于某一场景或某一层级,每个方法都去应用Attribute很繁琐,而且代码侵入严重。此时就可以考虑使用实现空接口(IRougamo<>)的方式进行批量Attribute应用

public interface IService : IRougamo<LoggingAttribute> { }public interface IMyService : IService { }public class MyService : IMyService
{
}

上面的示例中,MyService所有的public实例方法都将应用LoggingAttribute,你可能注意到我标红的部分了,为什么是public实例方法呢?这是默认值,你可以在继承MoAttribute时通过重写Flags属性来修改这一默认值,比如下面的示例中FullLoggingAttribute将会应用于所有方法。另外需要注意的是Flags属性在Attribute直接应用到方法上时是无效的,比如LoggingAttribute默认仅应用public实例方法,但像快速开始里的代码那样Async方法虽然是private的但还是会应用LoggingAttribute

public class FullLoggingAttribute : LoggingAttribute
{public override AccessFlags Flags => AccessFlags.All;
}

实例-Rougamo.OpenTelemetry

在快速开始里介绍了肉夹馍两种常用的使用方式,更多的使用方式可以到github查看readme,在本篇文章中就不再做更多介绍了,接下来我将介绍使用肉夹馍的一个项目Rougamo.OpenTelemetry,如果你准备使用肉夹馍,但你还是不太清楚具体应该怎么使用,可以参考这个项目的代码实现。

关于OpenTelemetry

在了解OpenTelemetry前,你需要先了解APM(Application Performance Management/Monitor),在这个微服务的时代,APM已经成为了必不可少的一部分,没有它整个系统对我们而言就是一个黑盒,你无法得知一个请求在微服务之间是如何调用如何完成,难以排查一个用户超时是哪个服务超时或出错。现在市面上有很多开源的APM比如Pinpoint, Zipkin, SkyWalking, CAT, jaeger等,虽说大家基本都是参考google的dapper论文设计出来的,但实现和功能侧重却大相径庭,为了对此形成一个规范,先后出现了OpenTracingOpenCensus,并在此后合并为现在的OpenTelemetry。OpenTelemetry的出现为APM的接入提供了一种可能“应用不需要在意具体的APM服务端使用的是Zipkin还是jaeger或是其他的情况下,应用只需要使用OpenTelemetry的SDK进行埋点,APM通过实现OTLP(OpenTelemetry Protocol)来支持OpenTelemetry数据格式即可”,当前已经有些APM完全采用OpenTelemetry SDK作为默认的SDK比如jaeger,也有部分支持的APM比如skywalking。

关于Rougamo.OpenTelemetry

现在大部分流行的APM都有对应语言的SDK并且还实现了常用的I/O组件埋点,opentelemetry-dotnet也已经提供了包括HttpClientSqlClientAspNetCore等I/O埋点。虽说一般而言服务的耗时一般就在I/O部分,但由于开发人员的代码习惯不同、代码水平不同以及业务复杂度等情况,某些非I/O代码也会产生一定的耗时,同时在一个接口中可能会执行多次I/O操作,如果仅仅只有I/O埋点,可能很难分辨层次关系,此时可能需要一些本地辅助埋点,Rougamo.OpenTelemetry便是用于添加本地埋点的组件。

快速开始

# 启动项目引用Rougamo.OpenTelemetry.Hosting
dotnet add package Rougamo.OpenTelemetry.Hosting
# 添加埋点的项目引用Rougamo.OpenTelemetry
dotnet add package Rougamo.OpenTelemetry
public class Startup
{public void ConfigureServices(IServiceCollection services){// ...services.AddOpenTelemetryTracing(builder =>{builder.AddRougamoSource() // 初始化Rougamo.OpenTelemetry.AddAspNetCoreInstrumentation().AddJaegerExporter();});// 修改Rougamo.OpenTelemetry默认配置services.AddOpenTelemetryRougamo(options =>{options.ArgumentsStoreType = ArgumentsStoreType.Tag;});}
}class Service
{[return: ApmIgnore]     // 返回值不记录[Otel] // 默认记录参数和返回值,需要通过ApmIgnoreAttribute来忽略不需要记录的参数或返回值public async Task<string> M1([ApmIgnore] string uid, // 该参数不记录DateTime time){// do somethingreturn string.Empty;}[PureOtel] // 默认不记录参数和返回值,需要通过ApmRecordAttribute来记录指定的参数或返回值public void M2([ApmRecord] double d1,  // 记录该参数double d2){// do something}
}// 通过实现空接口织入
public interface ITestService : IRougamo<FullOtelAttribute>
{// ...
}
public class TestService : ITestService
{// ...
}

Rougamo.OpenTelemetry的埋点会对应生成一个名称为方法全名称(ClassFullName.MethodName)的LocalSpan,根据你使用的是OtelAttribute还是PureOtelAttribute决定默认是否记录参数和返回值。Rougamo.OpenTelemetry是用来丰富APM埋点的,但是切记不要过度添加埋点,过多的埋点会让你的trace看起来很臃肿。
关于Rougamo.OpenTelemetry更多的使用说明,详见github,github上的代码中包含了一个jaeger的示例代码,你可以从jaeger官网上下载一个all-in-one包快速运行一个jaeger服务端,然后启动示例项目,访问http://localhost:5000/test接口,最后访问jaeger uihttp://localhost:16686查看刚刚访问的test接口的trace数据。

更多关于

关于肉夹馍的应用情况

写肉夹馍的动机是公司在使用postsharp做AOP,起初公司的代码是framework的并且基本使用同步方法,所以postsharp的免费版本是足足够用的,随着.NET的发展,公司的代码也逐渐从同步发展到异步从framework发展到core,然后我们通过购买付费版本的postsharp也能继续维持着,不过由于个人对postsharp的实现产生了兴趣,所以悄悄的建立了这个项目,但是由于个人比较懒,这个早在19年就建立了的项目直到21年才完成。
在发布1.0.1之前,项目一直处于闭源状态,但在闭源状态下已经在公司内部发布了几个测试版本,其中1.0.0版本已经在公司测试环境沉淀了一个季度有余,现在已经将1.0.0版本发布到了线上使用中,发布在nuget.org上的1.0.1版本相对于1.0.0版本在代码上没有任何修改。Rougamo项目的TargetFramework是netstandard2.0,公司应用了Rougamo的项目都是.NET Core3.1的,所以如果你的项目是.NET Core3.1的,你可以相对放心的使用(如果不着急应用,也推荐测试环境沉淀一下),如果你是其他版本,那么推荐你在测试环境沉淀一段时间,肉夹馍作为一个新项目,可能还会存在一些未知BUG,如果有任何BUG请反馈到github issue中。

关于.NET的静态代码织入

.NET的静态代码织入其实我了解的也不是特别多,我知道鼻祖应该是Mono.Cecil,百度也能搜到很多它的介绍,然后就是很强大(但大部分功能收费)的Postsharp,以及对Mono.Cecil进行封装,使其更易用的Fody,肉夹馍便是使用Fody实现AOP代码织入的。
静态代码织入在我观察下来使用得并不是很普遍,这或许是因为动态代理早已成熟的缘故吧。那么静态织入相对于动态代理有什么优势呢?说实话,开发肉夹馍很大一部分原因是个人兴趣,但这并不代表它没有优势,静态织入是在编译时进行的,静态织入只会让编译时间稍长些许,而动态代理的方式都是在应用启动时动态生成代理类来实现的,这个过程必定会占用些许时间,并且在这个初始化动作完成前,服务是不会进入就绪状态的,也就是这个服务暂时为不可用状态的,服务初始化时间越短,服务整体的可用性就会越好,这就是静态织入带来的优势。当然,有些朋友可能会认为这是在钻牛角尖,确实,很多时候我们可能认为这种耗时是微乎其微的,事实也确实如此,但做基础架构关注的就是这些微乎其微耗时,我们经常能看到java的一些技术博文上会写到他们做了很多字节码层面的优化,他们的这种优化很多时候只是优化了那么几个指令,单拎出来看着似乎没有多大的性能提升,然而在大流量高吞吐的服务中,这样优化的效果将会显现出来,静态织入也是如此,性能就是这样一点一点扣出来的。

关于Fody

.NET的开发者应该或多或少都听说甚至使用过ABP,它是.NET中非常流行的一套DDD框架了,如果你还看过ABP的源码,你或许见过Fody的影子,是的ABP也有使用到Fody,使用的是ConfigureAwait.Fody,我们在编写异步方法的时候经常会增加一个.ConfigureAwait(false)ConfigureAwait.Fody的功能就是为异步调用默认加上这个方法调用。
进入到Fody的github首页你将能看到很多借助于Fody开发的组件,我们也可以直接在nuget.org上以Fody为关键字进行搜索,你将能看到更多以Fody开发的组件,同时你可能还会发现,在下载量很高的NuGet包中有两个AOP相关实现MethodDecorator.Fody和MethodBoundaryAspect.Fody,早在我建立肉夹馍这个项目前我就看到了这两个项目,但当时的他们没有对异步方法的支持,就在这篇文章写到这里的时候我再次去查看了这两个项目,他们对异步的支持依旧不能满足我的需求,他们的OnExit方法都是在状态机在第一次返回也就是在遇到第一个await的时候执行的,这时候这个异步方法实际上可能并没有执行完毕,下面我会给一个例子,各位可以自己进行尝试。关于为什么我没有直接参与他们的项目,而是自己新建了一个项目,主要有两个原因:一是我有一丢丢懒,不确定这个项目我会投入多少精力并且什么时候去完成,事实也正如我的预期,两年过去了,二是我的英语有一丢丢差,IL方面我也不算老手,我担心有些问题交流起来有困难,所以最终也就独立建了肉夹馍这个项目了。

dotnet add package Rougamo.Fody
dotnet add package MethodDecorator.Fody
dotnet add package MethodBoundaryAspect.Fody
<!--FodyWeavers.xml-->
<Weavers xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="FodyWeavers.xsd"><Rougamo /><MethodDecorator /><MethodBoundaryAspect />
</Weavers>
class Program
{static async Task Main(string[] args){try{await Async();}catch{}}[RougamoLog]//[MethodDecoratorLog]//[MethodBoundaryAspectLog]static async Task Async(){Console.WriteLine(1);await Task.Delay(10);Console.WriteLine(2);throw new NotImplementedException("not implemented");}
}

分别用三个Attribute运行上面的程序你会得到下面的输出,肉夹馍的异常信息是在输出2之后输出,exit信息在最后输出(也就是异步方法执行完毕后);MethodDecorator没有捕获到异步的异常,并且exit信息在输出2之前就输出了;MethodBoundaryAspect捕获到了异步的异常信息,但是exit信息在输出2之前输出了,也就是你无法在异步方法真正执行完毕后织入代码。

[Rougamo] on entry
1
2
[Rougamo] on exception: not implemented
[Rougamo] on exit[MethodDecorator] on init
[MethodDecorator] on entry
1
[MethodDecorator] on exit
2[MethodBoundaryAspect] on entry
1
[MethodBoundaryAspect] on exit
2
[MethodBoundaryAspect] on exception: not implemented

关于使用肉夹馍开发组件的注意事项

最后如果你准备使用肉夹馍,并且你准备使用肉夹馍开发一个供他人使用的NuGet组件,那么你需要把项目文件(.csproj)中Rougamo.Fody的引用改成下面这样,不然你发布的NuGet其他人引用后将需要额外引用Fody,否则将无法进行代码织入,具体可以参考Rougamo.OpenTelemetry

<PackageReference Include="Rougamo.Fody" Version="1.0.1" IncludeAssets="all" PrivateAssets="contentfiles;analyzers" />

最后的最后,即使你不准备使用肉夹馍,也希望通过此文让你了解到静态代码织入,了解到Mono.CecilFody,如果.NET能够发展壮大起来,那么静态代码织入也终将得到更大的发展。这篇文章中不论是Rougamo还是Rougamo.OpenTelemetry都没有进行完整的介绍,如果你准备使用它们,请移步github了解更多。

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

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

相关文章

Android之电话状态的监听(来电和去电)

实现手机电话状态的监听&#xff0c;主要依靠两个类&#xff1a;TelephoneManger和PhoneStateListener。 TelephonseManger提供了取得手机基本服务的信息的一种方式。因此应用程序可以使用TelephonyManager来探测手机基本服务的情况。应用程序可以注册listener来监听电话状态的…

Only the original thread that created a view hierarchy can touch its views.

/*********************************************************************************** Only the original thread that created a view hierarchy can touch its views.* 说明&#xff1a;* 自定义view的时候出现这个错误&#xff0c;是用错了方法。* * …

史上最让数学家无奈的规定!背后真相让人不敢相信,可是没有人能证明对错.........

全世界只有3.14 % 的人关注了爆炸吧知识负负得正怎么证明&#xff1f;放假了&#xff0c;8岁表妹又来我家打算好好学习。今天聊着聊着&#xff0c;超模君差点被她给绕进去。她&#xff1a;“老师说负负得正&#xff0c;所以&#xff0c;所以所有的负数乘以负数都是正数&#xf…

paip.C#.NET JSON解析总结

paip.C#.NET JSON解析总结 作者Attilax &#xff0c; EMAIL:1466519819qq.com 近日做跑号器,需要解析JSON {"code":799,"data":{"backUrl":""},"message":"\u767b\u9646\u6210\u529f"} 查了下资料,有原生API…

网络编程资源

高性能 Socket 组件 HP-Socket v3.2.1-RC1 发布 http://bbs.csdn.net/topics/390763397 The C10K problem 高性能服务器设计 http://www.cnblogs.com/fll/archive/2008/05/17/1201540.html http://blog.sina.com.cn/s/blog_4aec22920100itkf.html 这周看了Dan Kegel那篇"…

计算机试题dddd,数据库系统概论试题及答案dddd_Image_Marked.pdf

数据库系统概论试题及答案dddd_Image_Marked数据库系统概论复习资料&#xff1a;第一章&#xff1a;一选择题&#xff1a;1&#xff0e;在数据管理技术的发展过程中&#xff0c;经历了人工管理阶段、文件系统阶段和数据库系统阶段。在这几个阶段中&#xff0c;数据独立性最高的…

Android之AudioManager(音频管理器)详解

AudioManager简介&#xff1a; AudioManager类提供了访问音量和振铃器mode控制。使用Context.getSystemService&#xff08;Context.AUDIO_SERVICE&#xff09;来得到这个类的一个实例。 公有方法&#xff1a; Public Methods int abandonAudioFocus(AudioManager.OnAudioFo…

Asp.NetCore3.1开源项目升级为.Net6.0

概述自从.Net6.0出来后&#xff0c;一直想之前开发的项目升级.Net6.0&#xff0c;有时想想毕竟中间还跨了个5.0版本&#xff0c;升级起来不知道坑大不大&#xff0c;最近抽时间对升级的方案做了些研究&#xff0c;然后将代码升级为.Net6.0。本质上来说我个人不太喜欢.Net6.0去掉…

ios开发备忘录三

为什么80%的码农都做不了架构师&#xff1f;>>> 在配置第三方库EGORefreshTableHeaderView的时候&#xff08;其实就只是一个头文件和一个源文件&#xff0c;还算不上库&#xff09;&#xff0c;可以从头文件发现 #import <QuartzCore/QuartzCore.h> 所以必…

Android之Eclipse下面如何看Android源代码

在看代码的时候,会经常用到 ctrl鼠标左键 去看自己写的类,有时候点到java的类或者android的类就无法打开,显示no class found之类的东西, 1.拿Button举例,ctrl加鼠标左键,然后显示了no class found之后,这时候会发现整个页面只有一个按钮可以点击,就是change attach source,如果…

项目管理 计算机仿真,分析计算机仿真技术在工程项目施工管理中的运用.pdf

第 33 卷 第 1 期(上) 赤 峰 学 院 学 报( 自 然 科 学 版 ) Vol. 33 No.12017 年 1 月 Journal of Chifeng University (Natural Science Edition) Jan. 2017分析计算机仿真技术在工程项目施工管理中的运用林振文(厦 门软件职业技 术学院 &#xff0c; 福 建 厦 门 361024 )摘…

C# 企业微信消息推送对接,实现天气预报推送

准备工作需要获取的东西1. 企业Id,2.应用secret 和 应用ID获取企业id注册完成后&#xff0c;在我的企业》企业信息》最下面企业id获取应用secret 和 应用ID发送微信消息class WeCom{public static string weComCId "ww2b b0bf8";//企业Id①public static string we…

通信协议——HTTP、TCP、UDP

TCP HTTP UDP:都是通信协议&#xff0c;也就是通信时所遵守的规则&#xff0c;只有双方按照这个规则“说话”&#xff0c;对方才能理解或为之服务。TCP HTTP UDP三者的关系:TCP/IP是个协议组&#xff0c;可分为四个层次&#xff1a;网络接口层、网络层、传输层和应用层…

++i 与 i++

i 与 i都能实现自增,它们的区别在哪? i:先自增,然后返回引用. i:先进行一个本地备份(可以理解为定义一个临时变量),自增,然后返回临时变量的引用. 理论上来讲,i操作会快于i,但由于现代编译器做了优化.所以,i与i在性能上的并无差别.转载于:https://www.cnblogs.com/heavenyes/p…

Visual Studio 远程调试正在运行的进程

使用场景当项目在测试环境上有bug&#xff0c;需要运行代码调试一下&#xff0c;这时就需要在测试环境上安装一个调试工具&#xff0c;然后在本地运行代码&#xff0c;远程链接到测试环境服务器来调试代码&#xff1b;&#xff08;假期鸽了这末长的时间&#xff09;方式一&…

直男约会能有多奇葩​?

1 六根烟来了七位领导怎么办&#xff08;素材来源网络&#xff0c;侵删&#xff09;▼2 和36号鞋垫一样大的毛肚&#xff08;素材来源网络&#xff0c;侵删&#xff09;▼3 直男约会能有多奇葩&#xff1f;&#xff08;素材来源网络&#xff0c;侵删&#xff09;▼4 高情商v…

Android之如何解决adb server is out of date,killing...ADB server didn't ACK

今天&#xff0c;久未出现的著名的“adb server is out of date. killing”又发生了&#xff0c;在此&#xff0c;将解决方法记下&#xff0c;以便日后查看。 1. 错误信息&#xff1a; C:\Users\lizy>adb devices adb devicesadb server is out of date. killing... ADB …

计算机如何实现共享接入,局域网内电脑实现共享设置方法

不同局域网内电脑怎么实现互访实现文件或打印机共享设置?下面由学习啦小编给你做出详细的实现共享设置方法介绍!希望对你有帮助!局域网内电脑实现共享设置方法&#xff1a;一、使用一条网线将两个路由器连接&#xff0c;网线插在路由器的LAN口上;二、两个路由器要设置两个不同…

python的内建函数详解

python内建函数最近一直在学习python&#xff0c;在网上看到和学习了关于python内建函数的一些分类和个内建函数的作用&#xff0c;下面是一些关于python内建函数的罗列&#xff0c;初学者的了解&#xff0c;分类可能不准确&#xff0c;一起交流。一、数学运算类abs(x)求绝对值…