【C#】使用 Castle 實現 AOP,以及 Autofac 集成 Castle

Castle 是 2003 年誕生於 Apache Avalon 項目,目的是爲了創建一個IOC 框架。發展到現在已經有四個組件:

  • ORM組件:ActiveRecord
  • IOC組件:Windsor
  • 動態代理組件:DynamicProxy
  • Web MVC組件:MonoRail

本文主要介紹 動態代理組件 Castle.DynamicProxy

基本用法

Castle.DynamicProxy 是通過 Emit 反射動態生成代理類來實現的,效率相對靜態植入要慢一點,但比普通的反射又高一些。動態代理只對公共接口方法、類中的虛方法生效,因爲只有接口中的方法、類中的虛方法纔可以在子類中重寫。

基於接口的攔截器

public interface IProductRepository
{
    void Add(string name);
}

public class ProductRepository : IProductRepository
{
    public void Add(string name) => Console.WriteLine($"新增產品:{name}");
}

public class LoggerInterceptor : IInterceptor
{
    public void Intercept(IInvocation invocation)
    {
        var methodName = invocation.Method.Name;

        Console.WriteLine($"{methodName} 執行前");

        //調用業務方法
        invocation.Proceed();

        Console.WriteLine($"{methodName} 執行完畢");
    }
}

class Program
{
    static void Main(string[] args)
    {
        ProxyGenerator generator = new ProxyGenerator();

        IInterceptor loggerIntercept = new LoggerInterceptor();

        IProductRepository productRepo = new ProductRepository();

        IProductRepository proxy = generator.CreateInterfaceProxyWithTarget(productRepo, loggerIntercept);

        proxy.Add("大米");

        Console.Read();
    }
}

基於類的攔截器

public class ProductRepository
{
    public virtual void Add(string name) => Console.WriteLine($"新增產品:{name}");
}

static void Main(string[] args)
{
    ProxyGenerator generator = new ProxyGenerator();

    IInterceptor loggerIntercept = new LoggerInterceptor();

    ProductRepository proxy = generator.CreateClassProxyWithTarget(new ProductRepository(), loggerIntercept);
    // 使用 CreateClassProxy 泛型方法可以省去實例化代碼
    //ProductRepository proxy = generator.CreateClassProxy<ProductRepository>(loggerIntercept);

    proxy.Add("大米");
}

在上例中,如果 ProductRepository.Add 不是虛方法,也不會報錯,但是攔截器不會被調用。

異步函數攔截

Castle.DynamicProxy 對異步函數的攔截跟同步沒啥差別,只是,如果要在方法執行完成後插入內容,需要 await

public class ProductRepository
{
    public virtual Task Add(string name)
    {
        return Task.Run(() =>
                        {
                            Thread.Sleep(1000);
                            Console.WriteLine($"異步新增產品:{name}");
                        });
    }
}

public class LoggerInterceptor : IInterceptor
{
    public async void Intercept(IInvocation invocation)
    {
        var methodName = invocation.Method.Name;

        Console.WriteLine($"{methodName} 執行前");

        invocation.Proceed();

        // 不 await 的話將會先輸出“執行完畢”,再輸出“異步新增產品”
        var task = (Task)invocation.ReturnValue;
        await task;

        Console.WriteLine($"{methodName} 執行完畢");
    }
}

上面這個寫法是簡單粗暴的,如果碰到返回值是 Task<TResult>,或者不是異步函數,就會出錯。所以這裏是要對返回值進行一個判斷的。

可以使用 Castle.Core.AsyncInterceptor 包,它包裝了 Castle,使異步調用更簡單。

Castle.Core.AsyncInterceptor 的 GitHub 地址:https://github.com/JSkimming/Castle.Core.AsyncInterceptor

public class ProductRepository : IProductRepository
{
    public Task Add(string name)
    {
        return Task.Run(() =>
                        {
                            Thread.Sleep(1000);
                            Console.WriteLine($"異步新增產品:{name}");
                        });
    }

    public Task<string> Get()
    {
        return Task.Run(() =>
                        {
                            Thread.Sleep(1000);
                            Console.WriteLine($"獲取產品");

                            return "大米";
                        });
    }
}

public class LoggerInterceptor : IAsyncInterceptor
{
    public void InterceptAsynchronous(IInvocation invocation)
    {
        invocation.ReturnValue = InternalInterceptAsynchronous(invocation);
    }

    async Task InternalInterceptAsynchronous(IInvocation invocation)
    {
        var methodName = invocation.Method.Name;

        Console.WriteLine($"{methodName} 異步執行前");

        invocation.Proceed();
        await (Task)invocation.ReturnValue;

        Console.WriteLine($"{methodName} 異步執行完畢");
    }

    public void InterceptAsynchronous<TResult>(IInvocation invocation)
    {
        invocation.ReturnValue = InternalInterceptAsynchronous<TResult>(invocation);

        Console.WriteLine(((Task<TResult>)invocation.ReturnValue).Id);
    }

    private async Task<TResult> InternalInterceptAsynchronous<TResult>(IInvocation invocation)
    {
        var methodName = invocation.Method.Name;

        Console.WriteLine($"{methodName} 異步執行前");

        invocation.Proceed();
        var task = (Task<TResult>)invocation.ReturnValue;
        TResult result = await task;

        Console.WriteLine(task.Id);

        Console.WriteLine($"{methodName} 異步執行完畢");

        return result;
    }

    public void InterceptSynchronous(IInvocation invocation)
    {
        var methodName = invocation.Method.Name;

        Console.WriteLine($"{methodName} 同步執行前");

        invocation.Proceed();

        Console.WriteLine($"{methodName} 同步執行完畢");
    }
}

class Program
{
    static void Main(string[] args)
    {
        ProxyGenerator generator = new ProxyGenerator();

        IAsyncInterceptor loggerIntercept = new LoggerInterceptor();

        IProductRepository productRepo = new ProductRepository();

        IProductRepository proxy = generator.CreateInterfaceProxyWithTarget(productRepo, loggerIntercept);

        proxy.Get();
    }
}

這是 Castle.Core.AsyncInterceptor 提供的示例寫法,這裏有個問題,也是我的疑惑。invocation.ReturnValue = InternalInterceptAsynchronous(invocation); 將導致代理返回的 Task 是一個新的 Task,這一點我們可以輸出 Task.Id 來確認。個人感覺有點畫蛇添足。

public async void InterceptAsynchronous<TResult>(IInvocation invocation)
{
    var methodName = invocation.Method.Name;

    Console.WriteLine($"{methodName} 異步執行前");

    invocation.Proceed();
    var task = (Task<TResult>)invocation.ReturnValue;
    await task;

    Console.WriteLine($"{methodName} 異步執行完畢");
}

這樣就挺好的。

如果有小夥伴知道爲什麼要返回一個新的 Task,請留言告訴我,謝謝!

Autofac 集成

Autofac.Extras.DynamicProxy 是一個 Autofac 擴展,可與 Castle 一起提供 AOP 攔截。

基於接口的攔截器

static void Main(string[] args)
{
    ContainerBuilder builder = new ContainerBuilder();
    //註冊攔截器
    builder.RegisterType<LoggerInterceptor>().AsSelf();

    //註冊要攔截的服務
    builder.RegisterType<ProductRepository>().AsImplementedInterfaces()
        .EnableInterfaceInterceptors()                  //啓用接口攔截
        .InterceptedBy(typeof(LoggerInterceptor));      //指定攔截器

    IContainer container = builder.Build();

    IProductRepository productRepo = container.Resolve<IProductRepository>();

    productRepo.Add("大米");
}

基於類的攔截器

static void Main(string[] args)
{
    ContainerBuilder builder = new ContainerBuilder();
    //註冊攔截器
    builder.RegisterType<LoggerInterceptor>().AsSelf();

    //註冊要攔截的服務
    builder.RegisterType<ProductRepository>()
        .EnableClassInterceptors()                      //啓用類攔截
        .InterceptedBy(typeof(LoggerInterceptor));      //指定攔截器

    IContainer container = builder.Build();

    ProductRepository productRepo = container.Resolve<ProductRepository>();

    productRepo.Add("大米");
}

異步函數攔截

Castle.Core.AsyncInterceptor 中,IAsyncInterceptor 接口並不集成 IInterceptor 接口,而 Autofac.Extras.DynamicProxy 是綁定 Castle 的,所以按上面同步攔截的寫法是會報錯的。

IAsyncInterceptor 提供了 ToInterceptor() 擴展方法來進行類型轉換。

public class LoggerInterceptor : IInterceptor
{
    readonly LoggerAsyncInterceptor interceptor;

    public LoggerInterceptor(LoggerAsyncInterceptor interceptor)
    {
        this.interceptor = interceptor;
    }

    public void Intercept(IInvocation invocation)
    {
        this.interceptor.ToInterceptor().Intercept(invocation);
    }
}

public class LoggerAsyncInterceptor : IAsyncInterceptor
{
    public void InterceptAsynchronous(IInvocation invocation)
    {
        //...
    }

    public void InterceptAsynchronous<TResult>(IInvocation invocation)
    {
        //...
    }

    public void InterceptSynchronous(IInvocation invocation)
    {
        //...
    }
}

static void Main(string[] args)
{
    ContainerBuilder builder = new ContainerBuilder();
    //註冊攔截器
    builder.RegisterType<LoggerInterceptor>().AsSelf();
    builder.RegisterType<LoggerAsyncInterceptor>().AsSelf();

    //註冊要攔截的服務
    builder.RegisterType<ProductRepository>().AsImplementedInterfaces()
        .EnableInterfaceInterceptors()                  //啓用接口攔截
        .InterceptedBy(typeof(LoggerInterceptor));      //指定攔截器

    var container = builder.Build();

    IProductRepository productRepo = container.Resolve<IProductRepository>();

    productRepo.Get();
}

參考

https://www.cnblogs.com/youring2/p/10962573.html

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章