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