.Net依賴注入神器Scrutor(下)

董瑞鹏發表於2024-03-20

前言

上一篇文章我們講到了Scrutor第一個核心功能Scanning,本文講解的是Scrutor第二個核心的功能Decoration 裝飾器模式在依賴注入中的使用。

  • 裝飾器模式允許您向現有服務類中新增新功能,而無需改變其結構
Install-Package Scrutor

本文的完整原始碼在文末

Decoration 依賴注入代理模式

首先首先一個 獲取 User 的服務

定義 User 類

public class User
{
    public int Id { get; set; }
    public string Name { get; set; }

    public int Age { get; set; }

    public string? Email { get; set; }
}

定義介面和實現

public interface IUserService
{
    List<User> GetAllUsers();
}

public class UserService : IUserService
{
    public List<User> GetAllUsers()
    {
        Console.WriteLine("GetAllUsers方法被呼叫~");
        List<User> users = [
        new User(){
            Id= 1,
            Name="張三",
            Age=18,
            Email="zhangsan@163.com"
        },
        new User(){
            Id= 2,
            Name="李四",
            Age=19,
            Email="lisi@163.com"
        },
        ];
        return users!;
    }
}

現在有了我們的獲取全部使用者的服務了,需求是在不破壞當前類的新增裝飾器模式,為 GetAllUsers 介面新增快取。

建立裝飾器類

public class UserDecorationService(IUserService userService, IMemoryCache cache) : IUserService
{
    public List<User> GetAllUsers()
    {
        Console.WriteLine("GetAllUsers代理方法被呼叫~");
        return cache.GetOrCreate("allUser", cacheEntry =>
           {
               cacheEntry.SetAbsoluteExpiration(
                    TimeSpan.FromMinutes(5));
               var allUsers = userService.GetAllUsers();
               return allUsers ?? [];
           })!;
    }
}

DI 容器新增服務

 builder.Services.AddTransient<IUserService, UserService>();
 builder.Services.AddMemoryCache();
 builder.Services.Decorate<IUserService, UserDecorationService>();

建立介面測試一下

app.MapGet("/GetAllUsers", ([FromServices] IUserService userService) => userService.GetAllUsers()).WithSummary("獲取全部使用者介面");

呼叫第一次

GetAllUsers代理方法被呼叫~
GetAllUsers方法被呼叫~

第二次呼叫

GetAllUsers代理方法被呼叫~

可以看出第一次沒快取裝飾器類和我們 UserService 都呼叫了,第二次因為只有了快取所以只呼叫了裝飾器類,可以看出我們的裝飾器模式生效了。

依賴注入裝飾器底層核心實現

    /// <summary>
    /// Decorates all registered services using the specified <paramref name="strategy"/>.
    /// </summary>
    /// <param name="services">The services to add to.</param>
    /// <param name="strategy">The strategy for decorating services.</param>
    public static bool TryDecorate(this IServiceCollection services, DecorationStrategy strategy)
    {
        Preconditions.NotNull(services, nameof(services));
        Preconditions.NotNull(strategy, nameof(strategy));

        var decorated = false;

        for (var i = services.Count - 1; i >= 0; i--)
        {
            var serviceDescriptor = services[i];

            if (IsDecorated(serviceDescriptor) || !strategy.CanDecorate(serviceDescriptor))
            {
                continue;
            }

            var serviceKey = GetDecoratorKey(serviceDescriptor);
            if (serviceKey is null)
            {
                return false;
            }

            // Insert decorated
            services.Add(serviceDescriptor.WithServiceKey(serviceKey));

            // Replace decorator
            services[i] = serviceDescriptor.WithImplementationFactory(strategy.CreateDecorator(serviceDescriptor.ServiceType, serviceKey));

            decorated = true;
        }


        return decorated;
    }

這個程式碼是在 dotNet8 的環境下編譯的,可以看出做了幾件事:
第一 IServiceCollection 集合倒序遍歷,找到符合條件的ServiceType
核心程式碼一

// Insert decorated
services.Add(serviceDescriptor.WithServiceKey(serviceKey));

將原先的ServiceDescription作為基礎,新增了ServiceKey後再進行新增操作,新的服務描述符會被新增到服務集合的末尾,

核心程式碼二

 // Replace decorator
 services[i] = serviceDescriptor.WithImplementationFactory(strategy.CreateDecorator(serviceDescriptor.ServiceType, serviceKey));

這一步是將原有的服務描述符替換為一個新的服務描述符,新的服務描述符使用裝飾器工廠方法建立,實現了服務的裝飾功能。

用的時候

app.MapGet("/GetAllUsers", ([FromServices] IUserService userService) => userService.GetAllUsers()).WithSummary("獲取全部使用者介面");

這樣就可以獲取到裝飾器類提供服務,之前看到services.Add(serviceDescriptor.WithServiceKey(serviceKey));在程式碼的最後新增了一個服務,那 IOC 獲取的時候肯定是從後面優先獲取,這地方用了 dotNet8 的鍵控依賴注入(KeyedService),以 ServiceType 獲取服務只會獲取到我們提供的裝飾器例項,這一手簡直是神來之筆 👍。

最後

Scrutor的裝飾器模式可以用於動態地給依賴注入的例項新增額外職責,實現動態增加和撤銷功能,而無需改變原有物件結構。可以在不影響其他物件的情況下,以透明且動態的方式給物件新增新功能,實現系統的靈活擴充套件和維護。

本文完整原始碼

相關文章