代理设计模式(二)动态代理
接上篇《代理设计模式(一)静态代理》本篇主要探究动态代理的实现方式。
概要
动态代理,故名思义,则为动态对指定业务对象进行添加代理处理。其实现的主要技术内容涉及有动态产生源码,代码编译,代码动态调用(涉及反射内容)。对于代码的编译,可参考:如何用C#动态编译、执行代码,主要使用到的为C#编译器“CodeDomProvider”,编译器参数“CompilerParameters”,主要设置所需要引用到的dll程序集,注意点,此处不生成可执行文件,同时设置生成到内存为true。
以下为简单的HelloWorld类,SayHello方法返回字符串内容的编译:
1 #region Compiler 2 private static void CompilerTest() 3 { 4 // 编译器 5 CodeDomProvider cdp = CodeDomProvider.CreateProvider("C#"); 6 7 // 编译器参数 8 CompilerParameters cps = new CompilerParameters(); 9 cps.ReferencedAssemblies.Add("System.dll"); 10 cps.GenerateExecutable = false; 11 cps.GenerateInMemory = true; 12 13 // 编译结果 14 CompilerResults cr = cdp.CompileAssemblyFromSource(cps, GenerateCode()); 15 16 if (cr.Errors.HasErrors) 17 { 18 Console.WriteLine("编译错误:"); 19 foreach (CompilerError err in cr.Errors) 20 { 21 Console.WriteLine(err.ErrorText); 22 } 23 } 24 else 25 { 26 // 通过反射,调用HelloWorld的实例 27 Assembly asm = cr.CompiledAssembly; 28 // 得到HelloWorld类中的SayHello方法 29 object objHelloWorld = asm.CreateInstance("DynamicCode.Test.HelloWorld"); 30 MethodInfo mi = objHelloWorld.GetType().GetMethod("SayHello"); 31 32 Console.WriteLine("动态代码调用结果:"); 33 Console.Write(mi.Invoke(objHelloWorld, null)); // 无参,有返回值的方法 34 } 35 36 Console.ReadLine(); 37 } 38 39 /// <summary> 40 /// 生成动态代码 41 /// </summary> 42 /// <returns></returns> 43 private static string GenerateCode() 44 { 45 StringBuilder sb = new StringBuilder(); 46 sb.Append("using System;"); 47 sb.Append(Environment.NewLine); 48 sb.Append("namespace DynamicCode.Test"); 49 sb.Append(Environment.NewLine); 50 sb.Append("{"); 51 sb.Append(Environment.NewLine); 52 sb.Append(" public class HelloWorld"); 53 sb.Append(Environment.NewLine); 54 sb.Append(" {"); 55 sb.Append(Environment.NewLine); 56 sb.Append(" public string SayHello()"); 57 sb.Append(Environment.NewLine); 58 sb.Append(" {"); 59 sb.Append(Environment.NewLine); 60 sb.Append(" return \"Hello world!\";"); 61 sb.Append(Environment.NewLine); 62 sb.Append(" }"); 63 sb.Append(Environment.NewLine); 64 sb.Append(" }"); 65 sb.Append(Environment.NewLine); 66 sb.Append("}"); 67 68 string code = sb.ToString(); 69 Console.WriteLine("========================="); 70 Console.WriteLine(code); 71 Console.WriteLine(); 72 73 return code; 74 } 75 #endregion
以下逐步实现动态代理所需要的各种条件步骤。
将代理类以源码形式创建出来:
1 namespace FD.Pattern.ConsoleApplicationProxy 2 { 3 // CreatedOn: 2016-04-12 4 // CreatedBy: Jackie Lee 5 /// <summary> 6 /// 日志代理 7 /// </summary> 8 public class RunLogProxy 9 { 10 private readonly string ProxyNameSpace = "FD.Pattern.ConsoleApplicationProxy"; 11 private static RunLogProxy _instance = new RunLogProxy(); 12 private RunLogProxy() { } 13 public static RunLogProxy Instance 14 { 15 get { return _instance; } 16 } 17 18 /// <summary> 19 /// 获取动态实例 20 /// </summary> 21 /// <param name="r"></param> 22 /// <returns></returns> 23 public object NewInstance(Runnable r) 24 { 25 string src = GenerateCode(); 26 // 编译 27 CodeDomProvider cdp = CodeDomProvider.CreateProvider("C#"); 28 CompilerParameters cps = new CompilerParameters(); 29 cps.ReferencedAssemblies.Add("System.dll"); 30 cps.ReferencedAssemblies.Add(System.IO.Path.GetFileName(Assembly.GetExecutingAssembly().Location)); 31 cps.GenerateExecutable = false; 32 cps.GenerateInMemory = true; 33 34 CompilerResults cr = cdp.CompileAssemblyFromSource(cps, src); 35 if(cr.Errors.HasErrors) 36 { 37 StringBuilder sb = new StringBuilder(); 38 sb.Append("Compile Code Failed:\n"); 39 foreach (CompilerError ce in cr.Errors) 40 sb.AppendFormat("{0};", ce.ErrorText); 41 throw new Exception(sb.ToString()); 42 } 43 // 通过反射,创建动态类的实例 44 Assembly asm = cr.CompiledAssembly; 45 object obj = asm.CreateInstance(ProxyNameSpace + ".RunnableLogProxy", false, BindingFlags.Instance | BindingFlags.Public, null, new object[] { r }, null, null); 46 47 return obj; 48 } 49 50 /// <summary> 51 /// 产生代理类源码 52 /// </summary> 53 /// <returns></returns> 54 private string GenerateCode() 55 { 56 string rtn = "\r\n"; 57 StringBuilder sb = new StringBuilder(); 58 sb.Append("using System;\r\n"); 59 sb.AppendFormat("namespace {0}\r\n", ProxyNameSpace); 60 sb.Append("{\r\n"); 61 62 sb.AppendFormat("\t// CreatedOn: 2016-04-12{0}", rtn); 63 sb.AppendFormat("\t// CreatedBy: Jackie Lee{0}", rtn); 64 sb.AppendFormat("\t/// <summary>{0}", rtn); 65 sb.AppendFormat("\t/// 日志代理{0}", rtn); 66 sb.AppendFormat("\t/// </summary>{0}", rtn); 67 sb.AppendFormat("\tpublic class RunnableLogProxy : Runnable{0}", rtn); 68 sb.Append("\t{\r\n"); 69 sb.AppendFormat("\t\tprivate Runnable run;{0}", rtn); 70 sb.AppendFormat("\t\tpublic RunnableLogProxy(Runnable r){0}", rtn); 71 sb.Append("\t\t{\r\n"); 72 sb.AppendFormat("\t\t\tthis.run = r;{0}", rtn); 73 sb.Append("\t\t}\r\n"); 74 sb.AppendFormat(Environment.NewLine); 75 sb.AppendFormat("\t\tpublic void Run(){0}", rtn); 76 sb.Append("\t\t{\r\n"); 77 sb.AppendFormat("\t\t\tConsole.WriteLine(\"the car is begin to run...\");{0}", rtn); 78 sb.AppendFormat("\t\t\trun.Run();{0}", rtn); 79 sb.AppendFormat("\t\t\tConsole.WriteLine(\"the car stops running\");{0}", rtn); 80 sb.Append("\t\t}\r\n"); 81 sb.Append("\t}\r\n"); 82 sb.Append("}\r\n"); 83 return sb.ToString(); 84 } 85 } 86 }
此时,在为指定的业务类添加日志代理,只需要简单通过如下方式调用即可。
1 Runnable r = (Runnable)RunLogProxy.Instance.NewInstance(c); 2 r.Run();
此时,只要实现接口Runnable,即可对其的Run方法进行动态代理,加上对应的运行日志信息。但是,这还是有局限性的。只能针对Runnable进行代理实现,同时还只能对Runnable中的Run方法实现代理。
为了实现用户能动态自己定义对所需要的接口类型及所需要添加的操作,还需要进一步地完善。
先将动态代理类,实现按接口产生代理方法列表,将NewInstance方法,修改为
1 public object NewInstance(Type interfaceType, object objInstance)
同时将接口类型及需要代理的对象传递进去,然后,在产生源码处,实现对接口方法的产生。
1 /// <summary> 2 /// 产生代理类源码 3 /// </summary> 4 /// <returns></returns> 5 private string GenerateCode(Type interfaceType) 6 { 7 MethodInfo[] mis = interfaceType.GetMethods(BindingFlags.Public | BindingFlags.Instance); 8 string rtn = "\r\n"; 9 StringBuilder sb = new StringBuilder(); 10 sb.Append("using System;\r\n"); 11 sb.AppendFormat("namespace {0}\r\n", ProxyNameSpace); 12 sb.Append("{\r\n"); 13 14 sb.AppendFormat("\t// CreatedOn: 2016-04-12{0}", rtn); 15 sb.AppendFormat("\t// CreatedBy: Jackie Lee{0}", rtn); 16 sb.AppendFormat("\t/// <summary>{0}", rtn); 17 sb.AppendFormat("\t/// 日志代理{0}", rtn); 18 sb.AppendFormat("\t/// </summary>{0}", rtn); 19 sb.AppendFormat("\tpublic class RunnableLogProxy : {0}\r\n", interfaceType.Name); 20 sb.Append("\t{\r\n"); 21 sb.AppendFormat("\t\tprivate {0} run;\r\n", interfaceType.Name); 22 sb.AppendFormat("\t\tpublic RunnableLogProxy({0} r)\r\n", interfaceType.Name); 23 sb.Append("\t\t{\r\n"); 24 sb.AppendFormat("\t\t\tthis.run = r;{0}", rtn); 25 sb.Append("\t\t}\r\n"); 26 sb.AppendFormat(Environment.NewLine); 27 28 sb.Append(GetMethodCode(mis)); 29 30 sb.Append("\t}\r\n"); 31 sb.Append("}\r\n"); 32 return sb.ToString(); 33 } 34 35 /// <summary> 36 /// 产生接口方法列表 37 /// </summary> 38 /// <param name="mis"></param> 39 /// <returns></returns> 40 private string GetMethodCode(MethodInfo[] mis) 41 { 42 StringBuilder sb = new StringBuilder(); 43 foreach (MethodInfo mi in mis) 44 { 45 sb.AppendFormat("\t\tpublic void {0}()\r\n", mi.Name); 46 sb.Append("\t\t{\r\n"); 47 sb.Append("\t\t\tConsole.WriteLine(\"the car is begin to run...\");\r\n"); 48 sb.AppendFormat("\t\t\trun.{0}();\r\n", mi.Name); 49 sb.Append("\t\t\tConsole.WriteLine(\"the car stops running\");\r\n"); 50 sb.Append("\t\t}\r\n"); 51 } 52 return sb.ToString(); 53 }
此时,可以针对NewInstance方法中的interfaceType类型接口的所有方法产生代理。此时,在Runnable接口中添加一个Stop方法。
1 // CreatedOn: 2016-04-12 2 // CreatedBy: Jackie Lee 3 /// <summary> 4 /// 移动的接口 5 /// </summary> 6 public interface Runnable 7 { 8 void Run(); 9 void Stop(); 10 }
通过
1 Runnable r = (Runnable)RunLogProxy.Instance.NewInstance(typeof(Runnable), c);
调用后,可产生如下代码效果:
发现,此时不但Run方法产生了,同时,Stop方法也动态产生了。即RunnableLogProxy代理类同时实现了Runnable接口的所有接口方法。
但,此时对业务添加的代理都还是默认的,是写死的,如上所述则为在真正方法调用前后输出两个信息。下面逐步实现让用户自行定义前后所需要执行的代理内容。
为了实现该功能,首先定义一个代理内容执行的接口InvocationHandler,然后用户如果需要对任何类实现代理,则继承该接口。
1 namespace FD.Pattern.ConsoleApplicationProxy 2 { 3 // CreatedOn: 2016-04-13 4 // CreatedBy: Jackie Lee 5 /// <summary> 6 /// 调用接口 7 /// </summary> 8 public interface InvocationHandler 9 { 10 /// <summary> 11 /// 实现对代理的调用 12 /// </summary> 13 /// <param name="objProxy">代理对象</param> 14 /// <param name="mi">方法</param> 15 void Invoke(object objProxy, MethodInfo mi); 16 } 17 }
如,现在实现对Runnable接口进行代理,让调用Runnable接口实例的方法进行日志记录,代理实现类为RunLogHandler
1 // CreatedOn: 2016-04-13 2 // CreatedBy: Jackie Lee 3 /// <summary> 4 /// 代理实现处理 5 /// </summary> 6 public class RunLogHandler : InvocationHandler 7 { 8 private object target; // 目标代理对象 9 public RunLogHandler(object target) 10 { 11 this.target = target; 12 } 13 14 public void Invoke(object objProxy, MethodInfo mi) 15 { 16 Console.WriteLine("{0} instance begin call the method {1} at {2}", objProxy.GetType().FullName, mi.Name, DateTime.Now); 17 mi.Invoke(target, null); 18 Console.WriteLine("{0} instance end call the method {1} at {2}", objProxy.GetType().FullName, mi.Name, DateTime.Now); 19 } 20 }
此时,测试调用
1 RunLogHandler rlh = new RunLogHandler(c); 2 Runnable r = (Runnable)RunLogProxy.Instance.NewInstance(typeof(Runnable), rlh); 3 r.Run(); 4 Console.WriteLine("=========================="); 5 Thread.Sleep(1500); // 为了让Stop方法调用时有时间差效果 6 r.Stop();
运行效果:
此时,神奇般地实现了对指定的Runnable接口实现RunnableLogHandler的接口。后续,则可将RunnnableLogProxy永久性地隐藏起来,无需再对其进行任何改动。需要做的,只是通过具体业务对具体对象进行代理而去实现InvocationHandler处理相应的代理内容。
以上只为特定的无返回无参方法的代理,后续再实现对有参有返回的代理。
有参有返回处理:略。
当完成了有参及有返回值处理后,可实现以下效果:
1 RunLogHandler rlh = new RunLogHandler(c); 2 Runnable r = (Runnable)RunLogProxy.Instance.NewInstance(typeof(Runnable), rlh); 3 r.Run(); 4 string state = r.State("Ferrari"); 5 Console.WriteLine("The car state info:{0}", state); 6 int n = r.RunLength(DateTime.Now); 7 Console.WriteLine("The car run {0}", n); 8 Console.WriteLine("=========================="); 9 Thread.Sleep(1500); // 为了让Stop方法调用时有时间差效果 10 r.Stop();