• 手把手教你写DI_3_小白徒手支持 `Singleton` 和 `Scoped` 生命周期


    手把手教你写DI_3_小白徒手支持 SingletonScoped 生命周期

    在上一节:手把手教你写DI_2_小白徒手撸构造函数注入

    浑身绷带的小白同学:我们继续开展我们的工作,大家都知道 Singleton是什么,就是全局只有一个呗,我们就先从它开始,这个多简单,我们找个字典放这些对象就ok啦

    public class ServiceProvider : IServiceProvider
    {
        ...
    
        private readonly ConcurrentDictionary<Type, object> singletonCache = new ConcurrentDictionary<Type, object>();
    
        public object GetService(Type serviceType)
        {
            case Lifetime.Singleton:
                    singletonCache.GetOrAdd(serviceType, x => 
                    {
                        if(defintion is DelegateServiceDefintion defi)
                        {
                            return defi.ImplementationFactory(this);
                        }
                        else
                        {
                            ConstructorInfo constructor = cache.GetOrAdd(serviceType, i => 
                            {
                                var d = defintion as TypeServiceDefintion;
                                var implementationType = serviceType.IsConstructedGenericType 
                                    ? d.ImplementationType.MakeGenericType(serviceType.GenericTypeArguments)
                                    : d.ImplementationType;
                                return implementationType.GetConstructors().FirstOrDefault(i => i.IsPublic);
                            });
                            ar ps = constructor.GetParameters();
                            var args = new object[ps.Length];
                            for (int j = 0; j < ps.Length; j++)
                            {
                                var p = ps[j];
                                args[j] = i.GetService(p.ParameterType);  // 小白同学:  获取参数值
                            }
                            return constructor.Invoke(args);  // 小白同学:  创建;
                        }
                    });
    
            ....
            case Lifetime.Transient:
                   .....
            ....
        }
    }
    

    大神:我的刀呢?

    小白同学:我错啦!!!

    public class ServiceProvider : IServiceProvider
    {
        ...
    
        private readonly ConcurrentDictionary<Type, object> singletonCache = new ConcurrentDictionary<Type, object>();
    
        public object GetService(Type serviceType)
        {
            case Lifetime.Singleton:
                 return singletonCache.GetOrAdd(serviceType, x => CreateObj(x));
    
            case Lifetime.Scoped:
                 return CreateObj(x);
    
            case Lifetime.Transient:
                 return CreateObj(x);
            ....
        }
    
        public object CreateObj(Type serviceType)
        {
            if(defintion is DelegateServiceDefintion defi)
            {
                return defi.ImplementationFactory(this);
            }
            else
            {
                ConstructorInfo constructor = cache.GetOrAdd(serviceType, i => 
                {
                    var d = defintion as TypeServiceDefintion;
                    var implementationType = serviceType.IsConstructedGenericType 
                        ? d.ImplementationType.MakeGenericType(serviceType.GenericTypeArguments)
                        : d.ImplementationType;
                    return implementationType.GetConstructors().FirstOrDefault(i => i.IsPublic);
                });
                ar ps = constructor.GetParameters();
                var args = new object[ps.Length];
                for (int j = 0; j < ps.Length; j++)
                {
                    var p = ps[j];
                    args[j] = i.GetService(p.ParameterType);  // 小白同学:  获取参数值
                }
                return constructor.Invoke(args);  // 小白同学:  创建;
            }
        }
    
                
    }
    

    小白同学:好了,我们来说下 Scoped 作用域,百度百科的解释是这样的: 作用域(scope),程序设计概念,通常来说,一段程序代码中所用到的名字并不总是有效/可用的,而限定这个名字的可用性的代码范围就是这个名字的作用域。
    作用域的使用提高了程序逻辑的局部性,增强程序的可靠性,减少名字冲突。
    对于对象而言(其他也是一样的),在main函数中,对象的作用域为他所在的最近的一对花括号内。在后花括号处析构函数被调用;全局的对象的作用域为声明之后的整个文件,析构函数在最后被调用。另外,临时产生的对象在使用完后立即会被析构。

    小白同学:虽然比较奇怪为啥百度百科强调的是名字,名字不过是我们方便自己对应以及找到变量/内存地址等的手段而已。不过不管啦,反正DI里面的Scoped概念和这段解释有点点相似,是为DI提供将对象生命周期控制在自定义的范围内部的一个手段,比如我们保证http 一次请求的生命周期内,一些比如context之类的处理,我们就可以用这样的作用域概念处理,

    小白同学:作用域由于考虑到不是我们自己控制,这是有使用者自定的,所以我们需要提供一些抽象接口让用户可以使用。这里呢,我们就偷懒啦,抄袭一下别人的定义

    public interface IServiceScopeFactory
    {
        IServiceProvider CreateScopeProvider();
    }
    

    小白同学:我们来实现它

    public class ServiceScopeFactory : IServiceScopeFactory
    {
        public IServiceProvider CreateScopeProvider()
        {
            return new ServiceProvider();
        }
    }
    

    小白同学:大家看,多简单,完美

    大神:你问过我的青龙偃月刀了吗?

    小白同学(尴尬): 哈哈,怎么可能写完了,我是开个玩笑,肯定要把服务定义给过去

    public class ServiceScopeFactory : IServiceScopeFactory
    {
        private readonly IServiceDefintions services;
    
        public ServiceScopeFactory(IServiceDefintions services)
        {
            this.services = services;
        }
    
        public IServiceProvider CreateScopeProvider()
        {
            return new ServiceProvider(services);
        }
    }
    

    青龙偃月刀:你希望你的生命周期也和这个ServiceScopeFactory一样无处安放吗?

    小白同学:为啥?我这不是实现了吗?

    青龙偃月刀:ServiceScopeFactory 用户从哪里拿?

    小白同学:我放进ServiceDefintions呀,

    var a = new ServiceDefintions();
    a.Add(new DelegateServiceDefintion(typeof(IServiceScopeFactory),typeof(ServiceScopeFactory),Lifetime.Transient, i => new ServiceScopeFactory(a)));
    

    青龙偃月刀:hehe, ServiceProviderIServiceScopeFactory 创建的都是新的吧?

    小白同学:对,就是这样,才能保证是新的作用域呀

    青龙偃月刀:hehe, 那新的 ServiceProvider 创建的对象也是新的吧?

    小白同学:对,就是这样,新的作用域创建的对象肯定和旧的作用域创建的对象肯定不一样

    青龙偃月刀:hehe, 那Singleton不是全局唯一吗?

    小白同学:啥?Singleton和作用域有什么关系?我不是有字典缓存了吗?

    青龙偃月刀:我真恨不得自己再把自己磨快点。

    青龙偃月刀:ServiceProvider 是不是可以创建 三种不同生命周期的对象?

    小白同学:对,Singleton ,Scoped, Transient

    青龙偃月刀:那新的ServiceProvider创建的Singleton对象呢?

    小白同学:都是从缓存字典private readonly ConcurrentDictionary<Type, object> singletonCache 里面拿呗

    青龙偃月刀:。。。。。。 这个字典你放哪呢?

    小白同学:我放ServiceProvider类上啊

    青龙偃月刀:。。。。。。 那每一个新的ServiceProvider是不是都有一个新的缓存字典?

    小白同学:吃惊.gif, 不愧是宝刀

    小白同学:我换静态的 static ConcurrentDictionary<Type, object> singletonCache

    青龙偃月刀:那整个程序就只有一份了啊

    小白同学:对呀,就是只要一份

    青龙偃月刀:那一个程序里面多个DI容器呢?

    小白同学:大吃一惊.gif,还能这么玩?

    青龙偃月刀:不说其他,就说你单元测试一个DI容器能测试各种场景?

    小白同学:尴尬.gif 我目前只写了一个

    青龙偃月刀:...............你改吧

    小白同学:哦

    // 小白同学:在IServiceProvider接口上添加我们需要数据字段
    public interface IServiceProvider
    {
        Dictionary<Type, ServiceDefintion> Services {get;}
        ConcurrentDictionary<Type, object> SingletonCache {get;}
    }
    
    public class ServiceProvider : IServiceProvider
    {
        public Dictionary<Type, ServiceDefintion> Services {get;}
        public ConcurrentDictionary<Type, object> SingletonCache {get;}
    
        // 小白同学:复用对应的缓存
        public ServiceProvider(IServiceProvider provider)
        {
            Services = provider.Services;
            SingletonCache = provider.SingletonCache;
        }
    }
    
    public class ServiceScopeFactory : IServiceScopeFactory
    {
        private readonly IServiceProvider provider;
    
        // 小白同学:这样我们可以直接取已有的provider
        public ServiceScopeFactory(IServiceProvider provider)
        {
            this.provider = provider;
        }
    
        public IServiceProvider CreateScopeProvider()
        {
             // 小白同学:有了存在的provider,我们就能复用对应的缓存
            return new ServiceProvider(provider);
        }
    }
    

    小白同学:我们就可以这样注册ServiceScopeFactory

    var a = new ServiceDefintions();
    a.Add(new DelegateServiceDefintion(typeof(IServiceScopeFactory),typeof(ServiceScopeFactory),Lifetime.Transient, i => new ServiceScopeFactory(i)));
    

    青龙偃月刀:磨刀石呢?我要磨快点

    小白同学:又咋了,我写的这么完美?

    青龙偃月刀:你确定这样符合作用域的概念?

    小白同学:怎么不符合了?SingletonCache 都只有一个了,每个ServiceProvider都是创建新的Scoped生命周期对象

    青龙偃月刀:你看看你是怎么写创建新的Scoped生命周期对象的?

    小白同学:这样啊

            case Lifetime.Scoped:
                 return CreateObj(x);
    

    青龙偃月刀:一个Scoped生命周期内,一个ServiceType对应生成对象不该唯一吗?

    小白同学:为啥啊?生命周期不是用户自己控制了吗?

    青龙偃月刀:一个方法的作用域内,可以声明多个同名对象吗?

    小白同学:不能呀

    青龙偃月刀:那你允许一个Scoped作用域内,可以生成相同ServiceType,实际不同的对象?

    小白同学:他可以自己回收呗

    青龙偃月刀:你让人家自己回收 !!!??? 那人家为什么不用Transient,你这样和Transient有什么区别?

    小白同学:你说的好有道理,我竟无言以对

    小白同学:那我加缓存

    public class ServiceProvider : IServiceProvider
    {
        private ConcurrentDictionary<Type, object> scopedCache = new ConcurrentDictionary<Type, object>();
    
        public object CreateObj(Type serviceType)
        {
            case Lifetime.Scoped:
                 return scopedCache.GetOrAdd(serviceType, x => CreateObj(x));
        }
    }
    

    小白同学:怎么样?完美吧?

    青龙偃月刀:我劝你好好考虑一下,我的大刀已经饥渴难耐

    小白同学:哪儿不完美?明明很beautiful

    青龙偃月刀:再提示一下,用户是不是会这样用?

    IServiceProvider a = IServiceScopeFactory.CreateScopeProvider();
    
    doSomethings(a);
    
    a.Dispose();
    

    小白同学:对呀,可以完美应对呀

    青龙偃月刀:。。。。。。。。。你的Dispose做了什么?

    小白同学:emmmm 什么。。。 都没做?

    青龙偃月刀:那用户Dispose什么?

    小白同学:emmmm。。。。。。

    小白同学:好吧,既然有问题我们再改下

    public class ServiceProvider : IServiceProvider
    {
        private void Dispose()
        {
            // 小白同学:Dispose every thing
            foreach (var item in SingletonCache.Union(scopedCache))
            {
                var disposable = item as IDisposable;
                disposable?.Dispose();
            }
            scopedCache.Clear();
            SingletonCache.Clear();
        }
    }
    

    青龙偃月刀:........... 一个子作用域可以把SingletonCache Dispose 了?难道活到98岁不好吗?

    小白同学:啊。。。。。活到那么久很好啊。。。。哈,我知道怎么改

    public class ServiceProvider : IServiceProvider
    {
        public IServiceProvider Root { get; }
    
        public ServiceProvider(IServiceProvider provider)
        {
            Services = provider.Services;
            SingletonCache = provider.SingletonCache;
            Root = provider.Root;
        }
    
        private void Dispose()
        {
            // 小白同学:only Root can Dispose every thing
            //          others can onlu disposable scopedCache
            var disposables = (Root == this
                ? SingletonCache.Union(scopedCache)
                : scopedCache)
                .Where(x => x.Value != this);
            foreach (var scoped in disposables)
            {
                var disposable = scoped.Value as IDisposable;
                disposable?.Dispose();
            }
            scopedCache.Clear();
            if (Root == this) SingletonCache.Clear();
        }
    }
    

    小白同学:真完美!!!!!

    青龙偃月刀:呵呵,这样也能算完美?多少没做,还有多少问题没搞?你看人家做成这样子都算差的了 - https://github.com/fs7744/Norns

  • 相关阅读:
    二、Python基础练习
    代码测试服同步到生产服务器
    支付宝网站支付 异步验签成功 同步验签失败
    最近机房让整改的漏洞 设置cookie httponly X-Frame-Options头未设置
    连连支付,或微信或支付宝支付,商品名称最后一个字乱码,php解决
    ci 框架新手使用
    php制作公司五章,圆形印章和椭圆形印章,正方形印章,圆角正方形印章,圆角框
    Libreoffice php使用命令行office转pdf,pdf转图片
    后台返回数据回显,使用js控制默认选中复选框和下拉框
    Nginx日志按日期切割详解(按天切割)
  • 原文地址:https://www.cnblogs.com/fs7744/p/9931141.html
Copyright © 2020-2023  润新知