Castle DynamicProxy 動態代理(C#)
- Castle Core版本 v4.4.0 Github
- .net core 2.2
- 本文章的被代理方法均爲同步方法,異步方法不適用。
基本概念
動態代理是實現代理模式的一種方法,而代理模式可以在不修改原有對象的情況下爲對象添加新的功能,也是面向切面(AOP)的一種實現。
對現有對象添加新功能,那麼相對於調用者來說接口應該是不變的,所以代理對象應該要與被代理對象實現相同的接口。並且一般來說在代理對象中都會有一個被代理對象的引用字段,那麼在代理對象中實現的接口就可以通過直接調用被代理對象,並且在調用被代理對象前後添加新的功能邏輯實現添加新功能。
- 靜態代理,手動爲一個類創建代理對象,在編譯期代理對象就已經存在。
- 動態代理,在運行時動態創建代理對象。
C# 中的動態代理可以通過System.Reflection.Emit,或者其他第三方類庫實現。前者需要一些中間語言的知識,所以這裏我選擇了Castle DynamicProxy類庫。
關於代理模式的更多介紹就不一一介紹了。
NuGet包
在VS的NuGet包管理器中搜索Castle.Core
添加到項目中即可。Castle.Core
包含了Logging
、DynamicProxy
、DictionaryAdapter
。只是這裏我們只用到了DynamicProxy。
創建代理對象
ProxyGenerator對象
ProxyGenerator
對象的作用是用來生成代理對象,下面的例子說明ProxyGenerator
對象的常用使用方式:
ProxyGenerator proxyGenerator = new ProxyGenerator();
SomeInterface proxyClass = proxyGenerator.CreateClassProxy<ImpClass>(new SomeInterceptor());
其中:
public interface SomeInterface
{
void DoSome();
}
//被代理類,也就是需要攔截這個類中的方法
//往這個類中添加功能
public class ImpClass : SomeInterface
{
public virtual void DoSome()
{
//....
}
}
注意:方法需要聲明爲 virtual,即虛方法。否則無法被代理。
從上面的例子可以看到我們是創建了ProxyGenerator
的實例對象,然後通過調用實例對象的CreateClassProxy()
方法來創建代理對象。那麼下面我們來看下CreateClassProxy()
的方法簽名:
public class ProxyGenerator : IProxyGenerator
{
//...
public TClass CreateClassProxy<TClass>(params IInterceptor[] interceptors) where TClass : class;
//...
}
在上面的方法定義中TClass
就是需要被代理的對象,而方法的參數是一個攔截器數組,意思就是創建一個對象通過一系列的攔截器攔截TClass
類的方法,而這個泛型方法的返回值也是TClass
對象,所以可以被隱式轉換爲對象實現的接口。
IInterceptor(攔截器)
攔截器這個概念在Castle DynamicProxy中可謂是非常的重要,由上面創建代理對象的ProxyGenerator
類就可以知道需要傳遞IInterceptor
類型作爲實參。這個IInterceptor
負責定義攔截器的行爲,也就是要怎麼攔截方法,和攔截方法後的動作等。
IInterceptor
是一個接口,該接口只有一個方法,負責定義攔截行爲:
public interface IInterceptor
{
void Intercept(IInvocation invocation);
}
可以看到在接口的Intercept()
方法中有一個IInvocation
的接口對象作爲形參,這個接口包含了被攔截(被代理對象)對象和方法的信息,我們可以看下這個接口的定義:
public interface IInvocation
{
object[] Arguments { get; }
Type[] GenericArguments { get; }
MethodInfo Method { get; }
MethodInfo MethodInvocationTarget { get; }
object Proxy { get; }
object ReturnValue { get; set; }
Type TargetType { get; }
IInvocationProceedInfo CaptureProceedInfo();
object GetArgumentValue(int index);
MethodInfo GetConcreteMethod();
MethodInfo GetConcreteMethodInvocationTarget();
void Proceed();
void SetArgumentValue(int index, object value);
}
其中包含了大量關於被代理對象的信息。
如果我們要創建攔截器就要創建一個類並且繼承IInterceptor
接口,如下:
public class SomeInterceptor : IInterceptor
{
public void Intercept(IInvocation invocation)
{
//Do before...
try
{
invocation.Proceed();
}catch
{
//...
}
//Do after...
}
}
invocation.Proceed()
是執行下一層的攔截器,當該攔截器是攔截器鏈的最後一個的時候則會直接調用被代理對象的方法。所以當攔截器鏈中只有一個攔截器時調用該方法也相當於直接調用被代理對象的方法。
到這裏我們就可以在把我們創建的攔截器當作實參傳遞進ProxyGenerator
對象進而創建出擁有特定攔截行爲的代理對象。
我們就可以在代理對象中對原對象進行異常捕獲,日誌記錄,事務等的處理。
帶參數構造函數的被代理對象
在我們上面所示的例子中ImpClass
就是一個帶有默認無參構造函數的被代理對象,ProxyGenerator
對象在調用CreateClassProxy<TClass>(IInterceptor[] interceptors)
方法創建代理對象的時候會嘗試調用被代理對象的默認構造函數,如果不存在默認構造函數則會拋出異常。
但是更多時候我們的被代理對象都是需要用帶參數的構造函數來創建,當我們對這些帶有參數的構造函數進行代理的時候應該怎麼做?
這時候我們一般是直接查看ProxyGenerator
對象中是否有對於有參構造函數的調用。我們可以看到在ProxyGenerator
中有一個方法可以傳遞參數到被代理對象的構造函數中,該方法定義如下:
public object CreateClassProxy(Type classToProxy, object[] constructorArguments, params IInterceptor[] interceptors);
可以注意到該方法並不是泛型方法,所以方法的返回值直接就是一個object,我們需要把它強制轉換爲我們需要的被代理對象的接口。該方法的第一個參數是被代理對象
,第二個參數是一個object數組,這個就是傳遞進被代理對象的有參構造函數的參數數組,創建代理對象的時候會尋找被代理對象的構造函數形參,直到找到與這個參數數組匹配的構造函數爲止,否則會拋出異常。第三個參數就是傳遞攔截器類型。
假如我們爲ImpClass
增加一個有參構造函數,如下:
public class ImpClass : SomeInterface
{
private string _s;
public ImpClass(string s)
{
this._s = s;
}
public void DoSome()
{
Console.WriteLine(_s);
}
}
那麼我們對該類創建代理對象的代碼也需要作如下更改:
ProxyGenerator proxyGenerator = new ProxyGenerator();
object[] args = {"Hello World"};
SomeInterface proxyClass = (SomeInterface)proxyGenerator.CreateClassProxy(ImpClass,args,new SomeInterceptor());
proxyClass.DoSome();
攔截器鏈
就像上面提到的,invocation.Proceed()
是進入下一個攔截器,當在下一個攔截器中也調用了invocation.Proceed()
的話就會再進入下一個攔截器,像這樣的一個從上到下,然後在從下到上冒泡的過程就構成了一個攔截器鏈。或者可以這麼說創建一層攔截器負責捕獲異常,一層攔截器負責日誌記錄等等。
創建攔截器鏈並沒有什麼新的知識,只是針對每個攔截器創建一個繼承IInterceptor
的對象,然後定義行爲。
可以看到CreateClassProxy
中的參數是一個IInterceptor
的數組,前面的params
關鍵字說明可以以可變參數列表的方式來調用函數傳遞參數。
public class ProxyGenerator : IProxyGenerator
{
//...
public TClass CreateClassProxy<TClass>(params IInterceptor[] interceptors) where TClass : class;
//...
}
例如,假設我們有兩個攔截器Interceptor1
,Interceptor2
那麼我們可以這樣來創建代理對象:
ProxyGenerator proxyGenerator = new ProxyGenerator();
SomeInterface proxyClass = proxyGenerator.CreateClassProxy<ImpClass>(new Interceptor1(),new Interceptor2());
把這些攔截器都當作實參傳遞給ProxyGenerator.CreateClassProxy()
對象就可以了,攔截器執行的順序就是按照參數列表裏面攔截器的順序從上往下一層一層調用。
總結
文章介紹了Castle DynamicProxy庫的基本使用,和對帶有有參構造函數的被代理對象的構造函數實參傳遞,和攔截器鏈的一些介紹。總的來說對於Castle DynamicProxy庫的使用都是比較簡單的。
需要注意的是以上的動態代理都是針對同步方法,異步方法的動態代理要更爲複雜一些。
本人的公衆號,有空就寫寫文章這樣,謝謝關注。