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


    前言

    上一篇文章我们讲到了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 GetAllUsers();
    }
    
    public class UserService : IUserService
    {
        public List GetAllUsers()
        {
            Console.WriteLine("GetAllUsers方法被调用~");
            List 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 GetAllUsers()
        {
            Console.WriteLine("GetAllUsers代理方法被调用~");
            return cache.GetOrCreate("allUser", cacheEntry =>
               {
                   cacheEntry.SetAbsoluteExpiration(
                        TimeSpan.FromMinutes(5));
                   var allUsers = userService.GetAllUsers();
                   return allUsers ?? [];
               })!;
        }
    }
    

    DI 容器添加服务

     builder.Services.AddTransient();
     builder.Services.AddMemoryCache();
     builder.Services.Decorate();
    

    创建接口测试一下

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

    调用第一次

    GetAllUsers代理方法被调用~
    GetAllUsers方法被调用~
    

    第二次调用

    GetAllUsers代理方法被调用~
    

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

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

        /// 
        /// Decorates all registered services using the specified .
        /// 
        /// The services to add to.
        /// The strategy for decorating services.
        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的装饰器模式可以用于动态地给依赖注入的实例添加额外职责,实现动态增加和撤销功能,而无需改变原有对象结构。可以在不影响其他对象的情况下,以透明且动态的方式给对象添加新功能,实现系统的灵活扩展和维护。

    本文完整源代码

  • 相关阅读:
    微服务组件Sentinel (Hystrix)详细分析
    GBase XDM API 错误处理调用(上)
    Vue:内置组件:KeepAlive(缓存组件实例)
    [Redis]Zset类型
    回归拟合 | 灰狼算法优化核极限学习机(GWO-KELM)MATLAB实现
    C++ 学习(15)文件操作、文件文件读与写操作、二进制文件读与写操作
    万字长文 - Python 日志记录器logging 百科全书 - 高级配置之 日志分层
    大数据HCIE成神之路之数学(1)——基础数学
    安装vue-router及报错问题
    rust 字符串字面量 - 字符串前缀
  • 原文地址:https://www.cnblogs.com/ruipeng/p/18084771