Norns.Urd 是什么?
Norns.Urd 是一个基于emit动态代理的轻量级AOP框架. 版本基于 netstandard2.0. 所以哪些.net 你能理解的版本。 完成此框架的目的主要来自以下个人意愿:
- 静态AOP和动态AOP都实现一次
- 如果不实现DI,怎么将AOP与其他现有框架一起实现DI框架集成
- 一个AOP 如何将 sync 和 async 方法兼容,如何将选择权完全交给用户
希望这个库能对大家有一些小作用 中文文档在:https://fs7744.github.io/Norns.Urd/zh-cn/index.html , github 地址:https://github.com/fs7744/Norns.Urd
顺便说一句,如果你不明白AOP同学们,可以看看这些文章: 面向切面的程序设计 什么是切面编程?AOP? AOP 实现的方法有多少?
Norns.Urd 有些设计
Norns.Urd的实现前提
由于Norns.Urd基于以下两个前提的实现
-
将 sync 和 async 方法兼容,如何将选择权完全交给用户
- 事实上,这是好的,工作量会变得两倍多,sync 和 async 完全分成两套实现。
- 为用户提供的Interceptor接口要提供 sync 和 async 混合在一套实现代码的方案中,毕竟不能强迫用户实现两套代码,很多场景用户不需要sync 和 async 实现两套代码的差异
-
不包括任何内置DI,但整体支持DI而作
- 事实上,如果内置的话DI支持容器 generic 场景变得很简单,毕竟从DI容器中的实例对象必须有明确的类型,但现在有这么多的实现库,我不想为一些场景实现很多功能(我真的很懒,否则库不会写这么久)
- 但是DI容器解耦真的很好,我经常从中受益,减少很多代码修改,所以做一个aop库必须考虑基础DI支持容器,这样,di 支持的 open generic / 支持自定义实例化方法,aop里面还得提供用户调用DI方法,否则不好用 (这样算下来,我真的懒吗?我在给自己挖坑吗?)
如何设计解决方案?
目前的计划并不一定完美,只是暂时解决了问题 (请告诉我有更好的计划,我迫切需要学习)
用户提供什么样的拦截器编写模式?
以前接触过别的aop许多人需要将拦截代码分为实现框架 方法前 / 方法后 / 有异常等。个人认为这种形式在一定程度上影响了拦截器实现的代码思维,总觉得不够流畅。
但是像 ASP.NET Core Middleware感觉很好,如下图和代码:
app.Run(async context => {
await context.Response.WriteAsync("Hello, World!"); });
拦截器也应该这样做,所以拦截器的代码应该是这样的:
public class ConsoleInterceptor {
public async Task InvokeAsync(Context context, Delegate next) {
Console.WriteLine("Hello, World!"); await next(context); } }
sync 和 async 方法如何拆分?又如何能合并在一起呢?用户有怎么自己选择实现sync 还是 async 还是两者都实现了?
public delegate Task AsyncAspectDelegate(AspectContext context); public delegate void AspectDelegate(AspectContext context); // 拆分: // 由AspectDelegate 和 AsyncAspectDelegate 建立两套完全区分 sync 和 async 的Middleware调用链,具体使用哪个由具体被拦截的方法本身决定 public abstract class AbstractInterceptor : IInterceptor {
public virtual void Invoke(AspectContext context, AspectDelegate next) {
InvokeAsync(context, c => {
next(c); return Task.CompletedTask; }).ConfigureAwait(false) .GetAwaiter() .GetResult(); } // 合并: // 默认实现转换方法内容,这样各种拦截器都可以混在一个Middleware调用链中 public abstract Task InvokeAsync(AspectContext context, AsyncAspectDelegate next); // 用户自主性选择: // 同时提供sync 和 async 拦截器方法可以重载,用户就可以自己选择了 // 所以用户在 async 中可以调用专门的未异步优化代码了,也不用说在 sync 中必须 awit 会影响性能了, // 你认为影响性能,你在乎就自己都重载,不在乎那就自己选 }
没有内置DI,如何兼容其他DI框架呢?
DI框架都有注册类型,我们可以通过 emit 生成代理类,替换原本的注册,就可以做到兼容。
当然每种DI框架都需要定制化的实现一些代码才能支持(唉,又是工作量呀)
AddTransient<IMTest>(x => new NMTest())
, 类似这样的实例化方法怎么支持呢?
由于这种DI框架的用法,无法通过Func函数拿到实际会使用的类型,只能根据IMTest定义通过emit 生成 桥接代理类型,其伪码类似如下:
interface IMTest
{
int Get(int i);
}
class IMTestProxy : IMTest
{
IMTest instance = (x => new NMTest())();
int Get(int i) => instance.Get(i);
}
.AddTransient(typeof(IGenericTest<,>), typeof(GenericTest<,>))
类似这样的 Open generic 怎么支持呢?
其实对于泛型,我们通过 emit 生成泛型类型一点问题都没有,唯一的难点是不好生成 Get<T>()
这样的方法调用, 因为IL需要反射找到的具体方法,比如Get<int>()
Get<bool>()
等等,不能是不明确的 Get<T>()
。
要解决这个问题就只能将实际的调用延迟到运行时调用再生成具体的调用,伪码大致如下:
interface GenericTest<T,R>
{
T Get<T>(T i) => i;
}
class GenericTestProxy<T,R> : GenericTest<T,R>
{
T Get<T>(T i) => this.GetType().GetMethod("Get<T>").Invoke(i);
}