本文主要展示在C#中如何使用Castle DynamicProxy來解耦logging體系
簡介
在這篇文章中,我將帶你在.NET環境中實現Aspect-Oriented Programming (AOP) ,演示如何使用Castle DynamicProxy創建一個切面。在開始之前,我簡單的介紹下AOP and IoC,如果你已經熟悉這些概念,你可以直接跳過下面的步驟。
什么是AOP?
Aspect-oriented programming (AOP) 面向切面編程是一個編程范式,以允許增加模塊化橫切關注點的分離。An aspect (方面,AOP概念中的術語,為了不混淆,下文使用英文表示,不做中文翻譯)是一種常見的功能,通常是分散在類和對象層次結構中的公用方法。這些行為看起來有結構,但使用傳統的面向對象編程(OOP)無法找到一個方式來表達它。
aspect一個比較貼切的例子是本文中將要討論的日志記錄功能,通常你需要在代碼中編寫日志,但是日志功能通常並不屬於領域對象的職責。
使用AOP方法,我們可以創建這些aspects的橫切關注點,並使用多種技術將它們集中附加到領域對象。IL代碼編織和攔截是廣泛使用的方法。在本文中,我將帶你使用Castel Windsor動態框架來處理aspects的創建和應用。
控制反轉(Inversion of control=Ioc)/依賴注入容器(DI Container)
IoC容器是一個用來在需要時自動創建和依賴項注入的框架。DI容器可以幫助我們以簡單和更有效的方式管理應用程序中的依賴項
大多數的主流的DI(依賴注入)容器具有攔截的內置支持。這是一項高級技術,使用它可以攔截方法調用並改變在運行時期間的域對象的行為。我們將利用此功能附加aspects到我們的域對象。我選擇的DI框架是Castle Windsor ,它的 DynamicProxy ,是比較流行的應用aspects的方法之一。
在CodeProject有很多很好的文章和博客提供了關於(IoC)主題更詳細的資料。對IoC更大范圍的詳細討論不在本文的范圍內。
使用Castle DynamicProxy進行攔截
Castle DynamicProxy是一個在運行時生成.NET代理的庫。 它允許你動態地更改和擴展業務對象的行為。因為橫切關注點與核心領域模型的徹底解耦使得你的域模型更容易維護。如果為任何組件指定攔截,Castle 會自動創建代理。可以使用攔截器注入特定行為到代理中。
你可能會好奇這整件事的內部如何工作。每當調用方請求業務對象(具體類),IoC容器在DynamicProxy的幫助下,解析並將其包裝在一個包含指定的攔截器的代理對象中。然后容器返回代理對象到調用方。然后,調用方直接與代理進行交互。 代理截取每個針對業務對象的方法調用,並讓請求按照流程通過攔截器傳遞
下圖顯示請求如何進入代理。您可以看到請求在實際執行方法之前和之后都要通過所有攔截器。
在項目中使用 Castle DynamicProxy的步驟
· 使用Nuget下載和安裝 ‘Castle.Windsor’ 包.
· 實現 IInterceptor 接口. 這是要被 DynamicProxy的接口.
- 實現IRegistration 接口並且注冊組件. 注冊攔截器其次是業務組件。指定每個業務組件要使用的攔截器。
- 為Windsor創建靜態實例容器(IWindsorContainer),用該組件注冊信息初始化它。.
這幾乎是Castle DynamicProxy需要的所有配置
開始編碼
晴朗的一天在班加羅爾微風習習,這樣的天氣條件適合發射火箭!讓我們開始我們的示例應用程序。 此應用程序包含業務對象'Rocket'我們使用一個控制台應用程序來模擬火箭的發射。
接口包含一個簽名叫” Launch”的行為。
public interface IRocket
{
void Launch(int delaySeconds);
}
然后我們來實現接口中的”Launch”方法。
public class Rocket : IRocket
{
public string Name { get; set; }
public string Model { get; set; }
public void Launch(int delaySeconds)
{
Console.WriteLine(string.Format(" {0} 秒收啟動火箭發射", delaySeconds));
Thread.Sleep(1000 * delaySeconds);
Console.WriteLine("恭喜, 你的火箭發射成功");
}
}
是時候來創建我們第一個攔截器了.我們需要繼承實現IInterceptor ,這個接口最終會被DynamicProxy使用.
正如你在下面看到的,我們在方法進入時進行日志記錄,通過調用invocation.Proceed()來執行真正的業務行為,然后在方法執行成功后、發生異常和退出時分別進行了日志記錄。我們不在需要在我們的業務模型中編寫相關日志記錄的代碼。我們只需要將LoggingInterceptor附加到需要日志記錄的組件上。
internal class LoggingInterceptor : IInterceptor
{
public void Intercept(IInvocation invocation)
{
var methodName = invocation.Method.Name;
try
{
Console.WriteLine(string.Format("執行方法:{0}, 參數: {1}", methodName, string.Join(",", invocation.Arguments)));
invocation.Proceed();
Console.WriteLine(string.Format("成功執行了方法:{0}", methodName));
}
catch (Exception e)
{
Console.WriteLine(string.Format("方法:{0}, 異常信息:{1}", methodName, e.Message));
throw;
}
finally
{
Console.WriteLine(string.Format("退出方法:{0}", methodName));
}
}
}
DynamicProxy 代理提供的對象是非常有用的。它使你可以訪問當前方法的方法信息、參數、返回代碼值和許多其他細節,如你下面看到的。.
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 container (IWindsorContainer)創建靜態實例, 使用組件注冊信息對它進行初始化(ComponentRegistration)。
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>();
}
}
在控制台應用中啟動應用程序
private static void Main(string[] args)
{
DependencyResolver.Initialize();
//resolve the type:Rocket
var rocket = DependencyResolver.For<IRocket>();
//method call
try
{
rocket.Launch(5);
}
catch (Exception ex)
{
}
System.Console.ReadKey();
}
讓我們看一下控制台的輸出。 不出所料,我們的LoggingInterceptor攔截方法調用並自動記錄方法進入和退出。由於動態代理!. Thanks to DynamicProxy !
知識點
這是一個介紹性的文章,使初學者和中級開發人員理解使用 Castle Windsor DynamicProxy來理解AOP的基本概念。在未來的幾天,我將不斷更新這篇文章,以演示如何在一個Web Api項目中使用Log4net和動態代理在實現此日志記錄解決方案