代理設計模式(二)動態代理

接上篇《代理設計模式(一)靜態代理》本篇主要探究動態代理的實現方式。html

概要

  動態代理,故名思義,則爲動態對指定業務對象進行添加代理處理。其實現的主要技術內容涉及有動態產生源碼,代碼編譯,代碼動態調用(涉及反射內容)。對於代碼的編譯,可參考:如何用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
View Code

  如下逐步實現動態代理所須要的各類條件步驟。ide

 將代理類以源碼形式建立出來:測試

 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 }
View Code

此時,在爲指定的業務類添加日誌代理,只須要簡單經過以下方式調用便可。ui

1 Runnable r = (Runnable)RunLogProxy.Instance.NewInstance(c);
2 r.Run();
View Code

 此時,只要實現接口Runnable,便可對其的Run方法進行動態代理,加上對應的運行日誌信息。可是,這仍是有侷限性的。只能針對Runnable進行代理實現,同時還只能對Runnable中的Run方法實現代理。this

爲了實現用戶能動態本身定義對所須要的接口類型及所須要添加的操做,還須要進一步地完善。spa

先將動態代理類,實現按接口產生代理方法列表,將NewInstance方法,修改成設計

1 public object NewInstance(Type interfaceType, object objInstance)

同時將接口類型及須要代理的對象傳遞進去,而後,在產生源碼處,實現對接口方法的產生。3d

 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 }
View Code

 此時,能夠針對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     }
View Code

 經過

 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 }
View Code

如,如今實現對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     }
View Code

此時,測試調用

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();
View Code

相關文章
相關標籤/搜索