Castle 系列:Castle DynamicProxy動態生成透明代理類型詳解

Castle DynamicProxy動態生成透明代理類型,

實體不需要繼承MarshalByRef、ContextBoundObject便可以實現代理類
基於透明代理的功能,可以實現對方法調用的攔截處理,例如NHibernate用它實現延遲加載
DP的使用非常簡單,內部沒有使用反射,而是採用Emit、委託等方式生成代理類型,調用真實類的方法,性能方面也沒有太多損失

基本示例
引用的命名空間:

using Castle.Core.Interceptor;
using Castle.DynamicProxy;
1
2
using Castle.Core.Interceptor;
using Castle.DynamicProxy;
被代理的實體類:
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
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);
    }
}
定義一個攔截器,調用代理類的方法時使用攔截器進行攔截處理:
?
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
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);
    }
}
測試代碼:
?
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();
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
創建代理對象時可以指定多個攔截器,例如再簡單的實現一個攔截器:
?
public class SimpleLogInterceptor : IInterceptor
{
    public void Intercept(IInvocation invocation)
    {
        Console.WriteLine(">>" + invocation.Method.Name);
        invocation.Proceed();
    }
}
1
2
3
4
5
6
7
8
public class SimpleLogInterceptor : IInterceptor
{
    public void Intercept(IInvocation invocation)
    {
        Console.WriteLine(">>" + invocation.Method.Name);
        invocation.Proceed();
    }
}
測試代碼如下修改即可:
?
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();
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種方式,例如:
?
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
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()
    {
    }
}
測試代碼改爲:
?
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();
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基本類似,但他提供了一個更改被代理對象(真實對象)的機會,示例如下:
?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
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();
    }
}
測試代碼:
?
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();
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();
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,示例如下:
?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
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");
    }
}
測試代碼:
?
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();
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文件可以避免動態生成代理類型
?
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);
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);
注意:上面用到的攔截器和其他測試類都必須加上[Serializable]屬性
可以用reflector查看生成的dll,大致瞭解代理對象是如何工作的
啓動時,可以使用
scope.LoadAssemblyIntoCache(assembly);
將生成的代理類型加載到內存中,其中assembly需要我們手動加載

參考:
Castle Dynamic Proxy Tutorial
Castle's DynamicProxy for .NET
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章