AOP从静态代理到动态代理 Emit实现
【前言】
AOP为Aspect Oriented Programming的缩写,意思是面向切面编程的技术。
何为切面?
一个和业务没有任何耦合相关的代码段,诸如:调用日志,发送邮件,甚至路由分发。一切能为代码所有且能和代码充分解耦的代码都可以作为一个业务代码的切面。
我们为什么要AOP?
那我们从一个场景举例说起:
如果想要采集用户操作行为,我们需要掌握用户调用的每一个接口的信息。这时候的我们要怎么做?
如果不采用AOP技术,也是最简单的,所有方法体第一句话先调用一个日志接口将方法信息传递记录。
有何问题?
实现业务没有任何问题,但是随之而来的是代码臃肿不堪,难以调整维护的诸多问题(可自行脑补)。
如果我们采用了AOP技术,我们就可以在系统启动的地方将所有将要采集日志的类注入,每一次调用方法前,AOP框架会自动调用我们的日志代码。
是不是省去了很多重复无用的劳动?代码也将变得非常好维护(有朝一日不需要了,只需将切面代码注释掉即可)
接下来我们看看AOP框架的工作原理以及实过程。
【实现思路】
AOP框架呢,一般通过静态代理和动态代理两种实现方式。
何为静态代理?
静态代理,又叫编译时代理,就是在编译的时候,已经存在代理类,运行时直接调用的方式。说的通俗一点,就是自己手动写代码实现代理类的方式。
我们通过一个例子来展现一下静态代理的实现过程:
我们这里有一个业务类,里面有方法Test(),我们要在Test调用前和调用后分别输出日志。
我们既然要将Log当作一个切面,我们肯定不能去动原有的业务代码,那样也违反了面向对象设计之开闭原则。
那么我们要怎么做呢?我们定义一个新类 BusinessProxy 去包装一下这个类。为了便于在多个方法的时候区分和辨认,方法也叫 Test()
这样,我们如果要在所有的Business类中的方法都添加Log,我们就在BusinessProxy代理类中添加对应的方法去包装。既不破坏原有逻辑,又可以实现前后日志的功能。
当然,我们可以有更优雅的实现方式:
我们可以定义代理类,继承自业务类。将业务类中的方法定义为虚方法。那么我们可以重写父类的方法并且在加入日志以后再调用父类的原方法。
当然,我们还有更加优雅的实现方式:
我们可以使用发射的技术,写一个通用的Invoke方法,所有的方法都可以通过该方法调用。
我们这样便实现了一个静态代理。
那我们既然有了静态代理,为什么又要有动态代理呢?
我们仔细回顾静态代理的实现过程。我们要在所有的方法中添加切面,我们就要在代理类中重写所有的业务方法。更有甚者,我们有N个业务类,就要定义N个代理类。这是很庞大的工作量。
这就是动态代理出现的背景,相比都可以猜得到,动态代理就是将这一系列繁琐的步骤自动化,让程序自动为我们生成代理类。
何为动态代理?
动态代理,又成为运行时代理。在程序运行的过程中,调用了生成代理类的代码,将自动生成业务类的代理类。不需要我们手共编写,极高的提高了工作效率和调整了程序员的心态。
原理不必多说,就是动态生成静态代理的代码。我们要做的,就是选用一种生成代码的方式去生成。
今天我分享一个简单的AOP框架,代码使用Emit生成。当然,Emit 代码的写法不是今天要讲的主要内容,需要提前去学习。
先说效果:
定义一个Action特性类 ActionAttribute 继承自 ActionBaseAttribute,里面在Before和After方法中输出两条日志;
定义一个Action特性类 InterceptorAttribute 继承自 InterceptorBaseAttribute,里面捕获了方法调用异常,以及执行前后分别输出日志;
然后定义一个业务类 BusinessClass 实现了 IBusinessClass 接口,定义了各种类型的方法
多余的方法不贴图了。
我们把上面定义的方法调用切面标签放在业务类上,表示该类下所有的方法都执行异常过滤;
我们把Action特性放在Test方法上,表明要在 Test() 方法的 Before 和 After 调用时记录日志;
我们定义测试类:
调用一下试试:
可见,全类方法标签 Interceptor 在 Test 和 GetInt 方法调用前后都打出了对应的日志;
Action方法标签只在 Test 方法上做了标记,那么Test 方法 Before 和 After 执行时打出了日志;
【实现过程】
实现的思路在上面已经有详细的讲解,可以参考静态代理的实现思路。
我们定义一个动态代理生成类 DynamicProxy,用于原业务代码的扫描和代理类代码的生成;
定义两个过滤器标签,ActionBaseAttribute,提供 Before 和 After 切面方法;InterceptorBaseAttribute,提供 Invoke “全调用”包装的切面方法;
Before可以获取到当前调用的方法和参数列表,After可以获取到当前方法调用以后的结果。
Invoke 可以拿到当前调用的对象和方法名,参数列表。在这里进行反射动态调用。
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, AllowMultiple = false, Inherited = true)]
public class ActionBaseAttribute : Attribute
{
public virtual void Before(string @method, object[] parameters) { } public virtual object After(string @method, object result) { return result; }
}
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = true)]
public class InterceptorBaseAttribute : Attribute
{
public virtual object Invoke(object @object, string @method, object[] parameters)
{
return @object.GetType().GetMethod(@method).Invoke(@object, parameters);
}
}
代理生成类采用Emit的方式生成运行时IL代码。
先把代码放在这里:
public class DynamicProxy
{
public static TInterface CreateProxyOfRealize<TInterface, TImp>() where TImp : class, new() where TInterface : class
{
return Invoke<TInterface, TImp>();
} public static TProxyClass CreateProxyOfInherit<TProxyClass>() where TProxyClass : class, new()
{
return Invoke<TProxyClass, TProxyClass>(true);
} private static TInterface Invoke<TInterface, TImp>(bool inheritMode = false) where TImp : class, new() where TInterface : class
{
var impType = typeof(TImp); string nameOfAssembly = impType.Name + "ProxyAssembly";
string nameOfModule = impType.Name + "ProxyModule";
string nameOfType = impType.Name + "Proxy"; var assemblyName = new AssemblyName(nameOfAssembly); var assembly = AppDomain.CurrentDomain.DefineDynamicAssembly(assemblyName, AssemblyBuilderAccess.Run);
var moduleBuilder = assembly.DefineDynamicModule(nameOfModule); //var assembly = AppDomain.CurrentDomain.DefineDynamicAssembly(assemblyName, AssemblyBuilderAccess.RunAndSave);
//var moduleBuilder = assembly.DefineDynamicModule(nameOfModule, nameOfAssembly + ".dll"); TypeBuilder typeBuilder;
if (inheritMode)
typeBuilder = moduleBuilder.DefineType(nameOfType, TypeAttributes.Public, impType);
else
typeBuilder = moduleBuilder.DefineType(nameOfType, TypeAttributes.Public, null, new[] { typeof(TInterface) }); InjectInterceptor<TImp>(typeBuilder, impType.GetCustomAttribute(typeof(InterceptorBaseAttribute))?.GetType(), inheritMode); var t = typeBuilder.CreateType(); //assembly.Save(nameOfAssembly + ".dll"); return Activator.CreateInstance(t) as TInterface;
} private static void InjectInterceptor<TImp>(TypeBuilder typeBuilder, Type interceptorAttributeType, bool inheritMode = false)
{
var impType = typeof(TImp);
// ---- define fields ----
FieldBuilder fieldInterceptor = null;
if (interceptorAttributeType != null)
{
fieldInterceptor = typeBuilder.DefineField("_interceptor", interceptorAttributeType, FieldAttributes.Private);
}
// ---- define costructors ----
if (interceptorAttributeType != null)
{
var constructorBuilder = typeBuilder.DefineConstructor(MethodAttributes.Public, CallingConventions.Standard, null);
var ilOfCtor = constructorBuilder.GetILGenerator(); ilOfCtor.Emit(OpCodes.Ldarg_0);
ilOfCtor.Emit(OpCodes.Newobj, interceptorAttributeType.GetConstructor(new Type[]));
ilOfCtor.Emit(OpCodes.Stfld, fieldInterceptor);
ilOfCtor.Emit(OpCodes.Ret);
} // ---- define methods ---- var methodsOfType = impType.GetMethods(BindingFlags.Public | BindingFlags.Instance); string[] ignoreMethodName = new[] { "GetType", "ToString", "GetHashCode", "Equals" }; foreach (var method in methodsOfType)
{
//ignore method
if (ignoreMethodName.Contains(method.Name))
return; var methodParameterTypes = method.GetParameters().Select(p => p.ParameterType).ToArray(); MethodAttributes methodAttributes; if (inheritMode)
methodAttributes = MethodAttributes.Public | MethodAttributes.Virtual;
else
methodAttributes = MethodAttributes.Public | MethodAttributes.HideBySig | MethodAttributes.NewSlot | MethodAttributes.Virtual | MethodAttributes.Final; var methodBuilder = typeBuilder.DefineMethod(method.Name, methodAttributes, CallingConventions.Standard, method.ReturnType, methodParameterTypes); var ilMethod = methodBuilder.GetILGenerator(); // set local field
var impObj = ilMethod.DeclareLocal(impType); //instance of imp object
var methodName = ilMethod.DeclareLocal(typeof(string)); //instance of method name
var parameters = ilMethod.DeclareLocal(typeof(object[])); //instance of parameters
var result = ilMethod.DeclareLocal(typeof(object)); //instance of result
LocalBuilder actionAttributeObj = null; //attribute init
Type actionAttributeType = null;
if (method.GetCustomAttribute(typeof(ActionBaseAttribute)) != null || impType.GetCustomAttribute(typeof(ActionBaseAttribute)) != null)
{
//method can override class attrubute
if (method.GetCustomAttribute(typeof(ActionBaseAttribute)) != null)
{
actionAttributeType = method.GetCustomAttribute(typeof(ActionBaseAttribute)).GetType();
}
else if (impType.GetCustomAttribute(typeof(ActionBaseAttribute)) != null)
{
actionAttributeType = impType.GetCustomAttribute(typeof(ActionBaseAttribute)).GetType();
} actionAttributeObj = ilMethod.DeclareLocal(actionAttributeType);
ilMethod.Emit(OpCodes.Newobj, actionAttributeType.GetConstructor(new Type[]));
ilMethod.Emit(OpCodes.Stloc, actionAttributeObj);
} //instance imp
ilMethod.Emit(OpCodes.Newobj, impType.GetConstructor(new Type[]));
ilMethod.Emit(OpCodes.Stloc, impObj); //if no attribute
if (fieldInterceptor != null || actionAttributeObj != null)
{
ilMethod.Emit(OpCodes.Ldstr, method.Name);
ilMethod.Emit(OpCodes.Stloc, methodName); ilMethod.Emit(OpCodes.Ldc_I4, methodParameterTypes.Length);
ilMethod.Emit(OpCodes.Newarr, typeof(object));
ilMethod.Emit(OpCodes.Stloc, parameters); // build the method parameters
for (var j = ; j < methodParameterTypes.Length; j++)
{
ilMethod.Emit(OpCodes.Ldloc, parameters);
ilMethod.Emit(OpCodes.Ldc_I4, j);
ilMethod.Emit(OpCodes.Ldarg, j + );
//box
ilMethod.Emit(OpCodes.Box, methodParameterTypes[j]);
ilMethod.Emit(OpCodes.Stelem_Ref);
}
} //dynamic proxy action before
if (actionAttributeType != null)
{
//load arguments
ilMethod.Emit(OpCodes.Ldloc, actionAttributeObj);
ilMethod.Emit(OpCodes.Ldloc, methodName);
ilMethod.Emit(OpCodes.Ldloc, parameters);
ilMethod.Emit(OpCodes.Call, actionAttributeType.GetMethod("Before"));
} if (interceptorAttributeType != null)
{
//load arguments
ilMethod.Emit(OpCodes.Ldarg_0);//this
ilMethod.Emit(OpCodes.Ldfld, fieldInterceptor);
ilMethod.Emit(OpCodes.Ldloc, impObj);
ilMethod.Emit(OpCodes.Ldloc, methodName);
ilMethod.Emit(OpCodes.Ldloc, parameters);
// call Invoke() method of Interceptor
ilMethod.Emit(OpCodes.Callvirt, interceptorAttributeType.GetMethod("Invoke"));
}
else
{
//direct call method
if (method.ReturnType == typeof(void) && actionAttributeType == null)
{
ilMethod.Emit(OpCodes.Ldnull);
} ilMethod.Emit(OpCodes.Ldloc, impObj);
for (var j = ; j < methodParameterTypes.Length; j++)
{
ilMethod.Emit(OpCodes.Ldarg, j + );
}
ilMethod.Emit(OpCodes.Callvirt, impType.GetMethod(method.Name));
//box
if (actionAttributeType != null)
{
if (method.ReturnType != typeof(void))
ilMethod.Emit(OpCodes.Box, method.ReturnType);
else
ilMethod.Emit(OpCodes.Ldnull);
}
} //dynamic proxy action after
if (actionAttributeType != null)
{
ilMethod.Emit(OpCodes.Stloc, result);
//load arguments
ilMethod.Emit(OpCodes.Ldloc, actionAttributeObj);
ilMethod.Emit(OpCodes.Ldloc, methodName);
ilMethod.Emit(OpCodes.Ldloc, result);
ilMethod.Emit(OpCodes.Call, actionAttributeType.GetMethod("After"));
} // pop the stack if return void
if (method.ReturnType == typeof(void))
{
ilMethod.Emit(OpCodes.Pop);
}
else
{
//unbox,if direct invoke,no box
if (fieldInterceptor != null || actionAttributeObj != null)
{
if (method.ReturnType.IsValueType)
ilMethod.Emit(OpCodes.Unbox_Any, method.ReturnType);
else
ilMethod.Emit(OpCodes.Castclass, method.ReturnType);
}
}
// complete
ilMethod.Emit(OpCodes.Ret);
}
}
}
DynamicProxy
里面实现了两种代理方式,一种是 面向接口实现 的方式,另一种是 继承重写 的方式。
但是继承重写的方式需要把业务类的所有方法写成virtual虚方法,动态类会重写该方法。
我们从上一节的Demo中获取到运行时生成的代理类dll,用ILSpy反编译查看源代码:
可以看到,我们的代理类分别调用了我们特性标签中的各项方法。
核心代码分析(源代码在上面折叠部位已经贴出):
解释:如果该方法存在Action标签,那么加载 action 标签实例化对象,加载参数,执行Before方法;如果该方法存在Interceptor标签,那么使用类字段this._interceptor调用该标签的Invoke方法。
解释:如果面的Interceptor特性标签不存在,那么会加载当前扫描的方法对应的参数,直接调用方法;如果Action标签存在,则将刚才调用的结果包装成object对象传递到After方法中。
这里如果目标参数是object类型,而实际参数是直接调用返回的明确的值类型,需要进行装箱操作,否则运行时报调用内存错误异常。
解释:如果返回值是void类型,则直接结束并返回结果;如果返回值是值类型,则需要手动拆箱操作,如果是引用类型,那么需要类型转换操作。
IL实现的细节,这里不做重点讨论。
【系统测试】
1.接口实现方式,Api测试(各种标签使用方式对应的不同类型的方法调用):
结论:对于上述穷举的类型,各种标签使用方式皆成功打出了日志;
2.继承方式,Api测试(各种标签使用方式对应的不同类型的方法调用):
结论:继承方式和接口实现方式的效果是一样的,只是方法上需要不同的实现调整;
3.直接调用三个方法百万次性能结果:
结论:直接调用三个方法百万次调用耗时 58ms
4.使用实现接口方式三个方法百万次调用结果
结论:结果见上图,需要注意是三个方法百万次调用,也就是300w次的方法调用
5.使用继承方式三个方法百万次调用结果
结论:结果见上图,需要注意是三个方法百万次调用,也就是300w次的方法调用
事实证明,IL Emit的实现方式性能还是很高的。
综合分析:
通过各种的调用分析,可以看出使用代理以后和原生方法调用相比性能损耗在哪里。性能差距最大的,也是耗时最多的实现方式就是添加了全类方法代理而且是使用Invoke进行全方法切面方式。该方式耗时的原因是使用了反射Invoke的方法。
直接添加Action代理类实现 Before和After的方式和原生差距不大,主要损耗在After触发时的拆装箱上。
综上分析,我们使用的时候,尽量针对性地对某一个方法进行AOP注入,而尽量不要全类方法进行AOP注入。
【总结】
通过自己实现一个AOP的动态注入框架,对Emit有了更加深入的了解,最重要的是,对CLR IL代码的执行过程有了一定的认知,受益匪浅。
该方法在使用的过程中也发现了问题,比如有ref和out类型的参数时,会出现问题,需要后续继续改进
本文的源代码已托管在GitHub上,又需要可以自行拿取(顺手Star哦~):https://github.com/sevenTiny/CodeArts (可以访问下面链接获取.Net Standard 跨平台版本代码)
最新SevenTiny.Bantina.Aop组件代码(跨平台):https://github.com/sevenTiny/SevenTiny.Bantina
该代码的位置在 CodeArts.CSharp 分区下
VS打开后,可以在 EmitDynamicProxy 分区下找到;本博客所有的测试项目都在项目中可以找到。
再次放上源代码地址,供一起学习的朋友参考,希望能帮助到你:https://github.com/sevenTiny/CodeArts (可以访问下面链接获取.Net Standard 跨平台版本代码)
最新SevenTiny.Bantina.Aop组件代码(跨平台):https://github.com/sevenTiny/SevenTiny.Bantina
AOP从静态代理到动态代理 Emit实现的更多相关文章
- java中代理,静态代理,动态代理以及spring aop代理方式,实现原理统一汇总
若代理类在程序运行前就已经存在,那么这种代理方式被成为 静态代理 ,这种情况下的代理类通常都是我们在Java代码中定义的. 通常情况下, 静态代理中的代理类和委托类会实现同一接口或是派生自相同的父类. ...
- java:struts框架2(方法的动态和静态调用,获取Servlet API三种方式(推荐IOC(控制反转)),拦截器,静态代理和动态代理(Spring AOP))
1.方法的静态和动态调用: struts.xml: <?xml version="1.0" encoding="UTF-8"?> <!DOCT ...
- spring——AOP(静态代理、动态代理、AOP)
一.代理模式 代理模式的分类: 静态代理 动态代理 从租房子开始讲起:中介与房东有同一的目标在于租房 1.静态代理 静态代理角色分析: 抽象角色:一般使用接口或者抽象类来实现(这里为租房接口) pub ...
- Spring AOP里的静态代理和动态代理,你真的了解嘛?
什么是代理? 为某一个对象创建一个代理对象,程序不直接用原本的对象,而是由创建的代理对象来控制原对象,通过代理类这中间一层,能有效控制对委托类对象的直接访问,也可以很好地隐藏和保护委托类对象,同时也为 ...
- Atitit 代理CGLIB 动态代理 AspectJ静态代理区别
Atitit 代理CGLIB 动态代理 AspectJ静态代理区别 1.1. AOP 代理主要分为静态代理和动态代理两大类,静态代理以 AspectJ 为代表:而动态代理则以 spring AOP 为 ...
- 【Java】代处理?代理模式 - 静态代理,动态代理
>不用代理 有时候,我希望在一些方法前后都打印一些日志,于是有了如下代码. 这是一个处理float类型加法的方法,我想在调用它前打印一下参数,调用后打印下计算结果.(至于为什么不直接用+号运算, ...
- Java:静态代理 and 动态代理
代理模式是常用的设计模式,其特征是代理类与委托类具有相同的接口,在具体实现上,有静态代理和动态代理之分.代理类与委托类之间通常会存在关联关系,一个代理类的对象与一个委托类的对象关联,代理类的对象本身并 ...
- JAVA中的代理技术(静态代理和动态代理)
最近看书,有两个地方提到了动态代理,一是在Head First中的代理模式,二是Spring AOP中的AOP.所以有必要补充一下动态代理的相关知识. Spring采用JDK动态代理和CGLib动态代 ...
- java代理:静态代理和动态代理
一.Java中有一个设计模式是代理模式 代理模式是常用的Java设计模式,特征是代理类与委托类有相同的接口,代理类主要负责为委托类预处理消息.过滤消息.把消息转发给委托类,以及事后处理消息等. 代理类 ...
随机推荐
- Jenkins结合.net平台综合之权限修正和文件排除
笔者在发布项目的时候遇到这样一个问题,第一次发布的时候成功发布,然后再次发布失败.但是这个问题很快就排除了,这里提出来是为了帮助遇到这个问题的小伙伴,以顺利避开坑.之所以会这样是因为我们在设置权限的时 ...
- Docker在Linux/Windows上运行NetCore文章系列
Windows系列 因为Window很简单,VS提供界面化配置,所以只写了一篇文章 Docker在Windows上运行NetCore系列(一)使用命令控制台运行.NetCore控制台应用 Linux( ...
- DSAPI多功能组件编程应用-网络相关(上)
[DSAPI.DLL下载地址] DSAPI多功能组件编程应用-网络相关,网络相关编程有很多很多,这里讲解一下封装在DSAPI中的网络相关的功能,这些都是本人简化到极点的功能了,可以在软件开发过程中节 ...
- Java开发笔记(二十四)方法的组成形式
经过前面的学习,我们发现演示的Java代码越来越复杂,而且每个例子的代码都堆在入口方法main内部,这会导致如下问题:1.一个方法内部堆砌了太多的代码行,看着费神,维护起来也吃力:2.部分代码描述的是 ...
- 【开源】Netty轻松实现聊天室,附带数据记录,聊天历史
阅读本文约“2.5分钟” 听说快七夕······ 不对,这不是今天的主题,嘿嘿. 今天说说一个小的网页聊天室,功能如下 群聊无限制 记录用户群聊信息 下次登录显示聊天历史 消息发送速度(光速) 聊天历 ...
- Java设计模式-单例模式详解(上)
单例模式整理 敲了多年代码后,回头来看会别有一番滋味在心头.. 概念 单例模式是为了保证在一个jvm环境下,一个类仅有一个对象. 代码中常见的懒汉式.饿汉式,这些实现方式可以通过代码的设计来强制保证的 ...
- js获取文件后缀
//获取文件后缀 function getType(file){ var filename=file; var index1=filename.lastIndexOf("."); ...
- python地理处理包——pySAL使用
Pysal是基于Python的开源地理处理库,能提供高层次的空间分析功能.
- 阿里云 API调用实践(python语言)
1.结论:阿里云的SDK开发,其实就是远程调用API,python的代码就是一个外壳,核心是封装成一个http报文,利用json格式,进行RPC调用. 2.SDK调用API的套路如下: # -*- c ...
- Docker 创建 Jira Core(Jira SoftWare) 7.12.3 中文版
目录 目录 1.介绍 1.1.什么是 JIRA Core? 1.2.什么是 JIRA SoftWare 2.JIRA 的官网在哪里? 3.如何下载安装? 4.对 JIRA 进行配置 4.1.JIRA ...