[前言]
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[0]));
- 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[0]));
- ilMethod.Emit(OpCodes.Stloc, actionAttributeObj);
- }
- //instance imp
- ilMethod.Emit(OpCodes.Newobj, impType.GetConstructor(new Type[0]));
- 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 = 0; j < methodParameterTypes.Length; j++)
- {
- ilMethod.Emit(OpCodes.Ldloc, parameters);
- ilMethod.Emit(OpCodes.Ldc_I4, j);
- ilMethod.Emit(OpCodes.Ldarg, j + 1);
- //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 = 0; j < methodParameterTypes.Length; j++)
- {
- ilMethod.Emit(OpCodes.Ldarg, j + 1);
- }
- 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
该代码的位置在 CodeArts.CSharp 分区下
VS 打开后, 可以在 EmitDynamicProxy 分区下找到; 本博客所有的测试项目都在项目中可以找到.
再次放上源代码地址, 供一起学习的朋友参考, 希望能帮助到你: https://github.com/sevenTiny/CodeArts
来源: https://www.cnblogs.com/7tiny/p/9657451.html