Autofac 之 基於 Castle DynamicProxy2 的 Interceptor 功能


Autofac 結合 Castle DynamicProxy2 功能
 
     Autofac 不僅作為輕量級高效的 IoC 容器,而且還能很好的與 Castle.DynamicProxy2 結合起來,實現 AOP 功能。
     首先,我們需要定義攔截器,簡單的定義可實現 Castle.DynamicProxy.IInterceptor 接口即可。
 
添加攔截器
 
     定義好了攔截器后,如何應用到相關對象呢?有兩種方式:
     1)使用 Autofac.Extras.DynamicProxy2.InterceptAttribute 特性將攔截器添加到相關的類或接口上;
     2)使用 ContainerBuilder 的 InterceptedBy() 方法在注冊對象的同時添加攔截器。
 
     如何啟用攔截器呢?我們只需要在 IoC 注冊啟用即可。啟用攔截器有三種方式,其中針對 WCF 比較特別,下面來具體分析這幾種方式的應用場景:
 
基於接口的攔截器
 
     在注冊對象的同時啟用 EnableInterfaceInterceptors() 方法。
     使用接口的攔截器,在使用特性 [Attribute] 注冊時,注冊攔截器可注冊到接口(Interface)上或其實現類(Implement)上。使用注冊到接口上方式,所有的實現類都能應用到攔截器。
     對於以接口方式的注入,Autofac Interceptor 要求類的方法為 public 或 virtual 即可。
     
示例代碼:
var builder = new ContainerBuilder();
builder.RegisterType<SomeType>()
       .As<ISomeInterface>()
       .EnableInterfaceInterceptors();
builder.Register(c => new CallLogger(Console.Out));
var container = builder.Build();
var willBeIntercepted = container.Resolve<ISomeInterface>();

 

 
基於類的攔截器
 
     在注冊對象的同時啟用 EnableClassInterceptors() 方法。
     對於以類方式的注入,Autofac Interceptor 要求類的方法為必須為 virtual 方法。
     值得注意的是:對於 子類,重寫(override)父類的虛方法時,能應用到攔截器。父類可在 IoC 中注冊也可不需要注冊,但子類必須在 IoC 中注冊(對於類的攔截器,類都必須要注冊,當然,攔截器也必須要注冊)。
 
示例代碼:
var builder = new ContainerBuilder();
builder.RegisterType<First>()
       .EnableClassInterceptors();
builder.Register(c => new CallLogger(Console.Out));

 

 
基於 WCF 的攔截器
 
     WCF 是一種特殊情況。雖然 WCF Proxy 的服務對象也是一種接口,但是使用 EnableInterfaceInterceptors 不會起作用,因為 .NET 實際上是使用了  類似於接口行為的 System.Runtime.Remoting.TransparentProxy 。因此需要這里需要使用  InterceptTransparentProxy() 方法。
 
示例代碼:
var cb = new ContainerBuilder();
cb.RegisterType<TestServiceInterceptor>();
cb.Register(c => CreateChannelFactory()).SingleInstance();
cb.Register(c => c.Resolve<ChannelFactory<ITestService>>().CreateChannel())
  .InterceptTransparentProxy(typeof(IClientChannel))
  .InterceptedBy(typeof(TestServiceInterceptor))
  .UseWcfSafeRelease();

 

實戰一下

先看看基於接口的攔截器:

我們先定義一個借口,名為 ICalculater

using Autofac.Extras.DynamicProxy2;

namespace AOP.Interceptors
{
    //[Intercept(typeof(CalculaterInterceptor))]
    public interface ICalculater
    {
        int Add(int x, int y);

        int Sub(int x, int y);
    }
}
View Code

然后定義該接口的實現類 Calculater

using Autofac.Extras.DynamicProxy2;

namespace AOP.Interceptors
{
    //[Intercept(typeof(CalculaterInterceptor))]
    public class Calculater : ICalculater
    {
        public int Add(int x, int y)
        {
            return x + y;
        }

        public int Sub(int x, int y)
        {
            return x - y;
        }
    }
}
View Code

 

接下來,我們來定義攔截器。這里我們定義了兩個連接器,通過這兩個攔截器,我們將能很清晰的看到攔截器是如何工作的。

定義第一個攔截器 CalculaterInterceptor

using System;
using Castle.DynamicProxy;

namespace AOP.Interceptors
{
    public class CalculaterInterceptor : IInterceptor
    {
        public void Intercept(IInvocation invocation)
        {
            // 在下個攔截器或目前方法處理之前處理
            var args = invocation.Arguments;

            Console.WriteLine($"Before: x={args[0]}, y={args[1]}");
            Console.WriteLine($"Before: Method={invocation.Method.Name}");

            invocation.SetArgumentValue(0, 5);

            // handle
            invocation.Proceed();  // 調用下一個攔截器,直到最終的目標方法。

            // Post
            Console.WriteLine($"After: TargetType={invocation.TargetType}");
            Console.WriteLine($"After: ReturnValue={invocation.ReturnValue}");

            invocation.ReturnValue = (int)invocation.ReturnValue - 2;
        }
    }
}
View Code

定義第二個攔截器 CalculaterInterceptor2

using System;
using Castle.DynamicProxy;

namespace AOP.Interceptors
{
    public class CalculaterInterceptor2 : IInterceptor
    {
        public void Intercept(IInvocation invocation)
        {
            var args = invocation.Arguments;

            Console.WriteLine($"Before2: x={args[0]}, y={args[1]}");
            Console.WriteLine($"Before2: Method={invocation.Method.Name}");

            invocation.Proceed();

            Console.WriteLine($"After2: TargetType={invocation.TargetType}");
            Console.WriteLine($"After2: ReturnValue={invocation.ReturnValue}");

            invocation.ReturnValue = (int)invocation.ReturnValue - 1;  // 將結果值減去 2
        }
    }
}
View Code

在 控制台 Main 函數輸入我們的結果:

static void Main(string[] args)
        {
            var builder = new ContainerBuilder();
            builder.RegisterType<Calculater>()
                .As<ICalculater>()
                .EnableInterfaceInterceptors()
                .InterceptedBy(typeof(CalculaterInterceptor), typeof(CalculaterInterceptor2));  // 這里定義了兩個攔截器,注意它們的順序

            builder.RegisterType<CalculaterInterceptor>();  // 注冊攔截器
            builder.RegisterType<CalculaterInterceptor2>();  // 注冊攔截器2

            var ioc = builder.Build();

            var calculater = ioc.Resolve<ICalculater>();
            var addResult = calculater.Add(2, 3);

            Console.WriteLine($"add result: {addResult}");

            Console.WriteLine("-------------------");
            Console.ReadLine();
        }
View Code

 

我們看看輸出結果:

 

這里我們可以看出,執行順序為 CalculaterInterceptor  --> CalculaterInterceptor2 --> Target Method --> CalculaterInterceptor2 --> CalculaterInterceptor 。攔截器中 invocation.Proceed() 方法用於調用下一個攔截器(若存在),直到最終的目標方法(Target Method)。不過 invocation.Proceed() 並不是一定要調用的,例如,對於有返回值的目標方法,我們在攔截器中設置 invocation.ReturnValue 值就可正確執行,這樣便不會執行目標方法。在有些場景中,如身份驗證、緩存讀取等還是特別有用的。

當然,在 Main() 方法中 Ioc 注冊 Caliculater 類型時我們注冊了兩個攔截器,".InterceptedBy(typeof(CalculaterInterceptor), typeof(CalculaterInterceptor2))"。我們也可以直接在 Calculater 類型 或 ICalculater 接口上以特性的形式注冊,如上面代碼中注釋掉的那部分。若是既有在類型上注冊,也有在 Autofac 的 Builder 中注冊,那么這個攔截器會重復執行。

 

基於類的攔截器:

我們定義兩個類 Flight 和其 子類 FlightOfSH:

 public class Flight
    {
        public virtual void Fly(DateTime time)
        {
            Console.WriteLine($"Flight: {time}");
        }
    }


public class FlightOfSH : Flight
    {
        public override void Fly(DateTime time)
        {
            Console.WriteLine($"FlightOfSH: Fly={time}");
        }

        public void Arrive(DateTime time)
        {
            Console.WriteLine($"FlightOfSH: Arrive={time}");
        }
    }
View Code

這兩個類的攔截器:

internal class FlightInterceptor : IInterceptor
    {
        public void Intercept(IInvocation invocation)
        {
            Console.WriteLine("Before Fly");

            invocation.Proceed();

            Console.WriteLine("After Fly");
        }
    }
View Code

 

在 Main 函數中定義代碼:
var builder = new ContainerBuilder();

            builder.RegisterType<Flight>()
               .EnableClassInterceptors().InterceptedBy(typeof(FlightInterceptor));
            builder.RegisterType<FlightOfSH>()
                .EnableClassInterceptors().InterceptedBy(typeof(FlightInterceptor));

            builder.RegisterType<FlightInterceptor>();

            var ioc = builder.Build();

            var flight = ioc.Resolve<Flight>();
            flight.Fly(DateTime.Now);

            var flightOfSH = ioc.Resolve<FlightOfSH>();
            flightOfSH.Fly(DateTime.Now);
            flightOfSH.Arrive(DateTime.Now);
View Code

 

我們看看輸出結果:
 
 
從輸出結果中可以發現一個有趣的現在, 子類 FlightOfSH 重寫了 Flight 的 Fly 方法,卻也調用才攔截器。Arrive() 方法因為是非虛方法,所有攔截器不會在該方法中調用。
當然,我們這里都是直接在 Autofac 的 Ioc 注冊類型時設定的,也可以同上面一樣使用 InterceptAttribute 特性來注入。
對於基於 WCF 的攔截器,大家可以自己試驗下。
 

總結

這里簡單的介紹了 Autofac 與 Castle 動態代理功能結合來實現 AOP 功能,當然,Castle 本身也是個很強大的開源框架,也有很強大的 IoC 功能,不過我還是比較喜歡 Autofac 的 IoC 功能。
本文就寫到此,謝謝大家。
 
參考文章:


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM