【前言】

  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实现的更多相关文章

  1. java中代理,静态代理,动态代理以及spring aop代理方式,实现原理统一汇总

    若代理类在程序运行前就已经存在,那么这种代理方式被成为 静态代理 ,这种情况下的代理类通常都是我们在Java代码中定义的. 通常情况下, 静态代理中的代理类和委托类会实现同一接口或是派生自相同的父类. ...

  2. java:struts框架2(方法的动态和静态调用,获取Servlet API三种方式(推荐IOC(控制反转)),拦截器,静态代理和动态代理(Spring AOP))

    1.方法的静态和动态调用: struts.xml: <?xml version="1.0" encoding="UTF-8"?> <!DOCT ...

  3. spring——AOP(静态代理、动态代理、AOP)

    一.代理模式 代理模式的分类: 静态代理 动态代理 从租房子开始讲起:中介与房东有同一的目标在于租房 1.静态代理 静态代理角色分析: 抽象角色:一般使用接口或者抽象类来实现(这里为租房接口) pub ...

  4. Spring AOP里的静态代理和动态代理,你真的了解嘛?

    什么是代理? 为某一个对象创建一个代理对象,程序不直接用原本的对象,而是由创建的代理对象来控制原对象,通过代理类这中间一层,能有效控制对委托类对象的直接访问,也可以很好地隐藏和保护委托类对象,同时也为 ...

  5. Atitit 代理CGLIB 动态代理 AspectJ静态代理区别

    Atitit 代理CGLIB 动态代理 AspectJ静态代理区别 1.1. AOP 代理主要分为静态代理和动态代理两大类,静态代理以 AspectJ 为代表:而动态代理则以 spring AOP 为 ...

  6. 【Java】代处理?代理模式 - 静态代理,动态代理

    >不用代理 有时候,我希望在一些方法前后都打印一些日志,于是有了如下代码. 这是一个处理float类型加法的方法,我想在调用它前打印一下参数,调用后打印下计算结果.(至于为什么不直接用+号运算, ...

  7. Java:静态代理 and 动态代理

    代理模式是常用的设计模式,其特征是代理类与委托类具有相同的接口,在具体实现上,有静态代理和动态代理之分.代理类与委托类之间通常会存在关联关系,一个代理类的对象与一个委托类的对象关联,代理类的对象本身并 ...

  8. JAVA中的代理技术(静态代理和动态代理)

    最近看书,有两个地方提到了动态代理,一是在Head First中的代理模式,二是Spring AOP中的AOP.所以有必要补充一下动态代理的相关知识. Spring采用JDK动态代理和CGLib动态代 ...

  9. java代理:静态代理和动态代理

    一.Java中有一个设计模式是代理模式 代理模式是常用的Java设计模式,特征是代理类与委托类有相同的接口,代理类主要负责为委托类预处理消息.过滤消息.把消息转发给委托类,以及事后处理消息等. 代理类 ...

随机推荐

  1. Spring Boot(九)Swagger2自动生成接口文档和Mock模拟数据

    一.简介 在当下这个前后端分离的技术趋势下,前端工程师过度依赖后端工程师的接口和数据,给开发带来了两大问题: 问题一.后端接口查看难:要怎么调用?参数怎么传递?有几个参数?参数都代表什么含义? 问题二 ...

  2. 内存管理-MRC与ARC详解

    Objective-C提供了两种内存管理机制MRC(Mannul Reference Counting)和ARC(Automatic Reference Counting),为Objective-C提 ...

  3. 【转】Android 开发规范(完结版)

    摘要 1 前言 2 AS 规范 3 命名规范 4 代码样式规范 5 资源文件规范 6 版本统一规范 7 第三方库规范 8 注释规范 9 测试规范 10 其他的一些规范 1 前言 为了有利于项目维护.增 ...

  4. 【学习笔记】tensorflow实现一个简单的线性回归

    目录 准备知识 Tensorflow运算API 梯度下降API 简单的线性回归的实现 建立事件文件 变量作用域 增加变量显示 模型的保存与加载 自定义命令行参数 准备知识 Tensorflow运算AP ...

  5. WEB前端 CSS(非布局)

    目录 WEB前端 CSS CSS引入方式 CSS结构 CSS选择器 直接选择器 组合选择器 分组选择器 也叫并集选择器 属性选择器 伪类选择器 伪元素选择器 CSS选择器是一个查找的过程,高效的查找影 ...

  6. vue HTTP 请求(vue-resource)

    来自:https://www.cnblogs.com/lhl66/p/8022423.html 侵删 //初始化页面需要做什么事情 //点击后需要做什么事情 //鼠标.键盘.冒泡.默认行为等事件 // ...

  7. Dynamics 365-ExecuteWorkflowRequest

    一般是通过对CRM上的数据进行手动操作,来触发Workflow,但是如果碰到数据量比较大的时候,纯手动操作无疑是一个耗时费力的事.这个时候,可以通过使用ExecuteWorkflowRequest来实 ...

  8. 从.Net到Java学习第十篇——Spring Boot文件上传和下载

    从.Net到Java学习系列目录 图片上传 Spring Boot中的文件上传就是Spring MVC中的文件上传,将其集成进来了. 在模板目录创建一个新的页面 profile/uploadPage. ...

  9. 常用matlab函数(不定时更新)

    直方图类: histc  直方图分组  示例 histc(a,0:1:10)  意义:将a(矩阵或向量)分组,分组设置为 0-1 1-2 2-3 -.. 9-10,(10-11) 百分位 prctil ...

  10. Mac 系统占用100g的解决办法

    Mac 关于本机-磁盘管理,如果发现系统占用超过80g以上的小伙伴们可以做以下操作只需要以下4个步骤,轻松降到30g以内!!!!!!!(仅适用于安装了Xcode的小伙伴) 打开Finder,comma ...