在本文中,我將引導您了解.NET環境中的面向方面編程(AOP)概念,以及如何使用Castle DynamicProxy創建和附加方面。在我們開始之前,讓我快速介紹AOP和 IoC。如果您已經熟悉這些概念,則可以完全跳過本節。
什么是AOP?
方面 - 面向對象編程 ( AOP)是一種 編程 范式,旨在通過允許的橫切關注分離,以增加模塊性。 一個方面是通常分散在方法,類和對象層次結構中的常見功能。看起來像它的行為具有結構,但是找不到使用傳統的面向對象技術來表達它的方法。
一個很好的例子就是日志記錄,我將在本文中詳細討論。通常,您在代碼庫中寫入信息豐富的日志,但日志記錄是您的類或對象模型真的不應該關心的,因為它不代表域對象。
使用AOP方法,我們可以創建這些交叉關注的方面,並使用多種技術將它們集中在域對象上。IL代碼編織和截取是廣泛使用的方法。在本文中,我將介紹使用Castel Windsor框架動態創建和應用方面的過程。
控制反轉(IoC)/依賴注入(DI)容器
IoC容器是一種在需要時自動創建和注入依賴項的框架。DI容器幫助我們以簡單和更有效的方式管理應用程序中的依賴關系。
大多數主流DI(依賴注入)容器都內置支持攔截。使用這種方法,您可以在運行時調用和更改域對象的行為來攔截該方法。我們將利用此功能將方面附加到我們的域對象。我的DI框架選擇是城堡溫莎,其DynamicProxy是應用方面的流行方式之一。
Code Project中有很多很好的文章和不同的博客,可以為您提供有關此(IoC)主題的更多詳細信息。關於IoC的詳細討論超出了本文的范圍。
攔截使用Castle DynamicProxy
Castle DynamicProxy是一個用於在運行時生成.NET代理的庫。它允許您動態地更改和擴展業務對象的行為。這使得您的域模型更加可維護,因為交叉關切純粹與核心域模型脫鈎。如果為任何組件指定攔截器,Castle將自動創建代理。您使用攔截器將代理注入行為。
你可能想知道這整個事情在內部如何工作。每當調用者請求業務對象(具體類)時,IoC容器將在DynamicProxy的幫助下解析並將其包裝在包含指定攔截器的代理對象中。容器然后將代理的對象返回給調用者。來電者然后直接與代理人進行交互。代理攔截對業務對象的每個方法調用,並讓請求流過攔截器管道。
下圖顯示了請求如何流入代理。您可以看到請求在實際執行方法之前和之后通過所有攔截器。
在您的項目中設置Castle DynamicProxy的步驟
- 從NuGet下載並安裝“Castle.Windsor”軟件包。
- 實現IInterceptor接口。這是DynamicProxy將要使用的接口。
- 實施IRegistration界面並注冊您的組件。注冊攔截器后跟業務組件。指定要與每個業務組件一起使用的攔截器。
- 創建Windsor容器(IWindsorContainer)的靜態實例,使用組件注冊信息進行初始化。
這幾乎是配置Castle DynamicProxy所需要的!
使用代碼
讓我們從我們的示例應用程序開始。此應用程序包含一個業務對象“火箭”,我們使用控制台應用程序啟動。
接口包含一個稱為“啟動”的單一方法簽名。
public interface IRocket { void Launch(int delaySeconds); }
允許通過實現唯一需要的方法“啟動”實現接口。
public class Rocket: IRocket { public string Name { get; set; } public string Model { get; set; } public void Launch(int delaySeconds) { Console.WriteLine(string.Format("Launching rocket in {0} seconds",delaySeconds)); Thread.Sleep(1000 * delaySeconds); Console.WriteLine("Congratulations! You have successfully launched the rocket"); } }
時間來創建我們的第一個攔截器。我們可以通過實現IInterceptor接口來實現。這是DynamicProxy將要使用的接口。
如下所示,我們正在登錄方法條目,調用執行實際方法的invocation.Proceed()方法,登錄成功執行,登錄異常和退出登錄。我們不必在其中編寫日志記錄代碼我們的業務模式了!我們只需要將LoggingInterceptor附加到需要記錄的組件上。
public class LoggingInterceptor : IInterceptor { public void Intercept(IInvocation invocation) { var methodName = invocation.Method.Name; try { Console.WriteLine(string.Format("Entered Method:{0}, Arguments: {1}", methodName, string.Join(",", invocation.Arguments))); invocation.Proceed(); Console.WriteLine(string.Format("Sucessfully executed method:{0}", methodName)); } catch (Exception e) { Console.WriteLine(string.Format("Method:{0}, Exception:{1}", methodName, e.Message)); throw; } finally { Console.WriteLine(string.Format("Exiting Method:{0}", methodName)); } }
DynamicProxy公開的IInvocation對象非常有用。它可以訪問當前的MethodInfo,Arguments,ReturnValue和許多其他細節,如下所示。
public interface IInvocation { object[] Arguments { get; } Type[] GenericArguments { get; } object InvocationTarget { get; } MethodInfo Method { get; } MethodInfo MethodInvocationTarget { get; } object Proxy { get; } object ReturnValue { get; set; } Type TargetType { get; } object GetArgumentValue(int index); MethodInfo GetConcreteMethod(); MethodInfo GetConcreteMethodInvocationTarget(); void Proceed(); void SetArgumentValue(int index, object value); }
實施IRegistration界面並注冊您的組件。注冊攔截器后跟業務組件。指定要與每個業務組件一起使用的攔截器。您可能已經注意到,LoggingInterceptor附加到我們唯一的業務組件Rocket
public class ComponentRegistration : IRegistration { public void Register(IKernelInternal kernel) { kernel.Register( Component.For<LoggingInterceptor>() .ImplementedBy<LoggingInterceptor>()); kernel.Register( Component.For<IRocket>() .ImplementedBy<Rocket>() .Interceptors(InterceptorReference.ForType<LoggingInterceptor>()).Anywhere); } }
創建Windsor容器(IWindsorContainer)的靜態實例,使用組件注冊信息進行初始化。
public class DependencyResolver { private static IWindsorContainer _container; //Initialize the container public static void Initialize() { _container = new WindsorContainer(); _container.Register(new ComponentRegistration()); } //Resolve types public static T For<T>() { return _container.Resolve<T>(); } }
用於運行我們的代碼的微型控制台應用程序。
internal class Program { public static void Main(string[] args) { //Initialize the dependency resolver DependencyResolver.Initialize(); //resolve the type:Rocket var rocket = DependencyResolver.For<IRocket>(); //method call try { rocket.Launch(5); } catch (Exception ex) { } System.Console.ReadKey(); } }
讓我們看看控制台輸出。正如你所期望的,我們的LoggingInterceptor攔截了方法調用和記錄的方法條目並自動退出。感謝DynamicProxy!
興趣點
這是一篇介紹性文章,讓初學者和中級開發人員使用Castle Windsor DynamicProxy了解AOP的基本概念。在未來的日子里,我將繼續更新本文,並展示如何在Web Api項目中使用Log4net和DynamicProxy實現此解決方案。