动态方法拦截(AOP)的N种解决方案
AOP的本质是方法拦截(将针对目标方法调用劫持下来,进而执行执行的操作),置于方法拦截的实现方案,不外乎两种代码注入类型,即编译时的静态注入和运行时的动态注入,本篇文章列出了几种常用的动态注入方案。这篇文章的目标并不是提供完整的AOP框架的解决方案,而是说明各种解决方案后面的原理,所以我们提供的实例代码会尽可能简单。为了确定拦截操作是否执行,我们定义了如下这个Indicator类型,我们的拦截操作会将其静态属性Injected属性设置为True,我们演示的代码最终通过这个属性来确定拦截是否成功。[源代码从这里下载]
public static class Indicator
{
public static bool Injected { get; set; }
}
一、IL Emit(接口)
IL Emit是实现AOP的首选方案。如果方法调用时针对接口完成,我们可以生成一个代理类型来封装对象,并且这个代理类型同时实现目标接口,那么只要我们能够将针对目标对象的方法调用转换成针对代理对象的调用,就能实现针对目标对象的方法拦截。举个简单的例子,Foobar实现了IFoobar接口,如果我们需要拦截接口方法Invoke,我们可以生成一个FoobarProxy类型。如代码片段所示,FoobarProxy封装了一个IFoobar对象,并实现了IFoobar接口。在实现的Invoke方法中,它在调用封装对象的同名方法之前率先执行了拦截操作。
public interface IFoobar
{
int Invoke();
} public class Foobar : IFoobar
{
public int Invoke() => 1;
} public class FoobarProxy : IFoobar
{
private readonly IFoobar _target;
public FoobarProxy(IFoobar target)=>_target = target
public int Invoke()
{
Indicator.Injected = true;
return _target.Invoke();
}
}
上述的这个FoobarProxy类型就可以按照如下的方式利用GenerateProxyClass方法来生成。在Main方法中,我们创建一个Foobar对象,让据此创建这个动态生成的FoobarProxy,当该对象的Invoke方法执行的时候,我们期望的拦截操作自然会自动执行。
class Program
{
static void Main(string[] args)
{
var foobar = new Foobar();
var proxy = (IFoobar)Activator.CreateInstance(GenerateProxyClass(), foobar); Debug.Assert(Indicator.Injected == false);
Debug.Assert(proxy.Invoke() == 1);
Debug.Assert(Indicator.Injected == true);
} static Type GenerateProxyClass()
{
var assemblyBuilder = AssemblyBuilder.DefineDynamicAssembly(new AssemblyName("Proxy"), AssemblyBuilderAccess.Run);
var moduleBuilder = assemblyBuilder.DefineDynamicModule("Proxy.dll");
var typeBuilder = moduleBuilder.DefineType("FoobarProxy", TypeAttributes.Public, null, new Type[] { typeof(IFoobar) });
var targetField = typeBuilder.DefineField("_target", typeof(IFoobar), FieldAttributes.Private | FieldAttributes.InitOnly); var constructor = typeBuilder.DefineConstructor(MethodAttributes.Public, CallingConventions.Standard, new Type[] { typeof(IFoobar) });
var il = constructor.GetILGenerator();
il.Emit(OpCodes.Ldarg_0);
il.Emit(OpCodes.Ldarg_1);
il.Emit(OpCodes.Stfld, targetField);
il.Emit(OpCodes.Ret); var attributes = MethodAttributes.Public | MethodAttributes.HideBySig | MethodAttributes.NewSlot | MethodAttributes.Virtual | MethodAttributes.Final;
var invokeMethod = typeBuilder.DefineMethod("Invoke", attributes, typeof(int), null);
il = invokeMethod.GetILGenerator();
il.Emit(OpCodes.Ldc_I4_1);
il.Emit(OpCodes.Call, typeof(Indicator).GetProperty("Injected").SetMethod);
il.Emit(OpCodes.Ldarg_0);
il.Emit(OpCodes.Ldfld, targetField);
il.Emit(OpCodes.Callvirt, typeof(IFoobar).GetMethod("Invoke"));
il.Emit(OpCodes.Ret); return typeBuilder.CreateType();
}
}
二、IL Emit(虚方法)
如果待拦截的并非接口方法,而是一个虚方法,我们可以利用IL Emit的方式动态生成一个派生类,并重写这个虚方法的方式来完成拦截。以下面的代码片段为例,我们需要拦截定义在Foobar中的虚方法Invoke,我们可以生成如下这个派生与Foobar的Foobar的FoobarProxy类型,在重写的Invoke方法中,我们在调用基类同名方法之前,率先执行拦截操作。
public class Foobar
{
public virtual int Invoke() => 1;
}
public class FoobarProxy : Foobar { public override int Invoke()
{
Indicator.Injected = true;
return base.Invoke();
}
}
上面这个FoobarProxy类型就可以通过如下这个GenerateProxyClass生成出来。
class Program
{
static void Main(string[] args)
{
var proxy = (Foobar)Activator.CreateInstance(GenerateProxyClass()); Debug.Assert(Indicator.Injected == false);
Debug.Assert(proxy.Invoke() == 1);
Debug.Assert(Indicator.Injected == true);
} static Type GenerateProxyClass()
{
var assemblyBuilder = AssemblyBuilder.DefineDynamicAssembly(new AssemblyName("Proxy"), AssemblyBuilderAccess.Run);
var moduleBuilder = assemblyBuilder.DefineDynamicModule("Proxy.dll");
var typeBuilder = moduleBuilder.DefineType("FoobarProxy", TypeAttributes.Public, typeof(Foobar)); var attributes = MethodAttributes.Public | MethodAttributes.HideBySig | MethodAttributes.Virtual | MethodAttributes.Final;
var invokeMethod = typeBuilder.DefineMethod("Invoke", attributes, typeof(int), null);
var il = invokeMethod.GetILGenerator();
il.Emit(OpCodes.Ldc_I4_1);
il.Emit(OpCodes.Call, typeof(Indicator).GetProperty("Injected").SetMethod);
il.Emit(OpCodes.Ldarg_0);
il.Emit(OpCodes.Call, typeof(Foobar).GetMethod("Invoke"));
il.Emit(OpCodes.Ret); return typeBuilder.CreateType();
}
}
三、方法替换(跳转)
上面两种方案都具有一个局限性:需要将针对目标对象的方法调用转换成针对代理对象的调用。如果我们能够直接将目标方法替换成另一个包含拦截操作的方案(或者说从原来的方法调转到具有拦截操作的方法),那么即使我们不改变方法的调用方式,方法依旧能够拦截。Harmony框架就是采用这样的方案实现的,我们可以通过下面这个简单的实例来模拟其实现原理(下面演示的程序引用了HarmonyLib包)。
class Program
{
static void Main(string[] args)
{
HarmonyLib.Memory.DetourMethod(typeof(Foobar).GetMethod("Invoke"), GenerateNewMethod());
Debug.Assert(Indicator.Injected == false);
Debug.Assert(new Foobar().Invoke() == 1);
Debug.Assert(Indicator.Injected == true);
} static MethodBase GenerateNewMethod()
{
var dynamicMethod = new DynamicMethodDefinition(typeof(Foobar).GetMethod("Invoke"));
var il = dynamicMethod.GetILProcessor();
var ldTrue = il.Create(OpCodes.Ldc_I4_1);
var setIndicator = il.Create(OpCodes.Call, dynamicMethod.Module.ImportReference(typeof(Indicator).GetProperty("Injected").SetMethod));
il.InsertBefore(dynamicMethod.Definition.Body.Instructions.First(), setIndicator);
il.InsertBefore(setIndicator, ldTrue);
return dynamicMethod.Generate();
}
}
public class Foobar
{
public virtual int Invoke() => 1;
}
如上面的代码片段所示,为了拦截Foobar的Invoke方法,我们在GenerateNewMethod方法中根据这个方法创建了一个DynamicMethodDefinition对象(定义在MonoMod.Common包中),并在方法体的前面添加了两个IL指令将Indicator的Injected属性设置为True,该方法最终返回通过这个DynamicMethodDefinition对象生成的MethodBase对象。在Main方法中,我们利用HarmonyLib.Memory的静态方法DetourMethod将原始的Invoke方法“转移”到生成的方法上。即使我们调用的依然是Foobar对象的Invoke方法,但是拦截操作依然会被执行。
四、RealProxy/TransparentProxy
RealProxy/TransparentProxy是.NET Framework时代一种常用的方法拦截方案。如果目标类型实现了某个接口或者派生于MarshalByRefObject类型,我们就可以采用这种拦截方案。如果需要拦截某个类型的方法,我们可以定义如下这么一个FoobarProxy<T>类型,泛型参数T代表目标类型或者接口。和第一种方案一样,我们的代理对象依旧是封装目标对象,在实现的Invoke方案中,我们利用作为参数的IMessage 方法得到代表目标方法的MethodBase对象,进而利用它实现针对目标方法的调用。在目标方法调用之前,我们可以执行拦截操作。
public interface IFoobar
{
int Invoke();
} public class Foobar : IFoobar
{
public int Invoke() => 1;
} public class FoobarProxy<T> : RealProxy
{
public T _target;
public FoobarProxy(T target):base(typeof(T))
=> _target = target;
public override IMessage Invoke(IMessage msg)
{
Indicator.Injected = true;
IMethodCallMessage methodCall = (IMethodCallMessage)msg;
IMethodReturnMessage methodReturn = null;
object[] copiedArgs = Array.CreateInstance(typeof(object), methodCall.Args.Length) as object[];
methodCall.Args.CopyTo(copiedArgs, 0);
try
{
object returnValue = methodCall.MethodBase.Invoke(_target, copiedArgs);
methodReturn = new ReturnMessage(returnValue, copiedArgs, copiedArgs.Length, methodCall.LogicalCallContext, methodCall);
}
catch (Exception ex)
{
methodReturn = new ReturnMessage(ex, methodCall);
}
return methodReturn;
}
}
在Main方法中,我们创建目标Foobar对象,然后将其封装成一个FoobarProxy<IFoobar>对象。我们最终调用GetTransparentProxy方法创建出透明代理,并将其转换成IFoobar类型。当我们调用这个透明对象的任何一个方法的时候,定义在FoobarProxy<T>中的Invoke方法均会执行。
class Program
{
static void Main(string[] args)
{
var proxy = (IFoobar)(new FoobarProxy<IFoobar>(new Foobar()).GetTransparentProxy());
Debug.Assert(Indicator.Injected == false);
Debug.Assert(proxy.Invoke() == 1);
Debug.Assert(Indicator.Injected == true);
}
}
五、DispatchProxy
RealProxy/TransparentProxy仅限于.NET Framework项目中实现,在.NET Core中它具有一个替代类型,那就是DispatchProxy。我们可以采用如下的方式利用DispatchProxy实现我们所需的拦截功能。
class Program
{
static void Main(string[] args)
{
var proxy = DispatchProxy.Create<IFoobar, FoobarProxy<IFoobar>>();
((FoobarProxy<IFoobar>)proxy).Target = new Foobar(); Debug.Assert(Indicator.Injected == false);
Debug.Assert(proxy.Invoke() == 1);
Debug.Assert(Indicator.Injected == true);
}
} public interface IFoobar
{
int Invoke();
} public class Foobar : IFoobar
{
public int Invoke() => 1;
} public class FoobarProxy<T> : DispatchProxy
{
public T Target { get; set; } protected override object Invoke(MethodInfo targetMethod, object[] args)
{
Indicator.Injected = true;
return targetMethod.Invoke(Target, args);
}
}
动态方法拦截(AOP)的N种解决方案的更多相关文章
- 01_5_Struts_ActionMethod_DMI_动态方法调用
01_5_Struts_ActionMethod_DMI_动态方法调用 1. ActionMethod_DMI_动态方法调用 Action执行的时候并不一定要执行execute()方法 可以在配置文件 ...
- 动态代理的两种方式,以及区别(静态代理、JDK与CGLIB动态代理、AOP+IoC)
Spring学习总结(二)——静态代理.JDK与CGLIB动态代理.AOP+IoC 目录 一.为什么需要代理模式 二.静态代理 三.动态代理,使用JDK内置的Proxy实现 四.动态代理,使用cg ...
- c# AOP编程:Context与方法拦截
之前做AgentBooking时候,遇到两个问题比较棘手,一个是异常的传递与捕获:如何可以合理地在层层代码调用中统一传递并统一捕获异常.因为如果有一个做法,可以地方统一处理异常,可以使代码减少很多tr ...
- AOP的几种实现方法
C# 实现AOP 的几种常见方式 原文出处:http://www.cnblogs.com/zuowj/p/7501896.html AOP为Aspect Oriented Programming的缩写 ...
- JAVA高级架构师基础功:Spring中AOP的两种代理方式:动态代理和CGLIB详解
在spring框架中使用了两种代理方式: 1.JDK自带的动态代理. 2.Spring框架自己提供的CGLIB的方式. 这两种也是Spring框架核心AOP的基础. 在详细讲解上述提到的动态代理和CG ...
- js 动态加载事件的几种方法总结
本篇文章主要是对js 动态加载事件的几种方法进行了详细的总结介绍,需要的朋友可以过来参考下,希望对大家有所帮助 有些时候需要动态加载javascript事件的一些方法往往我们需要在 JS 中动态添 ...
- Spring中AOP的两种代理方式(Java动态代理和CGLIB代理)
第一种代理即Java的动态代理方式上一篇已经分析,在这里不再介绍,现在我们先来了解下GCLIB代理是什么?它又是怎样实现的?和Java动态代理有什么区别? cglib(Code Generation ...
- bugfree如何修改Bug7种解决方案的标注方法
Bug有7种解决方案的标注方法 By Design- 就是这么设计的,无效的Bug Duplicate - 这个问题别人已经发现了,重复的Bug External - 是个外部因素(比如浏览器.操作系 ...
- WPF编程,通过Double Animation动态旋转控件的一种方法。
原文:WPF编程,通过Double Animation动态旋转控件的一种方法. 版权声明:我不生产代码,我只是代码的搬运工. https://blog.csdn.net/qq_43307934/art ...
随机推荐
- edusrc上海交通大学证书
- 补:冲刺Day1
各个成员在 Alpha 阶段认领的任务: 任务 执行人 用户模块 高嘉淳 订单模块 覃泽泰 商品模块 莫政.卢耀恒 充值模块 卢耀恒 前端界面设计&代码 许梓莹.梁小燕 发布博客 莫政 明日各 ...
- caffe源码 全连接层
图示全连接层 如上图所示,该全链接层输入n * 4,输出为n * 2,n为batch 该层有两个参数W和B,W为系数,B为偏置项 该层的函数为F(x) = W*x + B,则W为4 * 2的矩阵,B ...
- KM 算法
KM 算法 可能需要先去学学匈牙利算法等二分图相关知识. 模板题-洛谷P6577 [模板]二分图最大权完美匹配 给 \(n\) 和 \(m\) 与边 \(u_i,v_i,w_i(1\le i\le m ...
- Flask开发技巧之参数校验
Flask开发技巧之参数校验 目录 Flask开发技巧之参数校验 1.请求参数分类 2.解决方案使用到的库 3.针对url查询参数与一般json格式 4.针对复杂json格式数据 本人平时开发中使用的 ...
- Swagger2配置
配置类 package top.yalong; import org.springframework.beans.factory.annotation.Value; import org.spring ...
- JVM虚拟机(一):类加载机制
类加载的时机 类加载的生命周期为: 加载.验证.准备.解析.初始化.使用.卸载七个阶段,其中验证.准备.解析三个阶段统称为连接.其中加载与连接时交叉执行的. 类必须初始化的六种情况 遇到new.g ...
- IDEA中flink程序报错找不到类
Idea中运行flink程序,报错找不到类,其中pom文件中一项依赖为: <dependency> <groupId>org.apache.flink</groupId& ...
- 交换机配置OSPF负载分担
组网图形 OSPF负载分担简介 等价负载分担ECMP(Equal-Cost Multiple Path),是指在两个网络节点之间同时存在多条路径时,节点间的流量在多条路径上平均分摊.负载分担的作用是减 ...
- Python高级语法-对象实例对象属性-类与实例,class方法静态方法等(4.6.1)
@ 目录 1.说明 2.代码 关于作者 1.说明 python中属性:类属性,实例属性 方法:类方法,实例方法,静态方法 想修改类属性,只能是类方法,因为只有类方法把cls(类)传入数据里面 静态方法 ...