有这样的场景:我们已经完成了一套系统,但是客户提出要求,在进行重要操作时要记录日志。这时该如何应对?
一.直接修改源代码
拿到需求不加思索,OK,不就是记录日志吗,添加一个写日志的类,然后在重要操作中调用它的方法。
二.动态代理
方法1固然可以达到目的,却十分丑陋。先不说这样违背了基本的OO原则(对扩展开放,对修改封闭),当要记录日志的操作数量庞大时,就算Ctrl-c,Ctrl-V估计也得弄到手软。
但这个时候恰好是动态代理发挥作用的时候。
假设Subject包含了一个需要记录操作日志的方法Process:
{
public virtual void Process()
{
Console.WriteLine("Processing");
}
}
在摒弃了方法1后,我们可以这样来考虑,继承Subject并重写Process方法
{
public override void Process()
{
//TODO:Write Log
base.Process();
}
}
不过相比上面的方式,下面这种显得更优雅:
{
private Interceptor _interceptor;
public Proxy()
{
_interceptor = new Interceptor();
}
public override void Process()
{
_interceptor.Invoke(new Subject(), "Process", null);
}
}
public class Interceptor
{
public object Invoke(object obj, string methodName, object[] parameters)
{
Console.WriteLine(string.Format("before invoke {0}...", methodName));
var retObj= obj.GetType().GetMethod(methodName).Invoke(obj, parameters);
Console.WriteLine(string.Format("after invoke {0}...", methodName));
return retObj;
}
}
这样我们就可以将记录日志的动作全部移入Interceptor。
当然,如果所有的代理都需要手动去编写,工作量可想而知。但上面说了,这是动态代理擅长的领域。我们可以在运行时动态生成这些代理。下面就来说说如何用Emit动态生成这些代理。
其实,写到这个地方,思路已经很清晰了,我们只需要将上面那段代码里实现代理的代码换成用Emit来实现就可以了。
1.定义一个创建代理的类:
where T: class,new()
{
public static T CreateProxy()
{
......
}
}
2.接着进入关键部分:实现CreateProxy方法
(1).创建代理类:
var moduleBldr = asm.DefineDynamicModule("Main", "Proxy.dll");
var typeBldr = moduleBldr.DefineType(typeof(T).Name + "Proxy", TypeAttributes.Public, typeof(T));
(2).创建_interceptor实例变量
var fldInterceptor = typeBldr.DefineField("interceptor", typeof(Interceptor), FieldAttributes.Private);
(3).创建构造函数
var constructorBldr = typeBldr.DefineConstructor(MethodAttributes.Public, CallingConventions.Standard, null);
var il = constructorBldr.GetILGenerator();
il.Emit(OpCodes.Ldarg_0);
il.Emit(OpCodes.Newobj, typeof(Interceptor).GetConstructor(new Type[0]));
il.Emit(OpCodes.Stfld, fldInterceptor);
il.Emit(OpCodes.Ret);
(4).覆写需要记录操作日志的方法(这里选取了所有的public实例方法)
var methods = typeof(T).GetMethods(BindingFlags.Public | BindingFlags.Instance);
for (var i = 0; i < methods.Length; i++)
{
var paramTypes = GetParametersType(methods[i]);
var methodBlfr = typeBldr.DefineMethod(methods[i].Name, MethodAttributes.Public | MethodAttributes.Virtual, CallingConventions.Standard, methods[i].ReturnType, paramTypes);
il = methodBlfr.GetILGenerator();
il.Emit(OpCodes.Ldarg_0);
il.Emit(OpCodes.Ldfld, fldInterceptor);
il.Emit(OpCodes.Newobj, typeof(T).GetConstructor(new Type[0]));
il.Emit(OpCodes.Ldstr, methods[i].Name);
//创建参数数组
if (paramTypes == null)
{
il.Emit(OpCodes.Ldnull);
}
else
{
var parameters = il.DeclareLocal(typeof(object[]));
il.Emit(OpCodes.Ldc_I4, paramTypes.Length);
il.Emit(OpCodes.Newarr, typeof(object));
il.Emit(OpCodes.Stloc, parameters);
for (var j = 0; j < paramTypes.Length; j++)
{
il.Emit(OpCodes.Ldloc, parameters);
il.Emit(OpCodes.Ldc_I4, j);
il.Emit(OpCodes.Ldarg, j + 1);
il.Emit(OpCodes.Stelem_Ref);
}
il.Emit(OpCodes.Ldloc, parameters);
}
il.Emit(OpCodes.Callvirt, typeof(Interceptor).GetMethod("Invoke"));
if (methods[i].ReturnType == typeof(void))
{
il.Emit(OpCodes.Pop);
}
il.Emit(OpCodes.Ret);
}
(5).完成类型创建
asm.Save("Proxy.dll");
return Activator.CreateInstance(t) as T;
这样级基本完成了一个创建动态代理的工具类。接下来就来试试吧:
{
static void Main(string[] args)
{
var p = ProxyCreator<Subject>.CreateProxy();
p.Process();
}
}
结果如下图所示:
using System.Collections.Generic;
using System.Reflection;
using System.Reflection.Emit;
public class ProxyCreator<T>
where T: class,new()
{
public static T CreateProxy()
{
var asm = AppDomain.CurrentDomain.DefineDynamicAssembly(new AssemblyName("Proxy"), AssemblyBuilderAccess.RunAndSave);
var moduleBldr = asm.DefineDynamicModule("Main", "Proxy.dll");
var typeBldr = moduleBldr.DefineType(typeof(T).Name + "Proxy", TypeAttributes.Public, typeof(T));
//interceptor
var fldInterceptor = typeBldr.DefineField("interceptor", typeof(Interceptor), FieldAttributes.Private);
//construtor
var constructorBldr = typeBldr.DefineConstructor(MethodAttributes.Public, CallingConventions.Standard, null);
var il = constructorBldr.GetILGenerator();
il.Emit(OpCodes.Ldarg_0);
il.Emit(OpCodes.Newobj, typeof(Interceptor).GetConstructor(new Type[0]));
il.Emit(OpCodes.Stfld, fldInterceptor);
il.Emit(OpCodes.Ret);
//methods
var methods = typeof(T).GetMethods(BindingFlags.Public | BindingFlags.Instance);
for (var i = 0; i < methods.Length; i++)
{
var paramTypes = GetParametersType(methods[i]);
var methodBlfr = typeBldr.DefineMethod(methods[i].Name, MethodAttributes.Public | MethodAttributes.Virtual, CallingConventions.Standard, methods[i].ReturnType, paramTypes);
il = methodBlfr.GetILGenerator();
il.Emit(OpCodes.Ldarg_0);
il.Emit(OpCodes.Ldfld, fldInterceptor);
il.Emit(OpCodes.Newobj, typeof(T).GetConstructor(new Type[0]));
il.Emit(OpCodes.Ldstr, methods[i].Name);
//创建参数数组
if (paramTypes == null)
{
il.Emit(OpCodes.Ldnull);
}
else
{
var parameters = il.DeclareLocal(typeof(object[]));
il.Emit(OpCodes.Ldc_I4, paramTypes.Length);
il.Emit(OpCodes.Newarr, typeof(object));
il.Emit(OpCodes.Stloc, parameters);
for (var j = 0; j < paramTypes.Length; j++)
{
il.Emit(OpCodes.Ldloc, parameters);
il.Emit(OpCodes.Ldc_I4, j);
il.Emit(OpCodes.Ldarg, j + 1);
il.Emit(OpCodes.Stelem_Ref);
}
il.Emit(OpCodes.Ldloc, parameters);
}
il.Emit(OpCodes.Callvirt, typeof(Interceptor).GetMethod("Invoke"));
if (methods[i].ReturnType == typeof(void))
{
il.Emit(OpCodes.Pop);
}
il.Emit(OpCodes.Ret);
}
var t = typeBldr.CreateType();
asm.Save("Proxy.dll");
return Activator.CreateInstance(t) as T;
}
private static Type[] GetParametersType(MethodInfo method)
{
Type[] paramTypes = null;
if (method != null)
{
var parameters = method.GetParameters();
if (parameters.Length > 0)
{
paramTypes = new Type[parameters.Length];
for (var i = 0; i < parameters.Length; i++)
{
paramTypes[i] = parameters[i].ParameterType;
}
}
}
return paramTypes;
}
}
class Program
{
static void Main(string[] args)
{
var p = ProxyCreator<Subject>.CreateProxy();
p.Process();
}
}
public class Subject
{
public virtual void Process()
{
Console.WriteLine("Processing");
}
}
public class Interceptor
{
public object Invoke(object obj, string methodName, object[] parameters)
{
Console.WriteLine(string.Format("before invoke {0}...", methodName));
var retObj= obj.GetType().GetMethod(methodName).Invoke(obj, parameters);
Console.WriteLine(string.Format("after invoke {0}...", methodName));
return retObj;
}
}
谈到动态代理就会想到AOP,其实上面就是一个简单的AOP实现。这也是我学习Emit的初衷。鉴于对AOP认识不多,这里就不讨论了,有机会再share学习心得,也恳请大家为小弟推荐些学习AOP的资源。