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

前言

上一篇文章我们讲到了Scrutor第一个核心功能Scanning,本文讲解的是Scrutor第二个核心的功能Decoration 装饰器模式在依赖注入中的使用。

  • 装饰器模式允许您向现有服务类中添加新功能,而无需改变其结构
cmd 复制代码
Install-Package Scrutor

本文的完整源代码在文末

Decoration 依赖注入代理模式

首先首先一个 获取 User 的服务

定义 User 类

c# 复制代码
public class User
{
    public int Id { get; set; }
    public string Name { get; set; }

    public int Age { get; set; }

    public string? Email { get; set; }
}

定义接口和实现

c# 复制代码
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 接口添加缓存。

创建装饰器类

c# 复制代码
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 容器添加服务

c# 复制代码
 builder.Services.AddTransient<IUserService, UserService>();
 builder.Services.AddMemoryCache();
 builder.Services.Decorate<IUserService, UserDecorationService>();

创建接口测试一下

c# 复制代码
app.MapGet("/GetAllUsers", ([FromServices] IUserService userService) => userService.GetAllUsers()).WithSummary("获取全部用户接口");

调用第一次

c# 复制代码
GetAllUsers代理方法被调用~
GetAllUsers方法被调用~

第二次调用

c# 复制代码
GetAllUsers代理方法被调用~

可以看出第一次没缓存装饰器类和我们 UserService 都调用了,第二次因为只有了缓存所以只调用了装饰器类,可以看出我们的装饰器模式生效了。

依赖注入装饰器底层核心实现

c# 复制代码
    /// <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

核心代码一

c# 复制代码
// Insert decorated
services.Add(serviceDescriptor.WithServiceKey(serviceKey));

将原先的ServiceDescription作为基础,添加了ServiceKey后再进行添加操作,新的服务描述符会被添加到服务集合的末尾,

核心代码二

c# 复制代码
 // Replace decorator
 services[i] = serviceDescriptor.WithImplementationFactory(strategy.CreateDecorator(serviceDescriptor.ServiceType, serviceKey));

这一步是将原有的服务描述符替换为一个新的服务描述符,新的服务描述符使用装饰器工厂方法创建,实现了服务的装饰功能。

用的时候

c# 复制代码
app.MapGet("/GetAllUsers", ([FromServices] IUserService userService) => userService.GetAllUsers()).WithSummary("获取全部用户接口");

这样就可以获取到装饰器类提供服务,之前看到services.Add(serviceDescriptor.WithServiceKey(serviceKey));在代码的最后添加了一个服务,那 IOC 获取的时候肯定是从后面优先获取,这地方用了 dotNet8 的键控依赖注入(KeyedService),以 ServiceType 获取服务只会获取到我们提供的装饰器实例,这一手简直是神来之笔 👍。

最后

Scrutor的装饰器模式可以用于动态地给依赖注入的实例添加额外职责,实现动态增加和撤销功能,而无需改变原有对象结构。可以在不影响其他对象的情况下,以透明且动态的方式给对象添加新功能,实现系统的灵活扩展和维护。

本文完整源代码