Castle DynamicProxy動態生成透明代理類型,
實體不需要繼承MarshalByRef、ContextBoundObject便可以實現代理類
基於透明代理的功能,可以實現對方法調用的攔截處理,例如NHibernate用它實現延遲加載
DP的使用非常簡單,內部沒有使用反射,而是採用Emit、委託等方式生成代理類型,調用真實類的方法,性能方面也沒有太多損失
基本示例
引用的命名空間:
using
Castle.Core.Interceptor;
using
Castle.DynamicProxy; |
1
2
|
using
Castle.Core.Interceptor;
using
Castle.DynamicProxy; |
1
2
3
4
5
6
7
8
9
|
public
class
SimpleSamepleEntity
{
public
virtual
string
Name { get ;
set ; }
public
virtual
int
Age { get ;
set ; }
public
override
string
ToString() {
return
string .Format( "{{ Name: \"{0}\", Age: {1} }}" ,
this .Name,
this .Age);
}
} |
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
|
public
class
CallingLogInterceptor : IInterceptor
{
private
int
_indent = 0; private
void
PreProceed(IInvocation invocation)
{
if
( this ._indent > 0)
Console.Write( " " .PadRight( this ._indent
* 4, ' ' ));
this ._indent++;
Console.Write( "Intercepting: "
+ invocation.Method.Name +
"(" );
if
(invocation.Arguments !=
null
&& invocation.Arguments.Length > 0) for
( int
i = 0; i < invocation.Arguments.Length; i++)
{
if
(i != 0) Console.Write( ", " );
Console.Write(invocation.Arguments[i] ==
null ?
"null" : invocation.Arguments[i].GetType() ==
typeof ( string )
?
"\""
+ invocation.Arguments[i].ToString() + "\"" : invocation.Arguments[i].ToString());
}
Console.WriteLine( ")" );
}
private
void
PostProceed(IInvocation invocation)
{
this ._indent--;
}
public
void
Intercept(IInvocation invocation)
{
this .PreProceed(invocation);
invocation.Proceed();
this .PostProceed(invocation);
}
} |
1
2
3
4
5
6
7
8
|
ProxyGenerator generator =
new
ProxyGenerator(); CallingLogInterceptor interceptor =
new
CallingLogInterceptor(); SimpleSamepleEntity entity = generator.CreateClassProxy<SimpleSamepleEntity>(interceptor);
entity.Name =
"Richie" ;
entity.Age = 50;
Console.WriteLine( "The entity is: "
+ entity); Console.WriteLine( "Type of the entity: "
+ entity.GetType().FullName);
Console.ReadKey(); |
上面示例使用CreateClassProxy方法創建了一個代理對象entity,從圖中輸出的entity類型可以看到他的類型爲Castle.Proxies.SimpleSampleEntity
當調用代理對象的任何方法時,將使用CallingLogInterceptor這個攔截器進行處理,在CallingLogInterceptor中輸出了調用日誌信息
在攔截器的接口方法Intercept中,可以修改入參,決定是否調用真實方法,修改返回結果等
可以攔截的方法:
1. 對於Class Proxy類型的代理,因爲DP使用繼承和override虛方法的方式實現代理,所以被攔截的方法必須爲virtual類型,非virtual類型的方法無法攔截
2. 實體繼承自Object、MarshalByRefObject等對象的方法,如果在實體類中沒有override將無法攔截
DP會將動態創建的代理類型緩存起來
Interceptor Pipeline
創建代理對象時可以指定多個攔截器,例如再簡單的實現一個攔截器:
1
2
3
4
5
6
7
8
|
public
class
SimpleLogInterceptor : IInterceptor
{
public
void
Intercept(IInvocation invocation)
{
Console.WriteLine( ">>"
+ invocation.Method.Name);
invocation.Proceed();
}
} |
1
2
3
4
5
6
7
8
9
|
ProxyGenerator generator =
new
ProxyGenerator(); SimpleSamepleEntity entity = generator.CreateClassProxy<SimpleSamepleEntity>(
new
SimpleLogInterceptor(),
new
CallingLogInterceptor());
entity.Name =
"Richie" ;
entity.Age = 50;
Console.WriteLine( "The entity is: "
+ entity); Console.WriteLine( "Type of the entity: "
+ entity.GetType().FullName);
Console.ReadKey(); |
多個攔截器之間以pipeline方式處理調用順序:
實際中並不一定所有方法都需要運用全部的攔截器,對方法調用有選擇性的選擇攔截器有2種方式,例如:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
public
class
InterceptorSelector : IInterceptorSelector
{
public
IInterceptor[] SelectInterceptors(Type type, MethodInfo method, IInterceptor[] interceptors)
{
if
(method.Name.StartsWith( "set_" ))
return
interceptors; else
return
interceptors.Where(i => i
is CallingLogInterceptor).ToArray<IInterceptor>();
}
}
public
class
InterceptorFilter : IProxyGenerationHook
{
public
bool
ShouldInterceptMethod(Type type, MethodInfo memberInfo)
{
return
memberInfo.IsSpecialName &&
(memberInfo.Name.StartsWith( "set_" ) || memberInfo.Name.StartsWith( "get_" ));
}
public
void
NonVirtualMemberNotification(Type type, MemberInfo memberInfo)
{
}
public
void
MethodsInspected()
{
}
} |
1
2
3
4
5
6
7
8
9
10
|
ProxyGenerator generator =
new
ProxyGenerator(); var options =
new
ProxyGenerationOptions( new
InterceptorFilter()) { Selector =
new
InterceptorSelector() }; SimpleSamepleEntity entity = generator.CreateClassProxy<SimpleSamepleEntity>(
options,
new
SimpleLogInterceptor(),
new
CallingLogInterceptor()); entity.Name =
"Richie" ;
entity.Age = 50;
Console.WriteLine( "The entity is: "
+ entity); Console.WriteLine( "Type of the entity: "
+ entity.GetType().FullName);
Console.ReadKey(); |
IProxyGenerationHook接口決定整個方法是否運用攔截器,他是在動態構造代理類型的時候使用的;而IInterceptorSelector接口決定某個方法該運用哪些攔截器,他在每次調用被攔截的方法時執行
上面的示例只對setter和getter方法進行攔截,並且對getter方法只使用CallingLogInterceptor這個攔截器
Proxy Types
代理類型有class proxy、interface proxy with target、interface proxy without target、interface proxy with interface target幾種
上面示例使用的是class proxy,使用CreateClassProxy方法創建,該方法的某些版本要求有默認構造器,不使用默認構造器時則必須傳入構造參數。代理對象爲class proxy類型時,被攔截的方法必須爲virtual,non-virtual的方法無法實現攔截
interface proxy with target其實跟class proxy差不多,在創建代理對象時client指定接口,並且提供一個實現了該接口的對象作爲真實對象,DP將創建這個接口的代理對象,對代理對象方法的調用經過攔截器處理之後,最終將調用真實對象相應的方法。與class proxy的不同之處在於,真實對象的方法不必是virtual類型也可以實現攔截
interface proxy without target比較特殊,創建代理時只需要指定一個接口就可以,DP自動根據接口構造一個實現的類,作爲代理對象的類型,但這個代理類只能用於攔截目的,無法像class proxy一樣在攔截器中調用真實對象的處理方法。比如在提供了多個攔截器時,最後一個攔截器的接口方法中不能調用invocation.Proceed()方法,否則會拋異常(因爲真實對象根本不存在,只有一個假的代理對象)
interface proxy with interface target與interface proxy with target基本類似,但他提供了一個更改被代理對象(真實對象)的機會,示例如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
|
public
interface
IStorageNode
{
bool
IsDead { get ;
set ; }
void
Save( string
message); }
public
class
StorageNode : IStorageNode
{
private
string
_name; public
StorageNode( string
name) {
this ._name = name;
}
public
bool
IsDead { get ;
set ; }
public
void
Save( string
message) {
Console.WriteLine( string .Format( "\"{0}\"
was saved to {1}" , message, this ._name));
}
}
public
class
DualNodeInterceptor : IInterceptor
{
private
IStorageNode _slave;
public
DualNodeInterceptor(IStorageNode slave)
{
this ._slave = slave;
}
public
void
Intercept(IInvocation invocation)
{
IStorageNode master = invocation.InvocationTarget
as IStorageNode;
if
(master.IsDead)
{
IChangeProxyTarget cpt = invocation
as IChangeProxyTarget;
//將被代理對象master更換爲slave
cpt.ChangeProxyTarget( this ._slave);
//測試中恢復master的狀態,以便看到隨後的調用仍然使用master這一效果
master.IsDead =
false ;
}
invocation.Proceed();
}
} |
1
2
3
4
5
6
7
8
9
10
|
ProxyGenerator generator =
new
ProxyGenerator(); IStorageNode node = generator.CreateInterfaceProxyWithTargetInterface<IStorageNode>(
new
StorageNode( "master" )
,
new
DualNodeInterceptor( new
StorageNode( "slave" ))
,
new
CallingLogInterceptor()); node.Save( "my message" );
//應該調用master對象 node.IsDead =
true ;
node.Save( "my message" );
//應該調用slave對象 node.Save( "my message" );
//應該調用master對象 Console.ReadKey(); |
只有在interface proxy with interface target的情況下IInterceptor的接口參數IInvocation對象才實現了IChangeProxyTarget接口
IChangeProxyTarget的ChangeInvocationTarget方法將本次調用的被代理對象替換掉,而ChangeProxyTarget方法則永久的將被代理對象替換,但不包括本次調用
Mixins
要準確的將mixin翻譯爲中文比較難找到一個合適的詞彙,他的大意是指在運行期使用“合併”的方式修改對象的行爲。比如對象obj的類型爲A,在運行時將類型B的所有屬性和方法“混入”到對象obj上,使得對象obj同時具備類型A和B的屬性和行爲,就好像obj同時繼承了A和B。在動態語言中比較容易實現這個效果,比如python本身支持多繼承,還可以通過__bases__動態修改基類,所以python中使用mixin技術是非常簡單的,包括javascript也很容易實現這個效果。關於mixin概念的詳細說明參考wikipedia
使用DP我們可以在C#中實現mixin,示例如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
public
interface
InterfaceA {
void
ActionA(); }
public
class
ClassA : InterfaceA
{
public
void
ActionA() {
Console.WriteLine( "I'm from ClassA" );
}
}
public
class
ClassB {
public
virtual
void
ActionB() {
Console.WriteLine( "I'm from ClassB" );
}
} |
1
2
3
4
5
6
7
8
|
ProxyGenerator generator =
new
ProxyGenerator(); var options =
new
ProxyGenerationOptions(); options.AddMixinInstance( new
ClassA()); ClassB objB = generator.CreateClassProxy<ClassB>(options,
new
CallingLogInterceptor()); objB.ActionB();
InterfaceA objA = objB
as InterfaceA;
objA.ActionA();
Console.ReadKey(); |
可以看到代理對象同時具備了ClassA和ClassB的行爲
導出、生成代理類型
Castle Dynamic Proxy允許我們將運行時生成的代理類型生成dll文件存到磁盤上,下次啓動時通過加載這個dll文件可以避免動態生成代理類型
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
var scope =
new
ModuleScope( true ,
ModuleScope.DEFAULT_ASSEMBLY_NAME,
ModuleScope.DEFAULT_FILE_NAME,
"DynamicProxyTest.Proxies" ,
"DynamicProxyTest.Proxies.dll" );
var builder =
new
DefaultProxyBuilder(scope); var generator =
new
ProxyGenerator(builder); var options =
new
ProxyGenerationOptions( new
InterceptorFilter())
{
Selector =
new
InterceptorSelector() };
SimpleSamepleEntity entity = generator.CreateClassProxy<SimpleSamepleEntity>(
options,
new
SimpleLogInterceptor(),
new
CallingLogInterceptor()); IStorageNode node = generator.CreateInterfaceProxyWithTargetInterface<IStorageNode>(
new
StorageNode( "master" )
,
new
DualNodeInterceptor( new
StorageNode( "slave" ))
,
new
CallingLogInterceptor()); options =
new
ProxyGenerationOptions(); options.AddMixinInstance( new
ClassA()); ClassB objB = generator.CreateClassProxy<ClassB>(options,
new
CallingLogInterceptor()); scope.SaveAssembly( false ); |
可以用reflector查看生成的dll,大致瞭解代理對象是如何工作的
啓動時,可以使用
scope.LoadAssemblyIntoCache(assembly);
將生成的代理類型加載到內存中,其中assembly需要我們手動加載
參考:
Castle Dynamic Proxy Tutorial
Castle's DynamicProxy for .NET