Norns.Urd 是一个基于 emit 实现动态代理的轻量级 AOP 框架。
版本基于 netstandard2.0. 所以哪些.net 版本能用你懂的。
完成这个框架的目的主要出自于个人以下意愿:
希望该库能对大家有些小小的作用
对了,如果不了解AOP的同学,可以看看这些文章:
只是一个简单性能测试,不代表全部场景,也没有故意对比,
Castle 和 AspectCore 都是非常优秀的库,
Norns.Urd 很多实现都是参考了Castle 和 AspectCore的源码的。
BenchmarkDotNet=v0.12.1, OS=Windows 10.0.18363.1198 (1909/November2018Update/19H2)
Intel Core i7-9750H CPU 2.60GHz, 1 CPU, 12 logical and 6 physical cores
.NET Core SDK=5.0.100
[Host] : .NET Core 5.0.0 (CoreCLR 5.0.20.51904, CoreFX 5.0.20.51904), X64 RyuJIT
DefaultJob : .NET Core 5.0.0 (CoreCLR 5.0.20.51904, CoreFX 5.0.20.51904), X64 RyuJIT
Method | Mean | Error | StdDev | Median | Gen 0 | Gen 1 | Gen 2 | Allocated |
---|---|---|---|---|---|---|---|---|
TransientInstanceCallSyncMethodWhenNoAop | 69.10 ns | 1.393 ns | 2.512 ns | 69.70 ns | 0.0178 | - | - | 112 B |
TransientInstanceCallSyncMethodWhenNornsUrd | 148.38 ns | 2.975 ns | 5.588 ns | 145.76 ns | 0.0534 | - | - | 336 B |
TransientInstanceCallSyncMethodWhenCastle | 222.48 ns | 0.399 ns | 0.312 ns | 222.50 ns | 0.0815 | - | - | 512 B |
TransientInstanceCallSyncMethodWhenAspectCore | 576.04 ns | 7.132 ns | 10.229 ns | 573.46 ns | 0.1030 | - | - | 648 B |
TransientInstanceCallAsyncMethodWhenNoAop | 114.61 ns | 0.597 ns | 0.499 ns | 114.58 ns | 0.0408 | - | - | 256 B |
TransientInstanceCallAsyncMethodWhenNornsUrd | 206.36 ns | 0.937 ns | 0.830 ns | 206.18 ns | 0.0763 | - | - | 480 B |
TransientInstanceCallAsyncMethodWhenCastle | 250.98 ns | 3.315 ns | 3.101 ns | 252.16 ns | 0.1044 | - | - | 656 B |
TransientInstanceCallAsyncMethodWhenAspectCore | 576.00 ns | 4.160 ns | 3.891 ns | 574.99 ns | 0.1373 | - | - | 864 B |
这是一个简单的全局AOP拦截的简单示例,具体详细示例代码可以参阅Examples.WebApi
创建 ConsoleInterceptor.cs
using Norns.Urd;
using Norns.Urd.Reflection;
using System;
using System.Threading.Tasks;
namespace Examples.WebApi
{
public class ConsoleInterceptor : AbstractInterceptor
{
public override async Task InvokeAsync(AspectContext context, AsyncAspectDelegate next)
{
Console.WriteLine($"{context.Service.GetType().GetReflector().FullDisplayName}.{context.Method.GetReflector().DisplayName}");
await next(context);
}
}
}
设置 WeatherForecastController 的方法为 virtual
[ApiController]
[Route("[controller]")]
public class WeatherForecastController : ControllerBase
{
[HttpGet]
public virtual IEnumerable<WeatherForecast> Get() => test.Get();
}
AddControllersAsServices
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.AddControllers().AddControllersAsServices();
}
设置di 容器启用aop 功能
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.AddControllers().AddControllersAsServices();
services.ConfigureAop(i => i.GlobalInterceptors.Add(new ConsoleInterceptor()));
}
运行程序
你会在控制台看见如下输出
Norns.Urd.DynamicProxy.Generated.WeatherForecastController_Proxy_Inherit.IEnumerable<WeatherForecast> Get()
在Norns.Urd中,Interceptor 拦截器是用户可以在方法插入自己的逻辑的核心。
拦截器定义了标准结构为IInterceptor
public interface IInterceptor
{
// 用户可以通过Order自定义拦截器顺序,排序方式为ASC,全局拦截器和显示拦截器都会列入排序中
int Order { get; }
// 同步拦截方法
void Invoke(AspectContext context, AspectDelegate next);
// 异步拦截方法
Task InvokeAsync(AspectContext context, AsyncAspectDelegate next);
// 可以设置拦截器如何选择过滤是否拦截方法,除了这里还有NonAspectAttribute 和全局的NonPredicates可以影响过滤
bool CanAspect(MethodInfo method);
}
拦截器实际从设计上只有IInterceptor
这一个统一的定义,不过由于csharp的单继承和Attribute
的语言限制,所以有AbstractInterceptorAttribute
和 AbstractInterceptor
两个类。
public abstract class AbstractInterceptorAttribute : Attribute, IInterceptor
{
public virtual int Order { get; set; }
public virtual bool CanAspect(MethodInfo method) => true;
// 默认提供在同步拦截器方法中转换异步方法为同步方式调用,存在一些性能损失,如果用户想要减少这方面的损耗,可以选择重载实现。
public virtual void Invoke(AspectContext context, AspectDelegate next)
{
InvokeAsync(context, c =>
{
next(c);
return Task.CompletedTask;
}).ConfigureAwait(false)
.GetAwaiter()
.GetResult();
}
// 默认只需要实现异步拦截器方法
public abstract Task InvokeAsync(AspectContext context, AsyncAspectDelegate next);
}
一个拦截器实现举例:
public class AddTenInterceptorAttribute : AbstractInterceptorAttribute
{
public override void Invoke(AspectContext context, AspectDelegate next)
{
next(context);
AddTen(context);
}
private static void AddTen(AspectContext context)
{
if (context.ReturnValue is int i)
{
context.ReturnValue = i + 10;
}
else if(context.ReturnValue is double d)
{
context.ReturnValue = d + 10.0;
}
}
public override async Task InvokeAsync(AspectContext context, AsyncAspectDelegate next)
{
await next(context);
AddTen(context);
}
}
InterceptorAttribute
拦截器使用方式
Attribute
,如[AddTenInterceptor]
public interface IGenericTest<T, R> : IDisposable
{
// or
//[AddTenInterceptor]
T GetT();
}
public void ConfigureServices(IServiceCollection services)
{
services.ConfigureAop(i => i.GlobalInterceptors.Add(new AddTenInterceptorAttribute()));
}
和 AbstractInterceptorAttribute
几乎一模一样,不过不是Attribute
,不能用于对应场景,只能在全局拦截器中使用。其实本身就是提供给用户用于不想Attribute
场景简化Interceptor创建。
Interceptor
拦截器使用方式
只能在全局拦截器中设置
public void ConfigureServices(IServiceCollection services)
{
services.ConfigureAop(i => i.GlobalInterceptors.Add(new AddSixInterceptor()));
}
public void ConfigureServices(IServiceCollection services)
{
services.ConfigureAop(i => i.GlobalInterceptors.Add(new AddSixInterceptor()));
}
AbstractInterceptorAttribute
在所有需要的地方都显示声明[AddTenInterceptor]
public interface IGenericTest<T, R> : IDisposable
{
// or
//[AddTenInterceptor]
T GetT();
}
所以用户觉得怎么样方便就怎么用就好了
Norns.Urd 提供如下三种过滤方式
services.ConfigureAop(i => i.NonPredicates.AddNamespace("Norns")
.AddNamespace("Norns.*")
.AddNamespace("System")
.AddNamespace("System.*")
.AddNamespace("Microsoft.*")
.AddNamespace("Microsoft.Owin.*")
.AddMethod("Microsoft.*", "*"));
[NonAspect]
public interface IGenericTest<T, R> : IDisposable
{
}
public class ParameterInjectInterceptor : AbstractInterceptor
{
public override bool CanAspect(MethodInfo method)
{
return method.GetReflector().Parameters.Any(i => i.IsDefined<InjectAttribute>());
}
}
如果你向DI框架注册没有真正有具体实现的 Interface
和Abstract Class
, Norns.Urd 会实现默认的子类型。
为什么提供这样的功能呢?
这是为声明式编码思想提供一些底层实现支持,这样有更多的同学可以自定义自己的一些声明式库,简化代码,比如实现一个 声明式HttpClient
后面会完成一个简单的httpclient作为示例,这里先做个简单demo
public class AddTenAttribute : AbstractInterceptorAttribute
{
public override void Invoke(AspectContext context, AspectDelegate next)
{
next(context);
AddTen(context);
}
private static void AddTen(AspectContext context)
{
if (context.ReturnValue is int i)
{
context.ReturnValue = i + 10;
}
else if(context.ReturnValue is double d)
{
context.ReturnValue = d + 10.0;
}
}
public override async Task InvokeAsync(AspectContext context, AsyncAspectDelegate next)
{
await next(context);
AddTen(context);
}
}
[AddTen]
public interface IAddTest
{
int AddTen();
// 对于接口中的默认实现,并不会被Norns.Urd替代,这样可以提供某些场景用户可以自定义实现逻辑
public int NoAdd() => 3;
}
services.AddTransient<IAddTest>();
services.ConfigureAop();
[ApiController]
[Route("[controller]")]
public class WeatherForecastController : ControllerBase
{
IAddTest a;
public WeatherForecastController(IAddTest b)
{
a = b;
}
[HttpGet]
public int GetAddTen() => a.AddTen();
}
InjectAttribute
是对 Interface和Abstract Class的默认实现的功能补充,
特别是在做声明式client之类,提供自定义设置,比如interface 默认接口实现时,
用户可能需要从DI中获取实例,所以这里提供两种方式做一些补充。
方法参数可以设置InjectAttribute
:
示例:
public interface IInjectTest
{
public ParameterInjectTest T([Inject] ParameterInjectTest t = null) => t;
}
public interface IInjectTest
{
[Inject]
ParameterInjectInterceptorTest PT { get; set; }
}
按照业界编码习惯, field 不推荐没有赋值就是使用,所以该功能会导致代码检查出现需要修复的问题
public class ParameterInjectTest : IInjectTest
{
[Inject]
ParameterInjectInterceptorTest ft;
}
public class DoFallbackTest
{
[Fallback(typeof(TestFallback))] // just need set Interceptor Type
public virtual int Do(int i)
{
throw new FieldAccessException();
}
[Fallback(typeof(TestFallback))]
public virtual Task<int> DoAsync(int i)
{
throw new FieldAccessException();
}
}
public class TestFallback : AbstractInterceptor
{
public override void Invoke(AspectContext context, AspectDelegate next)
{
context.ReturnValue = (int)context.Parameters[0];
}
public override Task InvokeAsync(AspectContext context, AsyncAspectDelegate next)
{
var t = Task.FromResult((int)context.Parameters[0]);
context.ReturnValue = t;
return t;
}
}
Polly is .NET resilience and transient-fault-handling library.
这里通过Norns.Urd将Polly的各种功能集成为更加方便使用的功能
EnablePolly()
如:
new ServiceCollection()
.AddTransient<DoTimeoutTest>()
.ConfigureAop(i => i.EnablePolly())
[Timeout(seconds: 1)] // timeout 1 seconds, when timeout will throw TimeoutRejectedException
double Wait(double seconds);
[Timeout(timeSpan: "00:00:00.100")] // timeout 100 milliseconds, only work on async method when no CancellationToken
async Task<double> WaitAsync(double seconds, CancellationToken cancellationToken = default);
[Timeout(timeSpan: "00:00:01")] // timeout 1 seconds, but no work on async method when no CancellationToken
async Task<double> NoCancellationTokenWaitAsync(double seconds);
[Retry(retryCount: 2, ExceptionType = typeof(AccessViolationException))] // retry 2 times when if throw Exception
void Do()
[CircuitBreaker(exceptionsAllowedBeforeBreaking: 3, durationOfBreak: "00:00:01")]
//or
[AdvancedCircuitBreaker(failureThreshold: 0.1, samplingDuration: "00:00:01", minimumThroughput: 3, durationOfBreak: "00:00:01")]
void Do()
[Bulkhead(maxParallelization: 5, maxQueuingActions: 10)]
void Do()
由于Norns.Urd的实现基于以下两点前提
将 sync 和 async 方法同时兼容且如何将实现选择权完全交予用户
不包含任何内置DI,但要整体都为支持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);
}
}
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框架都有注册类型,我们可以通过 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);
}
Norns.Urd.HttpClient Norns.Urd.HttpClient 基于AOP框架 Norns.Urd实现, 是对 System.Net.Http下的 HttpClient封装,让大家只需简单在接口定义就可以实现http的调用,可以减少一些重复代码的书写。 可以和已有的 Norns.Urd.Extensions.Polly 以及 Norns.Urd.Caching.Memory 配
本文向大家介绍Asp.Net Core轻量级Aop解决方案:AspectCore,包括了Asp.Net Core轻量级Aop解决方案:AspectCore的使用技巧和注意事项,需要的朋友参考一下 什么是AspectCore Project ? AspectCore Project 是适用于Asp.Net Core 平台的轻量级 Aop(Aspect-oriented programming) 解决
本文向大家介绍轻量级javascript 框架Backbone使用指南,包括了轻量级javascript 框架Backbone使用指南的使用技巧和注意事项,需要的朋友参考一下 Backbone 是一款基于模型-视图-控制器 MVC 模式的轻量级javascript 框架 ,可以用来帮助开发人员创建单页Web应用。 借助Backbone 我们可以使用REST的方式来最小化客户端和服务器间的数据传输,
本文向大家介绍前端轻量级MVC框架CanJS详解,包括了前端轻量级MVC框架CanJS详解的使用技巧和注意事项,需要的朋友参考一下 选择正确的库 创建一个JS APP没有好的工具是很有难度的,jQuery只是操作DOM的库,没有提供任何创建APP的基础,这就是为什么我们要一个类似CanJS的专门的库。 CanJS 是一个轻量级的MVC库,提供你创建一个JS APP所需的工具。 CanJS 是一个轻
问题内容: 是否有一个提供发布/订阅模式的Java轻量级框架? 一些理想的功能 支持泛型 向发布者注册多个订阅者 API主要是接口和一些有用的实现 完全不需要内存,持久性和事务保证。 我了解JMS,但这对我来说太过分了。发布/订阅的数据是文件系统扫描的结果,扫描结果被馈送到另一个组件进行处理,然后在将其馈给另一个组件之前进行处理,依此类推。 编辑:所有在同一过程中。bean的PropertyCha
本文向大家介绍浅谈Android轻量级的数据缓存框架RxCache,包括了浅谈Android轻量级的数据缓存框架RxCache的使用技巧和注意事项,需要的朋友参考一下 请求网络数据是在安卓开发中使用最频繁的一个功能,网络请求的体验决定了用户对整个APP的感觉,因此合理地使用缓存对网络请求的数据进行处理极为重要。合理的进行缓存和网络请求,可以为APP带来更优秀的体验。图片的缓存有Picasso、Gl
问题内容: 我一直在使用jQuery在基于Web的应用程序中完成整个AJAX魔术。但是,我来到了一个决定,我并不需要所有这些神奇功能jQuery有,除了它的AJAX功能(例如,,,和)。 您能推荐轻量级的跨浏览器AJAX库/框架(最大10 kb)吗? 问题答案: 您可以通过删除不需要的模块来缩小jQuery的大小,只需修改Makefile文件即可。