Unity是一款知名的依賴注入容器,其支持通過自定義擴展來擴充功能。在Unity軟件包內默認包含了一個對象攔截(Interception)擴展定義。本篇文章將介紹如何使用對象攔截功能來幫助你分離橫切關注點(Separation of cross-cutting concerns)。
對象攔截簡介
對象攔截是一種AOP(Aspect-oriented programming)編程的實踐方法。其可幫助你保持業務類的純凈,而無需考慮諸如日志和緩存等外圍關注點。
在.NET中,實現AOP有多種方法。一種方式是采用編譯后處理方式,例如PostSharp。在編譯后,PostSharp通過修改IL代碼來諸如橫切代碼。
相反地,對象攔截是在運行時執行的,同時也意味着會有一些限制。依據不同的攔截器實現,會有如下這些約束:
- 透明代理攔截器:需要定義接口,或者要求類繼承自MarshalByRefObject。
- 接口攔截器:需要定義接口。
- 虛方法攔截器:僅需要方法被定義成virtual方法。
對象攔截如何工作
當從Unity容器請求目標對象時,將不會獲取到已配置的類的實例。實際上,將得到一個動態生成的代理對象,或者一個衍生類。
如果調用代理對象的一個方法,將可以在被調用方法執行前或執行后執行一些額外行為的代碼。那些定義行為的類需要實現ICallHandler接口。通過這些行為定義,我們可以訪問方法調用的參數列表,可以吞噬異常,或者可以返回自定義的異常。
附帶提一下,在不使用Unity容器的條件下,也是可以使用Unity攔截器的。
示例:將異常和方法調用參數列表記錄到日志中
在下面的示例中,我們將創建兩個自定義的行為,都實現了ICallHandler接口:
- LoggerCallHandler:將方法調用參數列表記錄到日志中。
- ExceptionLoggerCallHandler:將方法調用參數列表和異常信息及調用棧記錄到日志中。
ExceptionLoggerCallHandler定義如下:
1 internal class ExceptionLoggerCallHandler : ICallHandler 2 { 3 public IMethodReturn Invoke( 4 IMethodInvocation input, GetNextHandlerDelegate getNext) 5 { 6 IMethodReturn result = getNext()(input, getNext); 7 if (result.Exception != null) 8 { 9 Console.WriteLine("ExceptionLoggerCallHandler:"); 10 Console.WriteLine("\tParameters:"); 11 for (int i = 0; i < input.Arguments.Count; i++) 12 { 13 var parameter = input.Arguments[i]; 14 Console.WriteLine( 15 string.Format("\t\tParam{0} -> {1}", i, parameter.ToString())); 16 } 17 Console.WriteLine(); 18 Console.WriteLine("Exception occured: "); 19 Console.WriteLine( 20 string.Format("\tException -> {0}", result.Exception.Message)); 21 22 Console.WriteLine(); 23 Console.WriteLine("StackTrace:"); 24 Console.WriteLine(Environment.StackTrace); 25 } 26 27 return result; 28 } 29 30 public int Order { get; set; } 31 }
為了將行為應用到方法上,我們需要創建相應的HandlerAttribute來創建行為的實例。
1 internal class ExceptionLoggerAttribute : HandlerAttribute 2 { 3 public override ICallHandler CreateHandler(IUnityContainer container) 4 { 5 return new ExceptionLoggerCallHandler(); 6 } 7 }
在這個示例中,我們創建一個簡單的計算器類。同時為了使用接口攔截功能,我們還需創建一個接口類型,這樣才能應用指定的行為:
1 public interface ICalculator 2 { 3 [Logger] 4 int Add(int first, int second); 5 6 [ExceptionLogger] 7 int Multiply(int first, int second); 8 }
計算器類的實現還和常規的一樣。現在我們需要配置Unity容器:
1 IUnityContainer container = new UnityContainer(); 2 container.AddNewExtension<Interception>(); 3 container 4 .RegisterType<ICalculator, Calculator>() 5 .Configure<Interception>() 6 .SetInterceptorFor<ICalculator>(new InterfaceInterceptor()); 7 8 // Resolve 9 ICalculator calc = container.Resolve<ICalculator>(); 10 11 // Call method 12 calc.Add(1, 2);
當在容器上通過調用Resolve方法來嘗試獲得一個Calculate類的實例時,將會得到一個代理類實例。它的名字可能類似於 ‘DynamicModule.ns.Wrapped_ICalculator_83093f794c8d452e8af4524873a017de’。當調用此包裝類的某個方法時,CallHandlers將會被執行。
總結
Unity並不提供一個完整的AOP框架,因此使用它會有一些限制。但不管怎樣,使用Unity對象攔截功能來實現一些基本的AOP需求已經足夠了。