0. 前言
上接:思想無語言邊界:以 cglib 介紹 AOP 在 java 的一個實現方式
作為第四篇,我們回顧一下 csharp 裡面比較常見動態編織實現方式emit
內容安排如下:
- emit aop demo
- Norns.Urd
1. emit aop demo
1.1 emit 介紹
emit 是類似 java 中ASM地位的一個底層功能實現,
不過不是轉化java位元組碼,而是生成dotnet 的 IL程式碼,
生成的IL程式碼將由內建的JIT編譯器直接編譯到記憶體中。
官方的介紹文件
emit 對大家來說都是很熟悉的api了,動態做什麼事基本都會想到它。
我們是可以使用emit 做到上篇java 的 cglib 一模一樣的動態編織的AOP效果,所以語言真的只是工具,怎麼玩取決於玩工具的人,demo 如下。
1.2 demo
程式碼
1.2.1 ProxyGenerator 簡單實現
public abstract class MethodInterceptor
{
public abstract object Invoke(object instance, MethodInfo methodInfo, object[] parameters, object returnValue);
}
public static class ProxyGenerator
{
private static ModuleBuilder moduleBuilder;
private static MethodInfo getMethodMethod = typeof(MethodBase).GetMethod("GetMethodFromHandle", new[] { typeof(RuntimeMethodHandle) });
private static MethodInfo invoke = typeof(MethodInterceptor).GetMethod("Invoke");
static ProxyGenerator()
{
var asmBuilder = AssemblyBuilder.DefineDynamicAssembly(new AssemblyName("EmitAopDemoTest"), AssemblyBuilderAccess.RunAndCollect);
moduleBuilder = asmBuilder.DefineDynamicModule("Proxy");
}
public static T Generate<T>(Type methodInterceptorType)
{
var proxyType = GenerateProxyType(typeof(T), methodInterceptorType);
return (T)Activator.CreateInstance(proxyType);
}
public static Type GenerateProxyType(Type type, Type methodInterceptorType)
{
var typeBuilder = moduleBuilder.DefineType($"{type.Name}Proxy", TypeAttributes.Class | TypeAttributes.Public, type);
foreach (var m in type.GetTypeInfo().DeclaredMethods)
{
var ps = m.GetParameters().Select(i => i.ParameterType).ToArray();
var newM = typeBuilder.DefineMethod(m.Name, MethodAttributes.HideBySig | MethodAttributes.NewSlot | MethodAttributes.Virtual, m.CallingConvention, m.ReturnType, ps);
CreateProxyMethod(methodInterceptorType, m, ps, newM);
typeBuilder.DefineMethodOverride(newM, m);
}
return typeBuilder.CreateType();
}
private static void CreateProxyMethod(Type methodInterceptorType, MethodInfo m, Type[] ps, MethodBuilder newM)
{
var il = newM.GetILGenerator();
var argsLocal = il.DeclareLocal(typeof(object[]));
var returnLocal = il.DeclareLocal(typeof(object));
// 初始化引數集合
il.Emit(OpCodes.Ldc_I4, ps.Length);
il.Emit(OpCodes.Newarr, typeof(object));
for (var i = 0; i < ps.Length; i++)
{
il.Emit(OpCodes.Dup);
il.Emit(OpCodes.Ldc_I4, i);
il.Emit(OpCodes.Ldarg, i + 1);
il.Emit(OpCodes.Box, ps[i]);
il.Emit(OpCodes.Stelem_Ref);
}
il.Emit(OpCodes.Stloc, argsLocal);
// 呼叫被代理方法
il.Emit(OpCodes.Ldarg, 0); // load this
for (var i = 0; i < ps.Length; i++)
{
il.Emit(OpCodes.Ldarg, i + 1);
}
il.Emit(OpCodes.Call, m);
il.Emit(OpCodes.Box, newM.ReturnType);
il.Emit(OpCodes.Stloc, returnLocal);
//呼叫方法後攔截器
il.Emit(OpCodes.Newobj, methodInterceptorType.GetConstructors().First());
il.Emit(OpCodes.Ldarg, 0); // load this
//載入方法資訊引數
il.Emit(OpCodes.Ldtoken, m);
il.Emit(OpCodes.Call, getMethodMethod);
il.Emit(OpCodes.Castclass, typeof(MethodInfo));
il.Emit(OpCodes.Ldloc, argsLocal);
il.Emit(OpCodes.Ldloc, returnLocal);
il.Emit(OpCodes.Callvirt, invoke);
il.Emit(OpCodes.Stloc, returnLocal);
// return
il.Emit(OpCodes.Ldloc, returnLocal);
il.Emit(OpCodes.Unbox_Any, newM.ReturnType);
il.Emit(OpCodes.Ret);
}
}
1.2.2 Test
internal class Program
{
private static void Main(string[] args)
{
RealClass proxy = ProxyGenerator.Generate<RealClass>(typeof(AddOneInterceptor));
var i = 5;
var j = 10;
Console.WriteLine($"{i} + {j} = {(i + j)}, but proxy is {proxy.Add(i, j)}");
}
}
結果:
5 + 10 = 15, but proxy is 16
2. Norns.Urd
至此,
本系列已經介紹完了所有的aop實現方式,
以csharp 平臺重點舉例介紹了AOP 靜態編織和動態編織 的方法。
並以 java cglib 表達了思想無程式語言邊界。
最後呢,介紹一下自己正在做的專案 Norns.Urd
Github: https://github.com/fs7744/Norns.Urd
Norns.Urd 是一個基於emit實現動態代理的輕量級AOP框架.
版本基於 netstandard2.0. 所以哪些.net 版本能用你懂的。
完成這個框架的目的主要出自於個人以下意願:
- 靜態AOP和動態AOP都實現一次
- 如果不實現DI,怎麼將AOP框架實現與其他現有DI框架整合
- 一個AOP 如何將 sync 和 async 方法同時相容且如何將實現選擇權完全交予使用者
希望該庫能對大家有些小小的作用, 開源不易,大家可以給個star 就非常nice 了
希望看過大家做碼農做的開心。