AOP从静态代理到动态代理(Emit实现)详解

【前言】

AOP(Aspect Orient Programming),我们一般称为面向方面(切面)编程,作为面向对象的一种补充,用于处理系统中分布于各个模块的横切关注点,比如事务管理、日志、缓存等等。AOP实现的关键在于AOP框架自动创建的AOP代理,AOP代理主要分为静态代理和动态代理,静态代理的代表为AspectJ;而动态代理则以Spring AOP为代表。

何为切面?

一个和业务没有任何耦合相关的代码段,诸如:调用日志,发送邮件,甚至路由分发。一切能为代码所有且能和代码充分解耦的代码都可以作为一个业务代码的切面。

我们为什么要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);
  }
 }
 }

里面实现了两种代理方式,一种是 面向接口实现 的方式,另一种是 继承重写 的方式。

但是继承重写的方式需要把业务类的所有方法写成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 (本地下载)

总结

以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,如果有疑问大家可以留言交流,谢谢大家对我们的支持。

(0)

相关推荐

  • .NET(C#):Emit创建异常处理的方法

    目录 Emit异常处理流程 显示Exception对象的Message属性 返回目录 Emit异常处理流程来看这种C#异常处理代码: 复制代码 代码如下: static void doo(Exception e) { try { throw e; } catch (ApplicationException ex) { Console.WriteLine("捕获ApplicationException"); } catch { Console.WriteLine("捕获Exce

  • jsp中include指令静态导入和动态导入的区别详解

    1.什么是静态导入? 静态导入指的是,将一个外部文件嵌入到当前JSP文件中,同时解析这个页面的JSP语句,它会把目标页面的其他编译指令也包含进来.include的静态导入指令使用语法: 复制代码 代码如下: <%@include file="relativeURLSpec"%> 静态导入使用范例include1.jsp: 复制代码 代码如下: <%@ page contentType="text/html; charset=utf-8" langu

  • AOP从静态代理到动态代理(Emit实现)详解

    [前言] AOP(Aspect Orient Programming),我们一般称为面向方面(切面)编程,作为面向对象的一种补充,用于处理系统中分布于各个模块的横切关注点,比如事务管理.日志.缓存等等.AOP实现的关键在于AOP框架自动创建的AOP代理,AOP代理主要分为静态代理和动态代理,静态代理的代表为AspectJ:而动态代理则以Spring AOP为代表. 何为切面? 一个和业务没有任何耦合相关的代码段,诸如:调用日志,发送邮件,甚至路由分发.一切能为代码所有且能和代码充分解耦的代码都可

  • Spring AOP里的静态代理和动态代理用法详解

    什么是代理? 为某一个对象创建一个代理对象,程序不直接用原本的对象,而是由创建的代理对象来控制原对象,通过代理类这中间一层,能有效控制对委托类对象的直接访问,也可以很好地隐藏和保护委托类对象,同时也为实施不同控制策略预留了空间 什么是静态代理? 由程序创建或特定工具自动生成源代码,在程序运行前,代理类的.class文件就已经存在 通过将目标类与代理类实现同一个接口,让代理类持有真实类对象,然后在代理类方法中调用真实类方法,在调用真实类方法的前后添加我们所需要的功能扩展代码来达到增强的目的. 优点

  • 深入解析java中的静态代理与动态代理

    java编码中经常用到代理,代理分为静态代理和动态代理.其中动态代理可以实现spring中的aop. 一.静态代理:程序运行之前,程序员就要编写proxy,然后进行编译,即在程序运行之前,代理类的字节码文件就已经生成了 被代理类的公共父类 复制代码 代码如下: package staticproxy;public abstract class BaseClass {    public abstract void add();} 被代理类 复制代码 代码如下: package staticpro

  • JAVA中的静态代理、动态代理以及CGLIB动态代理总结

    代理模式是java中最常用的设计模式之一,尤其是在spring框架中广泛应用.对于java的代理模式,一般可分为:静态代理.动态代理.以及CGLIB实现动态代理. 对于上述三种代理模式,分别进行说明. 1.静态代理 静态代理其实就是在程序运行之前,提前写好被代理方法的代理类,编译后运行.在程序运行之前,class已经存在. 下面我们实现一个静态代理demo: 静态代理 定义一个接口Target package com.test.proxy; public interface Target { p

  • Java代理模式实例详解【静态代理与动态代理】

    本文实例讲述了Java代理模式.分享给大家供大家参考,具体如下: 即Proxy Pattern,23种java常用设计模式之一.代理模式的定义:对其他对象提供一种代理以控制对这个对象的访问. Java的代理模式是Java中比较常用的设计模式,分为2中代理:静态代理与动态代理(JDK动态代理和cglib动态代理) 优点: 职责清晰 真实角色只需关注业务逻辑的实现,非业务逻辑部分,后期通过代理类完成即可. 高扩展性 不管真实角色如何变化,由于接口是固定的,代理类无需做任何改动. 缺点: 很明显的一点

  • java代理模式(静态代理、动态代理、cglib代理)

    目录 代理模式 静态代理 代码 接口 被代理对象 代理对象 测试 动态代理 代码: 接口 目标对象 代理对象 测试 cglib代理 代码: 目标对象 代理对象 测试 应用 总结 代理模式 代理模式(Proxy Pattern)是一种结构性模式.代理模式为一个对象提供了一个替身,以控制对这个对象的访问.即通过代理对象访问目标目标对象,可以在目标对象实现的基础上,增强额外的功能操作,即扩展目标对象的功能. 被代理的对象可以是远程对象.创建开销答得对象或需要安全控制得对象.代理模式主要有三种形式,分别

  • 代理模式:JAVA静态代理和动态代理的实例和实现详解

    目录 前言 静态代理 实现简述 创建human接口 创建接口实现类 创建针对接口实现增强操作的代理 代理实现效果 动态代理 实现简述 要点:向上转型 创建YoungMan接口 创建两个接口实现类 创建动态代理实例对象 代理实现效果 要点:InvocationHandler补充 代理模式和修饰模式的区别 总结 前言 代理模式,我们这里结合JAVA的静态代理和动态代理来说明,类比Spring AOP面向切面编程:增强消息,也是代理模式. 而我们的静态代理和动态代理,与(service)接口和(ser

  • Java静态代理和动态代理总结

    静态代理 第一种实现(基于接口): 1>接口 public interface Hello { void say(String msg); } 2>目标类,至少实现一个接口 public class HelloImpl implements Hello { public void say(String msg) { System.out.println("Hi,"+msg); } } 3>代理类(与目标类实现相同接口,从而保证功能一致) public class He

  • Spring静态代理和动态代理代码详解

    本节要点: Java静态代理 Jdk动态代理 1 面向对象设计思想遇到的问题 在传统OOP编程里以对象为核心,并通过对象之间的协作来形成一个完整的软件功能,由于对象可以继承,因此我们可以把具有相同功能或相同特征的属性抽象到一个层次分明的类结构体系中.随着软件规范的不断扩大,专业化分工越来越系列,以及OOP应用实践的不断增多,随之也暴露了一些OOP无法很好解决的问题. 现在假设系统中有三段完全相似的代码,这些代码通常会采用"复制"."粘贴"方式来完成,通过这种方式开发

随机推荐