[譯]如何在ASP.NET Core中實現面向切面編程(AOP)

原文地址:ASPECT ORIENTED PROGRAMMING USING PROXIES IN ASP.NET CORE
原文作者:ZANID HAYTAM
譯文地址:如何在ASP.NET Core中實現面向切面編程(AOP)
譯文作者:Lamond Lu

https://blog.zhaytam.com/img/default.jpg

前言

在使用了Spring Boot數月之後,  我發覺ASP.NET Core中缺失了對面向切面編程(AOP)的默認支持。

維基百科中針對AOP的定義:

面向切面編程(AOP)是一種編程範例,其旨在通過允許跨領域關注點的分離來提高模塊化。它通過“切入點”規範指定要修改的代碼,不修改源代碼本身的情況下,向現有代碼提供額外行爲,例如使用日誌的方式記錄爲所有以"set"開頭的方法調用記錄。使用該方式,你可以向核心業務邏輯中追加一些不太重要的功能(例如日誌),而不會使代碼混亂。AOP爲面向切換的軟件開發奠定了基礎。

以下是AOP的一些常用場景

  • 日誌審計
  • 事務管理
  • 安全

代理模式(Proxy Pattern)也常用於Mocking(例如Moq, NSubstitute等)和延時加載(Lazy Loading)(例如EF Core, NHierante等)

C#中實現AOP

C#中其實已經支持AOP了,你可以快速Google搜索一下,AOP的實現方式有2種RealProxy真實代理和MarshalByRefObject.技術上講,他們都可以在本地和遠程使用,它看起來非常的美好,直到你明白的你的所有目標對象都必須繼承MarshalByRefObject。僅此一點,就讓大部分人不會考慮這種實現方式。

更好的實現方式

幸運的是,我們在C#中可以使用一種更好的方式創建代理對象,即使用Castle.DynamicProxy庫。

Castle.DynamicProxy是一個用於在運行時生成輕量級.NET代理的庫。生成代理對象允許你在不修改原始代碼的情況下攔截對對象成員的調用,只有virtual對象成員才能被攔截。 - Castle Project

使用Castle提供的動態代理,你可以爲抽象類、接口(同時提供實現)以及帶有virtual方法/屬性的普通類創建代理對象。

以下是一個例子,這裏我們假設創建了一個處理博客文章的服務應用。

public class BlogPost
{
    public int Id { get; set; }
    public string Title { get; set; }
    public string Description { get; set; }
    public bool Disabled { get; set; }
    public DateTime Created { get; set; }
}

public interface IBlogService
{
    void DisablePost(BlogPost post);
    BlogPost GetPost(int id);
}

public class BlogService : IBlogService
{
    public BlogPost GetPost(int id)
    {
        return new BlogPost
        {
            Id = id,
            Title = "Test",
            Description = "Test",
            Disabled = false,
            Created = DateTime.UtcNow
        };
    }

    public void DisablePost(BlogPost post)
    {
        post.Disabled = true;
    }
}

通常,你會將BlogService類註冊爲IBlogService接口的實現,一切都運轉的非常正常。但是現在,你希望代理這個接口,當接口中任何方法被調用的時候,做點什麼事情。

這裏,我們首先創建一個攔截器對象以便攔截方法調用,就像RealProxy一樣

public class LoggingInterceptor : IInterceptor
{
    public void Intercept(IInvocation invocation)
    {
        Console.WriteLine($"正在調用方法 {invocation.TargetType}.{invocation.Method.Name}.");
        invocation.Proceed(); // 執行當前被攔截的方法
    }
}

然後,我們將使用一個代理生成器生成代理對象。

var generator = new ProxyGenerator();
var actual = new BlogService();
var proxiedService = (IBlogService)proxyGenerator.CreateInterfaceProxyWithTarget(typeof(IBlogService), actual, new LoggingInterceptor());
// 使用proxiedService對象和你平常使用IBlogService對象是一樣的

現在我們就創建出了一個實現了IBlogService接口的代理對象,其中包含了內部實現BlogService。當任何一個接口方法被調用的時候,LoggingInterceptor.Intercept方法就會被調用,當攔截器調用invocation.Proceed()方法時,它在BlogService類中的具體實現方法就會被調用。

如何在ASP.NET Core中使用Castle實現AOP

在ASP.NET Core中使用Castle實現AOP的實現思路是, 始終使用ASP.NET Core的IOC容器來創建代理服務。雖然Castle項目中包含它自己的IOC容器Castle Windor , 使得注入代理更加的容易,但是我們暫時不使用它。

這裏,我們首先爲我們的LoggingInterceptor添加一個簡單的依賴以展示我們如何使用ASP.NET Core自帶的DI來處理依賴問題。因爲現實中,你的大部分攔截器都是需要一個或多個依賴項的。

public class LoggingInterceptor : IInterceptor
{
    private readonly ILogger<LoggingInterceptor> _logger;

    public LoggingInterceptor(ILogger<LoggingInterceptor> logger)
    {
        _logger = logger;
    }

    public void Intercept(IInvocation invocation)
    {
        _logger.LogDebug($"Calling method {invocation.TargetType}.{invocation.Method.Name}.");
        invocation.Proceed();
    }
}

第二步,我們在依賴注入容器中註冊一個單例的ProxyGenerator對象,以及我們即將使用的所有的攔截器對象

services.AddSingleton(new ProxyGenerator());
services.AddScoped<IInterceptor, LoggingInterceptor>();

最後,我們創建一個擴展方法AddProxiedScoped, 並使用它註冊其他所有服務。

public static class ServicesExtensions
{
    public static void AddProxiedScoped<TInterface, TImplementation>(this IServiceCollection services)
        where TInterface : class
        where TImplementation : class, TInterface
    {
        services.AddScoped<TImplementation>();
        services.AddScoped(typeof(TInterface), serviceProvider =>
        {
            var proxyGenerator = serviceProvider.GetRequiredService<ProxyGenerator>();
            var actual = serviceProvider.GetRequiredService<TImplementation>();
            var interceptors = serviceProvider.GetServices<IInterceptor>().ToArray();
            return proxyGenerator.CreateInterfaceProxyWithTarget(typeof(TInterface), actual, interceptors);
        });
    }
}

// In ConfigureServices
services.AddProxiedScoped<IBlogService, BlogService>();

這裏,讓我們看看它是如何工作的

  1. 我們註冊具體實現(例如BlogService)。這是因爲具體實現可能也需要使用依賴注入容器解決依賴問題。
  2. 每當從依賴注入容器中嘗試獲取接口對象的時候:
    1. 我們取得了一個ProxyGenerator對象的實例
    2. 我們獲得了一個接口的實現實例
    3. 我們獲取到了所有註冊的攔截器
    4. 使用代理生成器創建接口的代理對象,這個對象中包含了一個具體實現和其使用的攔截器。

現在,我們無論何時需要一個IBlogService接口對象,都可以通過依賴注入容器得到一個代理對象,這個代理對象會先經過所有的攔截器,然後調用BlogService中定義的實際方法。

但是這裏,相較與Spring,在ASP.NET Core中實現AOP還不夠簡單直接,但是我們可以輕鬆將其轉換爲簡單的“框架”,我們可以使用Castle.DynamicProxy的一些特定方法,來執行一些更高級的操作。

希望本篇文章對你有所幫助,Happy Coding!

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