动手造轮子:实现一个简单的 AOP 框架
动手造轮子:实现一个简单的 AOP 框架
Intro
最近实现了一个 AOP 框架 -- FluentAspects,API 基本稳定了,写篇文章分享一下这个 AOP 框架的设计。
整体设计
概览

IProxyTypeFactory
用来生成代理类型,默认提供了基于 Emit 动态代理的实现,基于接口设计,可以扩展为其他实现方式
接口定义如下:
public interface IProxyTypeFactory
{
Type CreateProxyType(Type serviceType);
Type CreateProxyType(Type serviceType, Type implementType);
}
IProxyFactory
用来生成代理实例,默认实现是基于 IProxyTypeFactory 生成代理类型之后创建实例
接口定义如下:
public interface IProxyFactory
{
object CreateProxy(Type serviceType, object[] arguments);
object CreateProxy(Type serviceType, Type implementType, params object[] arguments);
object CreateProxyWithTarget(Type serviceType, object implement, object[] arguments);
}
IInvocation
执行上下文,默认实现就是方法执行的上下文,包含了代理方法信息、被代理的方法信息、方法参数,返回值以及用来自定义扩展的一个 Properties 属性
public interface IInvocation
{
MethodInfo ProxyMethod { get; }
object ProxyTarget { get; }
MethodInfo Method { get; }
object Target { get; }
object[] Arguments { get; }
Type[] GenericArguments { get; }
object ReturnValue { get; set; }
Dictionary<string, object> Properties { get; }
}
IInterceptor
拦截器,用来定义公用的处理逻辑,方法拦截处理方法
接口定义如下:
public interface IInterceptor
{
Task Invoke(IInvocation invocation, Func<Task> next);
}
invocation 是方法执行的上下文,next 代表后续的逻辑处理,类似于 asp.net core 里的 next ,如果不想执行方面的方法不执行 next 逻辑即可
IInterceptorResolver
用来根据当前的执行上下文获取到要执行的拦截器,默认是基于 FluentAPI 的实现,但是如果你特别想用基于 Attribute 的也是可以的,默认提供了一个 AttributeInterceotorResovler,你也可以自定义一个适合自己的 InterceptorResolver
public interface IInterceptorResolver
{
IReadOnlyList<IInterceptor> ResolveInterceptors(IInvocation invocation);
}
IInvocationEnricher
上面 IInvocation 的定义中有一个用于扩展的 Properties,这个 enricher 主要就是基于 Properties 来丰富执行上下文信息的,比如说记录 TraceId 等请求链路追踪数据,构建方法执行链路等
public interface IEnricher<in TContext>
{
void Enrich(TContext context);
}
public interface IInvocationEnricher : IEnricher<IInvocation>
{
}
AspectDelegate
AspectDelegate 是用来将构建要执行的代理方法的方法体的,首先执行注册的 InvocationEnricher,丰富上下文信息,然后根据执行上下文获取要执行的拦截器,构建一个执行委托,执行拦截器逻辑
// apply enrichers
foreach (var enricher in FluentAspects.AspectOptions.Enrichers)
{
try
{
enricher.Enrich(invocation);
}
catch (Exception ex)
{
InvokeHelper.OnInvokeException?.Invoke(ex);
}
}
// get delegate
var builder = PipelineBuilder.CreateAsync(completeFunc);
foreach (var interceptor in interceptors)
{
builder.Use(interceptor.Invoke);
}
return builder.Build();
更多信息可以参考源码: https://github.com/WeihanLi/WeihanLi.Common/blob/dev/src/WeihanLi.Common/Aspect/AspectDelegate.cs
使用示例
推荐和依赖注入结合使用,主要分为以微软的注入框架为例,有两种使用方式,一种是手动注册代理服务,一种是自动批量注册代理服务,来看下面的实例就明白了
服务注册:
IServiceCollection services = new ServiceCollection();
services.AddFluentAspects(options =>
{
// 注册拦截器配置
options.NoInterceptProperty<IFly>(f => f.Name);
options.InterceptAll()
.With<LogInterceptor>()
;
options.InterceptMethod<DbContext>(x => x.Name == nameof(DbContext.SaveChanges)
|| x.Name == nameof(DbContext.SaveChangesAsync))
.With<DbContextSaveInterceptor>()
;
options.InterceptMethod<IFly>(f => f.Fly())
.With<LogInterceptor>();
options.InterceptType<IFly>()
.With<LogInterceptor>();
// 注册 InvocationEnricher
options
.WithProperty("TraceId", "121212")
;
});
使用方式一,手动注册代理服务:
为了方便使用,提供了一些扩展方法:
services.AddTransientProxy<IFly, MonkeyKing>();
services.AddSingletonProxy<IEventBus, EventBus>();
services.AddDbContext<TestDbContext>(options =>
{
options.UseInMemoryDatabase("Test");
});
services.AddScopedProxy<TestDbContext>();
var serviceProvider = services.BuildServiceProvider();
使用方式二,批量自动注册代理服务:
services.AddTransient<IFly, MonkeyKing>();
services.AddSingleton<IEventBus, EventBus>();
services.AddDbContext<TestDbContext>(options =>
{
options.UseInMemoryDatabase("Test");
});
var serviceProvider = services.BuildFluentAspectsProvider(options =>
{
options.InterceptAll()
.With<TestOutputInterceptor>(output);
});
// 使用 Castle 来生成代理
var serviceProvider = services.BuildFluentAspectsProvider(options =>
{
options.InterceptAll()
.With<TestOutputInterceptor>(output);
}, builder => builder.UseCastle());
// 忽略命名空间为 Microsoft/System 的服务类型
var serviceProvider = services.BuildFluentAspectsProvider(options =>
{
options.InterceptAll()
.With<TestOutputInterceptor>(output);
}, builder => builder.UseCastle(), t=> t.Namespace != null && (t.Namespace.StartWith("Microsft") ||t.Namespace.StartWith("Microsft")));
More
这个框架还不是很完善,有一些地方还是需要优化的,目前还是在我自己的类库中,因为我的类库里要支持 net45,所以有一些不好的设计改起来不太方便,打算迁移出来作为一个单独的组件,直接基于 netstandard2.0/netstandard2.1, 甩掉 netfx 的包袱。
Reference
动手造轮子:实现一个简单的 AOP 框架的更多相关文章
- 动手造轮子:实现简单的 EventQueue
动手造轮子:实现简单的 EventQueue Intro 最近项目里有遇到一些并发的问题,想实现一个队列来将并发的请求一个一个串行处理,可以理解为使用消息队列处理并发问题,之前实现过一个简单的 Eve ...
- 动手造轮子:实现一个简单的 EventBus
动手造轮子:实现一个简单的 EventBus Intro EventBus 是一种事件发布订阅模式,通过 EventBus 我们可以很方便的实现解耦,将事件的发起和事件的处理的很好的分隔开来,很好的实 ...
- 动手造轮子:基于 Redis 实现 EventBus
动手造轮子:基于 Redis 实现 EventBus Intro 上次我们造了一个简单的基于内存的 EventBus,但是如果要跨系统的话就不合适了,所以有了这篇基于 Redis 的 EventBus ...
- 动手写一个简单的Web框架(模板渲染)
动手写一个简单的Web框架(模板渲染) 在百度上搜索jinja2,显示的大部分内容都是jinja2的渲染语法,这个不是Web框架需要做的事,最终,居然在Werkzeug的官方文档里找到模板渲染的代码. ...
- 动手写一个简单的Web框架(Werkzeug路由问题)
动手写一个简单的Web框架(Werkzeug路由问题) 继承上一篇博客,实现了HelloWorld,但是这并不是一个Web框架,只是自己手写的一个程序,别人是无法通过自己定义路由和返回文本,来使用的, ...
- 动手写一个简单的Web框架(HelloWorld的实现)
动手写一个简单的Web框架(HelloWorld的实现) 关于python的wsgi问题可以看这篇博客 我就不具体阐述了,简单来说,wsgi标准需要我们提供一个可以被调用的python程序,可以实函数 ...
- 自己动手设计并实现一个linux嵌入式UI框架(设计)
看了"自己动手设计并实现一个linux嵌入式UI框架"显然没有尽兴,因为还没有看到庐山真面目,那我今天继续,先来说说,我用到了哪些知识背景.如:C语言基础知识,尤其是指针.函数指针 ...
- 用Python写一个简单的Web框架
一.概述 二.从demo_app开始 三.WSGI中的application 四.区分URL 五.重构 1.正则匹配URL 2.DRY 3.抽象出框架 六.参考 一.概述 在Python中,WSGI( ...
- 一个简单的web框架实现
一个简单的web框架实现 #!/usr/bin/env python # -- coding: utf-8 -- __author__ = 'EchoRep' from wsgiref.simple_ ...
随机推荐
- golang如何优雅的编写事务代码
目录 前言 需求 烂代码示例 重构套路 一.提前返回去除if嵌套 二.goto+label提取重复代码 三.封装try-catch统一捕获panic 前言 新手程序员概有如下特点 if嵌套特别多.重复 ...
- 8分钟为你详解React、Angular、Vue三大前端技术
[引言] 当前世界中,技术发展非常迅速并且变化迅速,开发者需要更多的开发工具来解决不同的问题.本文就对于当下主流的前端开发技术React.Vue.Angular这三个框架做个相对详尽的探究,目的是为了 ...
- parrot os安装drozer
dz需要支持 大部分parrot都装好了,只有Protobuf未安装 apt install Protobuf 安装dz 下面下载https://labs.f-secure.com/tools/dro ...
- remote desktop能实现什么?远程桌面管理的意义是什么?
随着互联网时代的发展,向人请教来说,视频教学已经不算便捷了,而远程桌面就发挥了重要作用.它意味着您可以从家里连接到工作计算机,并访问所有应用程序.文件和网络资源,好像正坐在工作计算机前面.您可以让程序 ...
- Cypress系列(5)- 自定义 Cypress
如果想从头学起Cypress,可以看下面的系列文章哦 https://www.cnblogs.com/poloyy/category/1768839.html 前言 Cypress 不仅支持用户自定义 ...
- 【算法】Tarjan算法求强连通分量
概念: 在有向图G中,如果两个定点u可以到达v,并且v也可以到达u,那么我们称这两个定点强连通. 如果有向图G的任意两个顶点都是强连通的,那么我们称G是一个强连通图. 一个有向图中的最大强连通子图,称 ...
- Rocket - interrupts - Nodes
https://mp.weixin.qq.com/s/BlW4y0Ez1kppxvSHAla31A 简单介绍interrupts相关的diplomacy节点. 1. IntImp 中断节点实现: 1) ...
- 面试官问我会不会Elasticsearch,我语塞了...
少点代码,多点头发 本文已经收录至我的GitHub,欢迎大家踊跃star 和 issues. https://github.com/midou-tech/articles 从今天开始准备给大家带来全新 ...
- Java实现 LeetCode 773 滑动谜题(BFS)
773. 滑动谜题 在一个 2 x 3 的板上(board)有 5 块砖瓦,用数字 1~5 来表示, 以及一块空缺用 0 来表示. 一次移动定义为选择 0 与一个相邻的数字(上下左右)进行交换. 最终 ...
- Java实现 LeetCode 494 目标和
494. 目标和 给定一个非负整数数组,a1, a2, -, an, 和一个目标数,S.现在你有两个符号 + 和 -.对于数组中的任意一个整数,你都可以从 + 或 -中选择一个符号添加在前面. 返回可 ...