最近在看项目,看到别人使用Rougamo框架,好奇花了点时间仔细研究了,在这里记录一下。

0. 静态编织 Aop

首先,我们先了解什么是Aop? Aop 是指面向切面编程 (Aspect Oriented Programming),而所谓的切面,可以认为是具体拦截的某个业务点。

我们常用的aop框架是 AspectCore,他是属于动态代理,也就是发生在运行时期间对代码进行“修改”。

Rougamo、Fody 是属于静态编织,是指在编译阶段将代码修改或额外的功能直接嵌入到程序集中,这个过程发生在源代码被编译成可执行文件或库之前。这意味着,一旦编译完成,插入的代码就已经是程序集的一部分,无需在运行时再进行额外的操作。

1. Rougamo 肉夹馍

Rougamo 是一个开源项目,github: https://github.com/inversionhourglass/Rougamo,他是通过Fody ->  Mono.Cecil 的方式实现静态编织 实现Aop功能。

创建控制台程序,Nuget安装 Rougamo.Fody

[AttributeUsage(AttributeTargets.Method)]
public class LoggingAttribute : MoAttribute
{
public override void OnEntry(MethodContext context)
{
Console.WriteLine("执行方法 {0}() 开始,参数:{1}.", context.Method.Name,
JsonConvert.SerializeObject(context.Arguments));
}
public override void OnException(MethodContext context)
{
Console.WriteLine("执行方法 {0}() 异常,{1}.", context.Method.Name, context.Exception.Message);
}
public override void OnExit(MethodContext context)
{
Console.WriteLine("执行方法 {0}() 结束.", context.Method.Name);
}
public override void OnSuccess(MethodContext context)
{
Console.WriteLine("执行方法 {0}() 成功.", context.Method.Name);
}
}
internal class Program
{
static void Main(string[] args)
{
Add(1, 2);
AddAsync(1, 2);
Divide(1, 2);
} [Logging]
static int Add(int a, int b) => a + b; [Logging]
static Task<int> AddAsync(int a, int b) => Task.FromResult(a + b); [Logging]
static decimal Divide(decimal a, decimal b) => a / b;
}

运行后会自动创建FodyWeavers.xsd 和 FodyWeavers.xml

<?xml version="1.0" encoding="utf-8"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
<!-- This file was generated by Fody. Manual changes to this file will be lost when your project is rebuilt. -->
<xs:element name="Weavers">
<xs:complexType>
<xs:all>
<xs:element name="Rougamo" minOccurs="0" maxOccurs="1" type="xs:anyType" />
</xs:all>
<xs:attribute name="VerifyAssembly" type="xs:boolean">
<xs:annotation>
<xs:documentation>'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed.</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="VerifyIgnoreCodes" type="xs:string">
<xs:annotation>
<xs:documentation>A comma-separated list of error codes that can be safely ignored in assembly verification.</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="GenerateXsd" type="xs:boolean">
<xs:annotation>
<xs:documentation>'false' to turn off automatic generation of the XML Schema file.</xs:documentation>
</xs:annotation>
</xs:attribute>
</xs:complexType>
</xs:element>
</xs:schema>
<Weavers xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="FodyWeavers.xsd">
<Rougamo />
</Weavers>

下面是运行结果

这时候我们可以看到 增加了LoggingAttribute 特性的方法在运行前、运行成功、运行结束 执行了 OnEntry(MethodContext context) 、OnSuccess(MethodContext context)、OnExit(MethodContext context) 方法,这时我们打开ILSpy工具,看看实际运行的代码

internal class Program
{
private static void Main(string[] args)
{
Add(1, 2);
AddAsync(1, 2);
Divide(1m, 2m);
} [DebuggerStepThrough]
private static int Add(int a, int b)
{
LoggingAttribute loggingAttribute = new LoggingAttribute();
IMo[] mos = new IMo[1] { loggingAttribute };
MethodContext methodContext = new MethodContext(null, typeof(Program), MethodBase.GetMethodFromHandle((RuntimeMethodHandle)/*OpCode not supported: LdMemberToken*/, typeof(Program).TypeHandle), isAsync: false, isIterator: false, mosNonEntryFIFO: false, mos, new object[2] { a, b });
loggingAttribute.OnEntry(methodContext);
int result = default(int);
if (methodContext.ReturnValueReplaced)
{
result = (int)methodContext.ReturnValue;
loggingAttribute.OnExit(methodContext);
return result;
}
if (methodContext.RewriteArguments)
{
a = (int)methodContext.Arguments[0];
b = (int)methodContext.Arguments[1];
}
bool flag = default(bool);
do
{
try
{
while (true)
{
try
{
flag = false;
result = $Rougamo_Add(a, b);
}
catch (Exception exception)
{
methodContext.Exception = exception;
methodContext.Arguments[0] = a;
methodContext.Arguments[1] = b;
loggingAttribute.OnException(methodContext);
if (methodContext.RetryCount > 0)
{
continue;
}
if (methodContext.ExceptionHandled)
{
result = (int)methodContext.ReturnValue;
break;
}
throw;
}
break;
}
}
finally
{
if (methodContext.HasException || methodContext.ExceptionHandled)
{
goto IL_0160;
}
methodContext.ReturnValue = result;
methodContext.Arguments[0] = a;
methodContext.Arguments[1] = b;
loggingAttribute.OnSuccess(methodContext);
if (methodContext.RetryCount <= 0)
{
if (methodContext.ReturnValueReplaced)
{
result = (int)methodContext.ReturnValue;
}
goto IL_0160;
}
flag = true;
goto end_IL_00fc;
IL_0160:
loggingAttribute.OnExit(methodContext);
end_IL_00fc:;
}
}
while (flag);
return result;
} [DebuggerStepThrough]
private static Task<int> AddAsync(int a, int b)
{
LoggingAttribute loggingAttribute = new LoggingAttribute();
IMo[] mos = new IMo[1] { loggingAttribute };
MethodContext methodContext = new MethodContext(null, typeof(Program), MethodBase.GetMethodFromHandle((RuntimeMethodHandle)/*OpCode not supported: LdMemberToken*/, typeof(Program).TypeHandle), isAsync: false, isIterator: false, mosNonEntryFIFO: false, mos, new object[2] { a, b });
loggingAttribute.OnEntry(methodContext);
Task<int> result = default(Task<int>);
if (methodContext.ReturnValueReplaced)
{
result = (Task<int>)methodContext.ReturnValue;
loggingAttribute.OnExit(methodContext);
return result;
}
if (methodContext.RewriteArguments)
{
a = (int)methodContext.Arguments[0];
b = (int)methodContext.Arguments[1];
}
bool flag = default(bool);
do
{
try
{
while (true)
{
try
{
flag = false;
result = $Rougamo_AddAsync(a, b);
}
catch (Exception exception)
{
methodContext.Exception = exception;
methodContext.Arguments[0] = a;
methodContext.Arguments[1] = b;
loggingAttribute.OnException(methodContext);
if (methodContext.RetryCount > 0)
{
continue;
}
if (methodContext.ExceptionHandled)
{
result = (Task<int>)methodContext.ReturnValue;
break;
}
throw;
}
break;
}
}
finally
{
if (methodContext.HasException || methodContext.ExceptionHandled)
{
goto IL_015b;
}
methodContext.ReturnValue = result;
methodContext.Arguments[0] = a;
methodContext.Arguments[1] = b;
loggingAttribute.OnSuccess(methodContext);
if (methodContext.RetryCount <= 0)
{
if (methodContext.ReturnValueReplaced)
{
result = (Task<int>)methodContext.ReturnValue;
}
goto IL_015b;
}
flag = true;
goto end_IL_00fc;
IL_015b:
loggingAttribute.OnExit(methodContext);
end_IL_00fc:;
}
}
while (flag);
return result;
} [DebuggerStepThrough]
private static decimal Divide(decimal a, decimal b)
{
LoggingAttribute loggingAttribute = new LoggingAttribute();
IMo[] mos = new IMo[1] { loggingAttribute };
MethodContext methodContext = new MethodContext(null, typeof(Program), MethodBase.GetMethodFromHandle((RuntimeMethodHandle)/*OpCode not supported: LdMemberToken*/, typeof(Program).TypeHandle), isAsync: false, isIterator: false, mosNonEntryFIFO: false, mos, new object[2] { a, b });
loggingAttribute.OnEntry(methodContext);
decimal result = default(decimal);
if (methodContext.ReturnValueReplaced)
{
result = (decimal)methodContext.ReturnValue;
loggingAttribute.OnExit(methodContext);
return result;
}
if (methodContext.RewriteArguments)
{
a = (decimal)methodContext.Arguments[0];
b = (decimal)methodContext.Arguments[1];
}
bool flag = default(bool);
do
{
try
{
while (true)
{
try
{
flag = false;
result = $Rougamo_Divide(a, b);
}
catch (Exception exception)
{
methodContext.Exception = exception;
methodContext.Arguments[0] = a;
methodContext.Arguments[1] = b;
loggingAttribute.OnException(methodContext);
if (methodContext.RetryCount > 0)
{
continue;
}
if (methodContext.ExceptionHandled)
{
result = (decimal)methodContext.ReturnValue;
break;
}
throw;
}
break;
}
}
finally
{
if (methodContext.HasException || methodContext.ExceptionHandled)
{
goto IL_0160;
}
methodContext.ReturnValue = result;
methodContext.Arguments[0] = a;
methodContext.Arguments[1] = b;
loggingAttribute.OnSuccess(methodContext);
if (methodContext.RetryCount <= 0)
{
if (methodContext.ReturnValueReplaced)
{
result = (decimal)methodContext.ReturnValue;
}
goto IL_0160;
}
flag = true;
goto end_IL_00fc;
IL_0160:
loggingAttribute.OnExit(methodContext);
end_IL_00fc:;
}
}
while (flag);
return result;
} [Logging]
private static int $Rougamo_Add(int a, int b)
{
return a + b;
} [Logging]
private static Task<int> $Rougamo_AddAsync(int a, int b)
{
return Task.FromResult(a + b);
} [Logging]
private static decimal $Rougamo_Divide(decimal a, decimal b)
{
return a / b;
}
}

从实际运行的代码我们可以看到,原先Add(int a, int b)方法中的执行内容被移动到 $Rougamo_Add方法中,而Add(int a, int b)方法先是new LoggingAttribute() 和 new Rougamo.Context.MethodContext() -> 执行了 loggingAttribute.OnEntry(methodContext); -> 在do{}while(bool) 执行了$Rougamo_Add(a, b); -> 在 exception 中执行了loggingAttribute.OnException(methodContext); -> 在 finally中执行了 loggingAttribute.OnSuccess(methodContext); 和 loggingAttribute.OnExit(methodContext);

注:do{}while(bool) 执行了$Rougamo_Add(a, b); 是因为 Rougamo 可以实现方法执行失败重试功能

至此我们明白了 Rougamo 实现 Aop功能是通过编译时修改IL代码,往代码增加对应的生命周期代码。那他为什么可以做到呢?其实是借用了Fody ->  Mono.Cecil 的方式。

代码如下:https://gitee.com/Karl_Albright/csharp-demo/tree/master/FodyDemo/RougamoDemo

2. Fody ->  Mono.Cecil

Fody 是一个开源项目,github: https://github.com/Fody/Fody,相关教程文档在 https://github.com/Fody/Home/tree/master/pages

创建类库,选择netstandard2.0,命名为HelloWorld,Nuget安装 Fody 和 FodyPackaging

注:必须创建 netstandard2.0,因为FodyPackaging的目标是netstandard2.0,

在HelloWorld项目中,我们只放 HWAttribute类,继承于 Attribute。代码如下

[AttributeUsage(AttributeTargets.Assembly | AttributeTargets.Module | AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Constructor | AttributeTargets.Method | AttributeTargets.Property)]
public class HWAttribute : Attribute
{ }

再次创建类库,选择netstandard2.0,命名为HelloWorld.Fody,Nuget安装 FodyHelpers,引用HelloWorld类库

在HelloWorld.Fody项目中,我们只放ModuleWeaver类(类名是固定的,详情见Fody文档),继承于 BaseModuleWeaver。代码如下

using Fody;
using Mono.Cecil;
using Mono.Cecil.Cil;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection; namespace HelloWorld.Fody
{
public partial class ModuleWeaver : BaseModuleWeaver
{
public override void Execute()
{
foreach (var type in ModuleDefinition.Types)
{
foreach (var method in type.Methods)
{
var customerAttribute = method.CustomAttributes.FirstOrDefault(x => x.AttributeType.Name == nameof(HWAttribute));
if (customerAttribute != null)
{
ProcessMethod(method);
}
}
}
} public override IEnumerable<string> GetAssembliesForScanning()
{
yield return "mscorlib";
yield return "System";
} private MethodInfo _writeLineMethod => typeof(Console).GetMethod("WriteLine", new Type[] { typeof(string) }); private void ProcessMethod(MethodDefinition method)
{
// 获取当前方法体中的第一个IL指令
var processor = method.Body.GetILProcessor();
var current = method.Body.Instructions.First(); // 插入一个 Nop 指令,表示什么都不做
var first = Instruction.Create(OpCodes.Nop);
processor.InsertBefore(current, first);
current = first; // 构造 Console.WriteLine("Hello World")
foreach (var instruction in GetInstructions(method))
{
processor.InsertAfter(current, instruction);
current = instruction;
}
}
private IEnumerable<Instruction> GetInstructions(MethodDefinition method)
{
yield return Instruction.Create(OpCodes.Nop);
yield return Instruction.Create(OpCodes.Ldstr, "Hello World.");
yield return Instruction.Create(OpCodes.Call, ModuleDefinition.ImportReference(_writeLineMethod));
}
}
}

在代码中,我们遍历了所有类型的所有方法,如果方法标注了 HWAttribute特性,则增加 Console.WriteLine("Hello World."); 代码。

创建控制台应用程序,命名为HelloWorldFodyDemo,添加 HelloWorld 和 HelloWorld.Fody 项目引用,并且手动增加 WeaverFiles标签,目标是HelloWorld.Fody.dll

在控制台中,我们需要一个方法,方法上有 HWAttribute 特性就可以了,代码如下

internal class Program
{
static void Main(string[] args)
{
Echo();
Console.ReadKey();
} [HW]
public static void Echo()
{
Console.WriteLine("Hello Fody.");
}
}

在控制台项目中,我们还需要 FodyWeavers.xml 和 FodyWeavers.xsd 文件,(我也是从上面Rougamo项目中复制的),内容如下

<Weavers xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="FodyWeavers.xsd">
<HelloWorld />
</Weavers>
<?xml version="1.0" encoding="utf-8"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xs:element name="Weavers">
<xs:complexType>
<xs:all>
<xs:element name="HelloWorld" minOccurs="0" maxOccurs="1" type="xs:anyType" />
</xs:all>
<xs:attribute name="VerifyAssembly" type="xs:boolean">
<xs:annotation>
<xs:documentation>'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed.</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="VerifyIgnoreCodes" type="xs:string">
<xs:annotation>
<xs:documentation>A comma-separated list of error codes that can be safely ignored in assembly verification.</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="GenerateXsd" type="xs:boolean">
<xs:annotation>
<xs:documentation>'false' to turn off automatic generation of the XML Schema file.</xs:documentation>
</xs:annotation>
</xs:attribute>
</xs:complexType>
</xs:element>
</xs:schema>

目前,文件结构如下

FodyDemo
|--- HelloWorld
|--- HWAttribute.cs
|--- HelloWorld.csproj
|--- HelloWorld.Fody
|--- HelloWorld.Fody.csproj
|--- ModuleWeaver.cs
|--- HelloWorldFodyDemo
|--- FodyWeavers.xml
|--- FodyWeavers.xsd
|--- HelloWorldFodyDemo.csproj
|--- Program.cs

代码如下:https://gitee.com/Karl_Albright/csharp-demo/tree/master/FodyDemo

最后运行结果如下,很明显,HWAttribute生效了,我们成功的在Echo()方法前打印了Hello World。

我们再次打开ILSpy工具,得到的结果如图,代码增加了Console.WriteLine("Hello World.");行代码

4. Fody 有很多其他的“插件”,大家可以多试试

AutoProperties.Fody: 这个外接程序为您提供了对自动属性的扩展控制,比如直接访问backing字段或拦截getter和setter。

PropertyChanged.Fody: 将属性通知添加到实现INotifyPropertyChanged的所有类。

InlineIL.Fody: 在编译时注入任意IL代码。

MethodDecorator.Fody:通过IL重写编译时间装饰器模式。

NullGuard.Fody: 将空参数检查添加到程序集。

ToString.Fody: 给属性生成ToString()方法

Rougamo.Fody: 在编译时生效的AOP组件,类似于PostSharp。

Rougamo、Fody 实现静态Aop的更多相关文章

  1. 3.静态AOP实现-代理模式

    通过代理模式实现在RegUser()方法本身业务前后加上一些自己的功能,如:BeforeProceed和AfterProceed,即不修改UserProcessor类又能增加新功能 定义1个用户接口, ...

  2. 2.静态AOP实现-装饰器模式

    通过装饰器模式实现在RegUser()方法本身业务前后加上一些自己的功能,如:BeforeProceed和AfterProceed,即不修改UserProcessor类又能增加新功能 定义1个用户接口 ...

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

    肉夹馍是什么 肉夹馍通过静态代码织入方式实现AOP的组件..NET常用的AOP有Castle DynamicProxy.AspectCore等,以上两种AOP组件都是通过运行时生成一个代理类执行AOP ...

  4. .NET静态代码织入——肉夹馍(Rougamo) 发布1.1.0

    肉夹馍(https://github.com/inversionhourglass/Rougamo)通过静态代码织入方式实现AOP的组件,其主要特点是在编译时完成AOP代码织入,相比动态代理可以减少应 ...

  5. .NET静态代码织入——肉夹馍(Rougamo) 发布1.2.0

    肉夹馍(https://github.com/inversionhourglass/Rougamo)通过静态代码织入方式实现AOP的组件,其主要特点是在编译时完成AOP代码织入,相比动态代理可以减少应 ...

  6. 【开源】.Net Aop(静态织入)框架 BSF.Aop

    BSF.Aop .Net 免费开源,静态Aop织入(直接修改IL中间语言)框架,类似PostSharp(收费): 实现前后Aop切面和INotifyPropertyChanged注入方式. 开源地址: ...

  7. AOP静态代理解析1-标签解析

    AOP静态代理使用示例见Spring的LoadTimeWeaver(代码织入) Instrumentation使用示例见java.lang.instrument使用 AOP的静态代理主要是在虚拟机启动 ...

  8. 静态实现AOP(翻译自MSDN)

    在.net实现AOP 本文通过一个简单的例子实现静态AOP.改例子主要实现客户的增删改查,突然有一天你的老板需要在程序上跟踪每个方法操作的运行日志. 主要分为5个步骤. 第一步:创建接口IReposi ...

  9. AOP的实现原理

    1 AOP各种的实现 AOP就是面向切面编程,我们可以从几个层面来实现AOP. 在编译器修改源代码,在运行期字节码加载前修改字节码或字节码加载后动态创建代理类的字节码,以下是各种实现机制的比较. 类别 ...

  10. 静态代理&动态代理

    原文地址:http://blog.csdn.net/partner4java/article/details/7048879 静态AOP和动态AOP. 静态代理: 代理对象与被代理对象必须实现同一个接 ...

随机推荐

  1. C#开发的CPU使用率小应用 - 开源研究系列文章 - 个人小作品

    这次用C#编写一个CPU使用率的小应用.想了一下,大概需要两个内容:一个是获取CPU使用率:一个是托盘图标的动画效果.这两个内容在上次的博文中有介绍了,此博文为具体的应用的例子. 对于要实现的应用,首 ...

  2. Linux — 进程管理

    进程创建 进程通过fork()创建的大致过程: #include <stdio.h> #include <stdlib.h> #include <sys/types.h& ...

  3. FTP主动模式和被动模式(2)

    防火墙对FTP的影响 ASPF 多通道协议 应用层程序有些使用的是单通道协议,有些使用的是多通道协议. 单通道协议 例如http协议,整个协议交互过程中,服务端和客户端只建立一个连接,并且服务端固定使 ...

  4. 应急响应web1

    应急响应的过程 目的:分析攻击时间.攻击操作.攻击结果.安全修复等并给出合理的解决方案. 保护阶段:直接断网,保护现场,看是否能够恢复数据: 分析阶段:对入侵过程进行分析,常见的方法为指纹库搜索.日志 ...

  5. wordpress多站点设置,移除 多站点链接中的 /blog 前缀

    ★★★最近想给自己的wordpress添加多个站点,就查了查相关的设置方法,以下我亲自尝试可行 1.首先需要开始配置:在网站根目录下的 wp-config.php 添加: define('WP_ALL ...

  6. RMBG1.4服务器部署指南

    近期,一家AIGC公司BRIA开源了一个出圈的模型:RMBG-1.4,它可以实现高质量地一键去除图片中的背景.下面是一些具体的例子,可以看到这个模型可以实现非常精细的"抠图". R ...

  7. pageoffice6在线编辑word 文件禁止鼠标右键

    有时让用户使用PageOffice只读模式(OpenModeType.docReadOnly)打开Word文件后,为了更好的只读效果,还希望禁用Word中的右键菜单,实现此效果只需创建com.zhuo ...

  8. C#利用win32API创建窗体

    效果图 代码实现 1 using System; 2 using System.Runtime.InteropServices; 3 //using System.Windows.Forms; 4 5 ...

  9. NFS共享文件

    NFS共享文件 服务端 安装NFS [root@localhost www] yum -y install nfs-utils rpcbind 创建需要共享的文件夹share [root@localh ...

  10. Centos6/RHEL6下恢复ext4文件系统下误删除的文件

    目录 一.关于ext4文件系统 二.linux文件系统的组成(inode,block) 三.问题:为什么删除比复制快? 四.问题:当我们误删除文件后,第一件事要做什么? 五.准备测试环境 六.安装ex ...