实现一个基于动态代理的 AOP

实现一个基于动态代理的 AOP

Intro

上次看基于动态代理的 AOP 框架实现,立了一个 Flag, 自己写一个简单的 AOP 实现示例,今天过来填坑了

目前的实现是基于 Emit 来做的,后面有时间再写一个基于 Roslyn 来实现的示例

效果演示

演示代码:

切面逻辑定义:

public class TryInvokeAspect : AbstractAspect
{public override void Invoke(MethodInvocationContext methodInvocationContext, Action next){Console.WriteLine($"begin invoke method {methodInvocationContext.ProxyMethod.Name} in {GetType().Name}...");try{next();}catch (Exception e){Console.WriteLine($"Invoke {methodInvocationContext.ProxyMethod.DeclaringType?.FullName}.{methodInvocationContext.ProxyMethod.Name} exception");Console.WriteLine(e);}Console.WriteLine($"end invoke method {methodInvocationContext.ProxyMethod.Name} in {GetType().Name}...");}
}
public class TryInvoke1Aspect : AbstractAspect
{public override void Invoke(MethodInvocationContext methodInvocationContext, Action next){Console.WriteLine($"begin invoke method {methodInvocationContext.ProxyMethod.Name} in {GetType().Name}...");try{next();}catch (Exception e){Console.WriteLine($"Invoke {methodInvocationContext.ProxyMethod.DeclaringType?.FullName}.{methodInvocationContext.ProxyMethod.Name} exception");Console.WriteLine(e);}Console.WriteLine($"end invoke method {methodInvocationContext.ProxyMethod.Name} in {GetType().Name}...");}
}
public class TryInvoke2Aspect : AbstractAspect
{public override void Invoke(MethodInvocationContext methodInvocationContext, Action next){Console.WriteLine($"begin invoke method {methodInvocationContext.ProxyMethod.Name} in {GetType().Name}...");try{next();}catch (Exception e){Console.WriteLine($"Invoke {methodInvocationContext.ProxyMethod.DeclaringType?.FullName}.{methodInvocationContext.ProxyMethod.Name} exception");Console.WriteLine(e);}Console.WriteLine($"end invoke method {methodInvocationContext.ProxyMethod.Name} in {GetType().Name}...");}
}

测试服务定义

// 测试接口定义
public interface ITestService
{[TryInvokeAspect]void Test();[TryInvokeAspect][TryInvoke1Aspect][TryInvoke2Aspect]void Test1(int a, string b);[TryInvokeAspect]string Test2();[TryInvokeAspect]int Test3();
}
// 测试接口实例定义
public class TestService : ITestService
{[TryInvokeAspect]public virtual string TestProp { get; set; }public void Test(){Console.WriteLine("test invoked");}public virtual void Test1(int a, string b){Console.WriteLine($"a:{a}, b:{b}");}[TryInvoke1Aspect]public virtual string Test2(){return "Hello";}[TryInvokeAspect]public virtual int Test3(){return 1;}
}

测试代码:

//var testService = ProxyGenerator.Instance.CreateInterfaceProxy<ITestService>();
var testService = ProxyGenerator.Instance.CreateInterfaceProxy<ITestService, TestService>();
// var testService = ProxyGenerator.Instance.CreateClassProxy<TestService>();
// testService.TestProp = "12133";
testService.Test();
Console.WriteLine();
testService.Test1(1, "str");
var a = testService.Test2();
var b = testService.Test3();
Console.WriteLine($"a:{a}, b:{b}");
Console.ReadLine();

输出效果:

整体结构

ProxyGenerator

ProxyGenerator 代理生成器,用来创建代理对象

public class ProxyGenerator
{public static readonly ProxyGenerator Instance = new ProxyGenerator();public object CreateInterfaceProxy(Type interfaceType){var type = ProxyUtil.CreateInterfaceProxy(interfaceType);return Activator.CreateInstance(type);}public object CreateInterfaceProxy(Type interfaceType, Type implementationType){var type = ProxyUtil.CreateInterfaceProxy(interfaceType, implementationType);return Activator.CreateInstance(type);}public object CreateClassProxy(Type classType, params Type[] interfaceTypes){var type = ProxyUtil.CreateClassProxy(classType, interfaceTypes);return Activator.CreateInstance(type);}public object CreateClassProxy(Type classType, Type implementationType, params Type[] interfaceTypes){var type = ProxyUtil.CreateClassProxy(implementationType, interfaceTypes);return Activator.CreateInstance(type);}
}

为了更方便的使用泛型,定义了几个扩展方法:

public static class Extensions
{public static TInterface CreateInterfaceProxy<TInterface>(this ProxyGenerator proxyGenerator) =>(TInterface)proxyGenerator.CreateInterfaceProxy(typeof(TInterface));public static TInterface CreateInterfaceProxy<TInterface, TImplement>(this ProxyGenerator proxyGenerator) where TImplement : TInterface =>(TInterface)proxyGenerator.CreateInterfaceProxy(typeof(TInterface), typeof(TImplement));public static TClass CreateClassProxy<TClass>(this ProxyGenerator proxyGenerator) where TClass : class =>(TClass)proxyGenerator.CreateClassProxy(typeof(TClass));public static TClass CreateClassProxy<TClass, TImplement>(this ProxyGenerator proxyGenerator) where TImplement : TClass =>(TClass)proxyGenerator.CreateClassProxy(typeof(TClass), typeof(TImplement));
}

AbstractAspect

AbstractAspect 切面抽象类,继承了 Attribute,可以继承它来实现自己的切面逻辑

public abstract class AbstractAspect : Attribute
{public abstract void Invoke(MethodInvocationContext methodInvocationContext, Action next);
}

MethodInvocationContext

MethodInvocationContext 方法执行上下文,包含了执行方法时的原始方法信息以及代理方法信息,方法参数,方法返回值

public class MethodInvocationContext
{public MethodInfo ProxyMethod { get; }public MethodInfo MethodBase { get; }public object ProxyTarget { get; }public object Target { get; }public object[] Parameters { get; }public object ReturnValue { get; set; }public MethodInvocationContext(MethodInfo method, MethodInfo methodBase, object proxyTarget, object target, object[] parameters){ProxyMethod = method;MethodBase = methodBase;ProxyTarget = proxyTarget;Target = target;Parameters = parameters;}
}

代理方法逻辑

生成代理的方法在上一节已经介绍,主要就是通过 Emit 生成代理类,要写一些 Emit 代码, Emit 不在今天的讨论范围内,这里不多介绍,生成代理方法的时候,会检查方法上的 Attribute ,如果是切面逻辑就注册切面逻辑,最后像 asp.net core 中间件一样组装在一起拼成一个委托。

核心代码如下:

// var invocation = new MethodInvocationContext(method, methodBase, this, parameters);
var localAspectInvocation = il.DeclareLocal(typeof(MethodInvocationContext));
il.Emit(OpCodes.Ldloc, localCurrentMethod);
il.Emit(OpCodes.Ldloc, localMethodBase);
il.Emit(OpCodes.Ldarg_0);
il.Emit(OpCodes.Ldloc, localTarget);
il.Emit(OpCodes.Ldloc, localParameters);
// 创建一个 MethodInvocationContext 实例
il.New(typeof(MethodInvocationContext).GetConstructors()[0]); 
il.Emit(OpCodes.Stloc, localAspectInvocation);
// AspectDelegate.InvokeAspectDelegate(invocation);
il.Emit(OpCodes.Ldloc, localAspectInvocation);
var invokeAspectDelegateMethod =typeof(AspectDelegate).GetMethod(nameof(AspectDelegate.InvokeAspectDelegate));
// 执行方法以及注册的切面逻辑
il.Call(invokeAspectDelegateMethod);
il.Emit(OpCodes.Nop);
if (method.ReturnType != typeof(void))
{// 获取方法返回值il.Emit(OpCodes.Ldloc, localAspectInvocation);var getMethod = typeof(MethodInvocationContext).GetProperty("ReturnValue").GetGetMethod();il.EmitCall(OpCodes.Callvirt, getMethod, Type.EmptyTypes);if (method.ReturnType.IsValueType){// 如果是值类型,做一下类型转换il.EmitCastToType(typeof(object), method.ReturnType);}il.Emit(OpCodes.Stloc, localReturnValue);il.Emit(OpCodes.Ldloc, localReturnValue);
}
il.Emit(OpCodes.Ret);

注册并执行切面逻辑代码实现:

// 缓存方法体执行的委托,包含切面逻辑的执行和方法的调用
private static readonly ConcurrentDictionary<string, Action<MethodInvocationContext>> _aspectDelegates = new ConcurrentDictionary<string, Action<MethodInvocationContext>>();
public static void InvokeAspectDelegate(MethodInvocationContext context)
{var action = _aspectDelegates.GetOrAdd($"{context.ProxyMethod.DeclaringType}.{context.ProxyMethod}", m =>{// 获取切面逻辑,这里根据切面类型做了一个去重var aspects = new List<AbstractAspect>(8);if (context.MethodBase != null){// 获取类方法上的切面逻辑foreach (var aspect in context.MethodBase.GetCustomAttributes<AbstractAspect>()){if (!aspects.Exists(x => x.GetType() == aspect.GetType())){aspects.Add(aspect);}}}// 获取接口方法上的切面var methodParameterTypes = context.ProxyMethod.GetParameters().Select(p => p.GetType()).ToArray();foreach (var implementedInterface in context.ProxyTarget.GetType().GetImplementedInterfaces()){var method = implementedInterface.GetMethod(context.ProxyMethod.Name, methodParameterTypes);if (null != method){foreach (var aspect in method.GetCustomAttributes<AbstractAspect>()){if (!aspects.Exists(x => x.GetType() == aspect.GetType())){aspects.Add(aspect);}}}}// 构建切面逻辑执行管道,类似于 asp.net core 里的请求管道, 以原始方法调用作为中间件的最后一步var builder = PipelineBuilder.Create<MethodInvocationContext>(x => x.Invoke());foreach (var aspect in aspects){// 注册切面逻辑builder.Use(aspect.Invoke);}// 构建方法执行委托return builder.Build();});// 执行委托action.Invoke(context);// 检查返回值,防止切面逻辑管道的中断执行导致值类型返回值没有赋值if (context.ProxyMethod.ReturnType != typeof(void)){if (context.ReturnValue == null && context.ProxyMethod.ReturnType.IsValueType){// 为值类型返回值设置默认值作为返回值context.ReturnValue = Activator.CreateInstance(context.ProxyMethod.ReturnType);}}
}

More

以上基本可以实现一个 AOP 功能,但是从扩展性以及功能上来说都还比较欠缺,基于 Attribute 的方式固然可以实现功能,但是太不灵活,如果我要在一个无法修改的接口上的某一个方法做一个切面逻辑,显然只使用 Attribute 是做不到的,还是 Fluent-API 的方式比较灵活。

像做一层 AOP 的抽象,切面逻辑通过 Fluent-API 的方式来注册,大概的 API 可能是这样的:

var settings = FluentAspects.For<ITestService>();
setting.PropertySetter(x=>x.TestProp).InterceptWith<TryInterceptor>().InterceptWith<TryInterceptor1>();
setting.Method(x=> x.Test2()).InterceptWith<TryInterceptor>().InterceptWith<TryInterceptor1>();

然后基于 AspectCoreCastle.Core 来实现具体的 AOP 功能,暂时先想一下,争取尽快的发布一个基本可用的版本,然后之前基于 EF Core 的自动审计也可以基于 AOP 来实现了,这样就不需要显示继承 AuditDbContext 了~

文章所有源码可以在 Github 上获取到,Github 地址:https://github.com/WeihanLi/SamplesInPractice/tree/master/AopSample

Reference

  • 让 .NET 轻松构建中间件模式代码

  • 让 .NET 轻松构建中间件模式代码--支持中间件管道的中断和分支

  • NET 下基于动态代理的 AOP 框架实现揭秘

  • EF Core 数据变更自动审计设计

  • AopSample

  • AspectCore

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/310406.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

数据结构与算法-- 二叉树后续遍历序列校验

二叉树后续遍历序列校验 题目&#xff1a;输入一个整数数组&#xff0c;判断改数组是否是某个二叉搜索树的后续遍历结果&#xff0c;如果是返回true否则false&#xff0c;假设输入数组的任意两个数字不相同。 例如输入{5,7,6,9,11,10,8}则返回true&#xff0c;因为这个整数序列…

程序员过关斩将-- 工作好多年可能还未真正了解接口和抽象类

点击上方“蓝字”关注我们菜菜哥&#xff0c;我偷偷出去面试了&#xff0c;然后面试官让我回来等消息那你可能挂了呀&#xff0c;有什么问题没回答上来吗确实有一个问题回答的不太好哎&#xff0c;就是接口和抽象类这个确实是面试官比较爱问的题目之一那能不能说说接口和抽象类…

数据结构与索引-- mysql InnoDB存储引擎索引

索引与算法 索引是我们在应用开发过程中程序数据可开发的一个重要助力。也是一个重要的研究方向&#xff0c;索引太多&#xff0c;应用的性能可能受到影响&#xff0c;如果索引太少&#xff0c;对查询性能又会有制约。我们需要找到一个合适的平衡点&#xff0c;这个对性能至关…

扫盲消息队列 | 消息中间件 | Kafka

先吐槽我真的写技术文章写到怀疑人生&#xff0c;我翻看历史发文记录&#xff0c;只要我一本正经的写的技术文章&#xff0c;都没人看&#xff0c;但是&#xff01;一发闲扯淡的内容&#xff0c;阅读量肯定是技术文的好几倍&#xff08;读者爸爸们别这么搞嘛&#xff09;这说明…

数据结构与索引-- B+树索引

B树索引 上一节中我们讨论的都是B树的数据结构的由来以及他的一些操作&#xff0c;B树索引在本质就是B树在数据库中的一个实现&#xff0c;但是B索引在数据库中有一个特点就是他的高扇出性&#xff0c;因此在数据库中&#xff0c;B树的高度一般是2~3层&#xff0c;也就是对于查…

7种方法帮助企业改进软件维护效率

前言为了更高效地维护软件&#xff0c;同时为新的软件开发创造尽可能多的时间&#xff0c;以下为你介绍一些企业采取的方法和步骤。2019年&#xff0c;Tiedlift&#xff0c;一个开源支持和维护的企业&#xff0c;对软件开发人员进行了一项调查&#xff0c;结果显示&#xff0c;…

数据结构与索引-- mySql索引诡异事件

什么时候使用B树索引 并不是所有查询条件下出现的列都需要添加索引。对于什么时候添加索引&#xff0c;我们通过经验判断&#xff0c;访问表中很少一部分行时候&#xff0c;使用B树索引才有意义。对于性别字段&#xff0c;地区字段&#xff0c;类型字段&#xff0c;他们取值范…

[Java基础]抽象类和接口的区别

抽象类和接口的区别&#xff1a;

async,await执行流看不懂?看完这篇以后再也不会了

昨天有朋友在公众号发消息说看不懂await&#xff0c;async执行流&#xff0c;其实看不懂太正常了&#xff0c;因为你没经过社会的毒打&#xff0c;没吃过牢饭就不知道自由有多重要&#xff0c;没生过病就不知道健康有多重要&#xff0c;没用过ContinueWith就不知道await,async有…

如何分析EFCore引发的内存泄漏

调查实体框架核心中的内存泄漏不要让内存泄漏成为洪水术语“内存泄漏”和“ .NET应用程序”不是经常一起使用。但是&#xff0c;我们最近在一个.NET Core Web应用程序中出现了一系列内存不足异常。事实证明&#xff0c;此问题是由Entity Framework Core中的行为更改引起的&…

数据结构与算法-- 二叉树中和为某一值的路径

二叉树中和为某一值的路径 题目&#xff1a;输入一颗二叉树和一个整数&#xff0c;打印出二叉树中节点值的和为给定值的所有路径。从树的根节点开始往下一只到叶子节点所经过的节点形成一条路径。我们用二叉树节点的定义沿用之前文章中 二叉查找树实现原理定义。如下&#xff…

微服务统计,分析,图表,监控, 分布式追踪一体化的 HttpReports 在 .Net Core 的应用...

前言介绍HttpReports 是针对.Net Core 开发的轻量级APM系统&#xff0c;基于MIT开源协议, 使用HttpReports可以快速搭建.Net Core环境下统计,分析,图表,监控&#xff0c;分布式追踪一体化的站点&#xff0c; 适应.Net Core WebAPI,MVC&#xff0c;Web项目, 通过引用Nuget构建Da…

WPF 创建自定义面板

前面两个章节分别介绍了两个自定义控件:自定义的ColorPicker和FlipPanel控件。接下来介绍派生自定义面板以及构建自定义绘图控件。创建自定义面板是一种特殊但较常见的自定义控件开发子集。前面以及介绍过有关面板方面的知识&#xff0c;了解到面板驻留一个或多个子元素&#x…

vue.js中mock本地json数据

vue.js中mock本地json数据 新版本的vue项目中已经将dev-server.js&#xff0c;dev-client.js两个js文件合并到了webpack.dev.conf.js文件中&#xff0c;以下分别是新旧版本的build目录结构&#xff1a; 新版本&#xff1a; 旧版本&#xff1a; 本次验证mock&#xff1a;运…