never下ioc

生命周期

当前分单例,做用域(范围),短暂。单例是整个服务中只有一个实例,短暂则是每一次获得的都是新的实例,做用域就是在该一套行动中内获得的是同一个实例,该行动中指的是什么?咱们看看demo下的startup里面一个方法html

                using (var sc = x.ServiceLocator.BeginLifetimeScope())
                {
                    var serv = sc.Resolve<IUserService>();
                    sc.Resolve<IVCodeService>();
                    sc.Resolve<IUserService>();
                    sc.Resolve<IUserProxyService>();
                    sc.Resolve<Controllers.LoginController>();
                    var logger = sc.Resolve<ILoggerBuilder>().Build(typeof(Startup));
                    logger.Info("startup at " + DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"));
                }
View Code

这里using块代码就是咱们使用了一个做用域的例子,因此做用域应该是指一件事的整个过程(这件事里面拆分了几个子事件,每一个子事件又能够是一个做用域)。git

在web模式中,从beginreqeust到endrequest,咱们均可以认为从开始到结束的一种做用域,这就是web的周期。autofac对周期的描述:IoC之AutoFac(三)——生命周期github

easyioc中用了ILifetimeScopeTracker接口让使用者去管理做用域周期,好比想在web实现的begin+end周期,ILifetimeScope StartScope(ILifetimeScope parent)方法中返回的对象使用HttpContext.Item去管理就能够了。web

每一次使用都要开启一个做用域,将要释放资源的对象(实现了IDisposable 接口)放到ILifetimeScope的上下文的释放队列中,用于等下被调用方法释放。单例不会进入ILifetimeScope的释放队列中,而短暂 + 做用域的就有可能被加入到队列中(有可能对象没有实现IDisposable接口)。sql

    /// <summary>
    /// 组件生命范围定义跟踪者
    /// </summary>
    public interface ILifetimeScopeTracker
    {
        /// <summary>
        /// 开始一个范围
        /// </summary>
        /// <param name="parent"></param>
        /// <returns></returns>
        ILifetimeScope StartScope(ILifetimeScope parent);

        /// <summary>
        /// 清空全部范围
        /// </summary>
        void CleanScope();
    }
View Code

当前easyioc中有 DefaultLifetimeScopeTracker,ThreadLifetimeScopeTracker,WebLifetimeScopeTracker三个做用域跟踪者。shell

  1. DefaultLifetimeScopeTracker
    #region ILifetimeScopeTracker
    
            /// <summary>
            /// 开始一个范围
            /// </summary>
            /// <param name="parent"></param>
            /// <returns></returns>
            public virtual ILifetimeScope StartScope(ILifetimeScope parent)
            {
                return parent == null ? parent : parent.BeginLifetimeScope();
            }
    
            /// <summary>
            /// 结束全部范围
            /// </summary>
            public virtual void CleanScope()
            {
            }
    
            #endregion ILifetimeScopeTracker
    View Code

    能够看到,该对象始终都会开启范围,因为参数ILifetimeScope parent始终是系统ILifetimeScope第一个实例,每一次BeginLifetimeScope获得的对象都是新的一个ILifetimeScope实例。设计模式

  2. ThreadLifetimeScopeTracker
    private readonly System.Threading.ThreadLocal<ILifetimeScope> threadLocal = null;
       
         public override ILifetimeScope StartScope(ILifetimeScope parent)
            {
                if (this.threadLocal.IsValueCreated)
                    return this.threadLocal.Value;
    
                return this.threadLocal.Value = base.StartScope(parent);
            }
    
            public override void CleanScope()
            {
                if (this.threadLocal.IsValueCreated && this.threadLocal.Value != null)
                {
                    this.threadLocal.Value.Dispose();
                    this.threadLocal.Value = null;
                }
    
                base.CleanScope();
            }
    View Code

    使用了System.Threading.ThreadLocal<T>去管理当前ILifetimeScope,跟名字同样,用在线程管理的场景,可是异步线程会有切换问题,能够看看AsyncLocal<T>的来源。api

  3. WebLifetimeScopeTracker
    public override ILifetimeScope StartScope(ILifetimeScope parent)
            {
                return new HttpThreadCache().Get("BeginLifetimeScope", () => base.StartScope(parent));
            }
    
            public override void CleanScope()
            {
                var cache = new HttpThreadCache();
                var scope = cache.Get<ILifetimeScope>("BeginLifetimeScope");
                if (scope != null)
                    scope.Dispose();
    
                cache.Remove("BeginLifetimeScope");
                base.CleanScope();
            }
    View Code
    static HttpThreadCache()
            {
                asyncLocak = new AsyncLocal<IDictionary>();
                init = new Func<IDictionary>(() =>
                {
    #if NET461
                    if (HttpContext.Current == null)
                        goto _do;
    
                    if (HttpContext.Current.Items.Contains(key))
                        return System.Web.HttpContext.Current.Items[key] as Hashtable;
    
                    var result = new Hashtable();
                    HttpContext.Current.Items[key] = result;
    
                    return result;
    #else
                    goto _do;
    #endif
                _do:
                    {
                        if (asyncLocak.Value == null)
                            asyncLocak.Value = new Hashtable();
    
                        return asyncLocak.Value;
                    }
                });
            }
    View Code

    web周期的跟踪者,HttpThreadCached对象就是在framework中使用了上面说到的HttpContent.Item去管理,非framework则使用了System.Thread.AsyncLocal<T>去管理。framework下相对ThreadLifetimeScopeTracker无非就是将周期拉长而已数组

注册规则

ioc.RegisterType<T,IT>(string key,lifestyle style) 像这样的方法注入了IT接口T实现的一个规则,key能够为空。在easyioc中还能够注入回调方法去构造对象缓存

        /// <summary>
        /// 注册对象实例映射关系
        /// </summary>
        /// <typeparam name="TService">服务类型</typeparam>
        /// <param name="mission">回调生成</param>
        /// <param name="key">key</param>
        /// <param name="lifeStyle">生命周期</param>
        /// <returns></returns>
        public void RegisterCallBack<TService>(string key, ComponentLifeStyle lifeStyle, Func<ILifetimeScope, TService> mission)
        {
            if (this.option.Value.Unabled)
                throw new InvalidException("the builder is builded,can not update rules");

            var rule = new RegisterRuleCollector(1);
            rule.RegisterCallBack(key, lifeStyle, mission);
            register.Update(rule);
        }
View Code

全部的注册规则要遵照:

  1. T一定是可实例化的(即使在回调注入中,本身返回的T也是要本身构造出来),IT能够是接口,也能够是对象
  2. 屡次注册相同的实例,是合理的,并不会出现前浪被后浪拍死,只是后面会引起Resolve的优先级问题。
  3. 每一个注册规则RegisterRule都有惟一标识,该标识内部自动生成。

注册规则对象RegisterRule

该对象的定义比较复杂,实际上你能够理解这里是保存了4个核心对象:T,IT,key,lifestyle。咱们上面ioc.RegisterType<T,IT>(string key,lifestyle style)方法用到的对象就是这个RegisterRule对象了。

    /// <summary>
    /// 注册规则
    /// </summary>
    public class RegisterRule : IEquatable<RegisterRule>, ICloneable, IDisposable, IRegisterRule, IParameterRegisterRule, IProxyRegisterRule, IRegisterRuleDescriptor
    {
          .....
    }
  1. IEquatable<RegisterRule>接口 实现两个规则相等性,每一个规则有惟一Id,故里面一定使用上该Id去区分
    private string ConcatCachedKey()
            {
                switch (this.lifeStyle)
                {
                    case ComponentLifeStyle.Singleton:
                        {
                            return string.Concat("s", this.key, "_", increment);
                        }
                    case ComponentLifeStyle.Transient:
                        {
                            return string.Concat("t", this.key, "_", increment);
                        }
                    case ComponentLifeStyle.Scoped:
                        {
                            return string.Concat("l", this.key, "_", increment);
                        }
                }
    
                return this.serviceType.FullName;
            }
    View Code

    在这里咱们加上key和style表示一些额外的信息,实际彻底能够用该Id去对比。

  2. ICloneable 接口,用来克隆该规则,目前用于生成代理用到 + 泛型规则,生成代理和IProxyRegisterRule配合使用,主要思想是生成的代理实现了被代理对象的功能,使用了装饰者设计模式,代理类注入了被代理的对象,此时代理类也被当生成新的注册规则;注入是泛型Repository<T>,等下要Resolve的是Repostory<int>,这样Repostory<int>从Repository<T>规则克隆出来。
  3. IRegisterRuleDescriptor 描述规则属性,一定包含了4个核心对象:T,IT,key,lifestyle;还带有其余属性,好比Parameters属性,表示这个规则匹配构造参数可指定特定参数。
  4. IParameterRegisterRule 参数注册规则,用于规则指定使用某个规则注入(系统注入多个IA接口,好比AA,BA,那么该方法能够指定注入AA,不然系统会找到BA)
    /// <summary>
        /// 参数注册规则
        /// </summary>
        public interface IObviousProxyRegisterRule
        {
            /// <summary>
            /// 构造函数参数
            /// </summary>
            /// <typeparam name="TService">服务类型</typeparam>
            /// <param name="key">注册key</param>
            /// <returns></returns>
            IObviousProxyRegisterRule WithParameter<TService>(string key);
        }
    View Code
  5. IProxyRegisterRule 代理注册规则,能够注入多个拦截器
            IProxyRegisterRule WithInterceptor<TInterceptor>(string key) where TInterceptor : Never.Aop.IInterceptor;

    拦截器定义以下:

    /// <summary>
        /// 拦截接口
        /// </summary>
        public interface IInterceptor
        {
            /// <summary>
            /// 在对方法进行调用前
            /// </summary>
            /// <param name="invocation">调用信息</param>
            void PreProceed(IInvocation invocation);
    
            /// <summary>
            /// 对方法进行调用后
            /// </summary>
            /// <param name="invocation">调用信息</param>
            void PostProceed(IInvocation invocation);
        }
    View Code

    能够扩展一下:在webapi请求过程当中,对每一个方法调用进行监督其性能,可使用该特性注入性能监督拦截器

规则构建者RegisterRuleBuilder

对每一条使用到的规则,去进行实例化的构建;RegisterRuleBuilder该对象分析规则的构造函数,找到适当的构造方法(含参数),使用emit去调用该构造而去实例目标对象(指的是规则里面的T目标),将构造好的方法缓存起来放到RegisterRule的Builder与OptionalBuilder这2个属性

  1. 生命周期的相容,一般来讲,单例能够注入任何周期中,做用域只能注入到做用域+短暂中,短暂只能注入到短暂;而easyioc遵照该规则
            /// <summary>
            /// 是否相容的周期
            /// </summary>
            /// <param name="current">当前周期</param>
            /// <param name="target">目标周期</param>
            /// <returns></returns>
            public static string Compatible(this RegisterRule target, RegisterRule current)
            {
                switch (current.LifeStyle)
                {
                    /*单例能够注入到任何实例中,其构造只能是单例对象*/
                    case ComponentLifeStyle.Singleton:
                        {
                            return string.Empty;
                        }
                    /*短暂只能注入到短暂,其构造可接受任何实例对象*/
                    case ComponentLifeStyle.Transient:
                        {
                            if (target.LifeStyle != ComponentLifeStyle.Transient)
                                return string.Format("构建当前对象{0}为{1},指望对象{2}为短暂,不能相容",
                                    target.ServiceType.FullName,
                                    target.LifeStyle == ComponentLifeStyle.Scoped ? "做用域" : "单例",
                                    current.ServiceType.FullName);
    
                            return string.Empty;
                        }
                    /*做用域其构造不能接受短暂,可接受有做用域和单例*/
                    case ComponentLifeStyle.Scoped:
                        {
                            if (target.LifeStyle == ComponentLifeStyle.Singleton)
                                return string.Format("构建当前对象{0}为单例,指望对象{1}为做用域,不能相容",
                                    target.ServiceType.FullName,
                                    current.ServiceType.FullName);
    
                            return string.Empty;
                        }
                }
    
                return string.Empty;
            }
    View Code
    代码说明:target参数指的是目标对象,curren指得是目标对象构造方法里面的参数。为何要遵照该规则?2个例子:(1)好比单例注入短暂的参数,很明显短暂有可能只能依赖HttpContent,可是在单例中,在非Web执行环境中,这个短暂的实例就会有HttpContent为空的错误。(2)短暂参数被设计为构造的时候开启事务 + 被disponse的时候释放,被注入到单例对象后这个事务一直开户而且形成不disponse的后果。
  2. 其余工具的生命周期的相容性,对于autofac,netcore的provider,彷佛对上面的相容性没有那么大的限制,所以在easyioc中使用Resolveoptional则能够不用遵照上述相容规则:就是处理过程当中优先遵照规则,出现问题至少使用一个规则,这样能够保证Resolve可正常获得对象。
  3. 构造者会检查循环引用,一旦发现有死循环引用,则抛异常
                /*选择策略*/
                if (level > 0)
                {
                    /*递归检查*/
                    foreach (var re in recursion)
                    {
                        if (re.ImplementationType == rule.ImplementationType)
                        {
                            throw new ArgumentOutOfRangeException(string.Format("{0}和{1}类型造成递归调用", re.ImplementationType.FullName, rule.ImplementationType.FullName));
                        }
                    }
    
                    if (recursion[recursion.Count - 1] != null)
                    {
                        RuleMatchUsingNegativeSort(rule, recursion[recursion.Count - 1]);
                    }
                }
    View Code

    代码中RuleMatchUsingNegativeSort是检查规则的相容性。

  4. ResolveAll<T>去构建数组对象的,永不返回null,至少返回new T[0]
  5. Resolve过程当中若是构造方法参数Generic<int>是泛型Generic<T>注入的,则找到Generic<T>规则后从新构造一个Generic<int>的规则(该新规则被缓存到T目标对象规则里面的数组里面,能够看看ReigsterRule的实现)
  6. 系统默认注入了数组和字典的注册规则,考虑到咱们只注入了<T,T>(key,style),若是咱们Resolve<IEnumerable<T>>,系统没有数组的注入规则,则放方法直接抛异常。。
  7. 系统注入多个IA接口,好比AA,BA。当要Resolve<IA>的时候,先将BA,AA都查询出来到某个集合,再按策略去使用BA仍是AA,策略当前是:先是key是否相等,再是相容性(若是不是ResolveOptional方法的话),而后是加入时序:从尾到首,所以BA的几率会比AA的几率大。

容器定义

实际上叫容器是要在不一样场景的叫法,好比咱们的注册规则也要有个集合,保存着全部的规则,咱们也叫容器,而相对于整个系统来讲,注入Register,构建Resolve等全部组件组合起来,这也是容器(easyContainer的面貌)

    /// <summary>
    /// IoC容器接口
    /// </summary>
    public interface IContainer
    {
        /// <summary>
        /// 服务注册器
        /// </summary>
        IServiceRegister ServiceRegister { get; }

        /// <summary>
        /// 服务定位器
        /// </summary>
        IServiceLocator ServiceLocator { get; }

        /// <summary>
        /// 服务建立器
        /// </summary>
        IServiceActivator ServiceActivator { get; }

        /// <summary>
        /// 类型发现者
        /// </summary>
        ITypeFinder TypeFinder { get; }
    }
View Code
  1. ServiceRegister 对象,注册规则
  2. ServiceLocator 对象,Resolve对象
  3. ServiceActivator 一些没有注入的对象,可使用规则去构造一个(生成规则过程会特别一点,跟启动顺序有关系),跟Activator.CreateInstance差很少相同的方式。
  4. TypeFinder 类型发现者,协助查询特定类。
    /// <summary>
    /// IoC容器
    /// </summary>
    public class EasyContainer : Never.IoC.IContainer, Never.IoC.IContainerStartup, IValuableOption<UnableRegisterRule>
  1. IContainerStartup接口定义容器的启动行为:初始化,启动中。初始化Init(),里面能够注入规则,触发OnIniting事件。启动中Startup(),也能够注入规则,触发OnStarting事件。二者有什么区别?还记得netcore下的startup启动代码吗UseEasyIoC的两个回调方法分别是对OnIniting和OnStarting两个事件的一个委托绑定,二者的区别就是里面说的“ioc分开2种启动方法:第一与最后,主要缘由以下:(1)服务启动有前后顺序,不一样的系统组件所注册的顺序不一样的,但有些组件要求在全部环境下都只有第一或最后启动(2)因为使用环境自动注册这种设计下,一些组件要手动注册会带本身的规则就会被自动注册覆盖”。第2个缘由能够解释为:当你注入的对象有可能被当成一种组件而系统自动化注入了,但实际上咱们又想手动加一些参数注入,因此咱们能够在Startup方法调用就能够了。
  2. IValuableOption<UnableRegisterRule> 该接口是用来限制注册规则的注入时机。想一想一个场景下,咱们在时刻A的时候Resolve<IA>用的是AA规则,实际上咱们一直指望后面用到IA的都是AA规则就行了,此时时刻B若是再注入了一个BA,咱们指望一直使用AA就出现麻烦,按注入规则后再要使用到Resolve<IA>是会找到BA规则的,固然有人说BA带个key注入也是个解决办法。可是为了保护规则不被破坏,咱们就要设定一旦系统组件已经初始化后(Startup调用方法)就再也不接受注入规则。按这个定义咱们应该在注入规则的容器中应该会有这样的判断,追随代码能够看到RegisterRuleContainer里面的Update方法
            /// <summary>
            /// 更新容器规则
            /// </summary>
            /// <param name="collector"></param>
            public void Update(RegisterRuleCollector collector)
            {
                if (option != null && option.Value.Unabled)
                    return;
    ....
    }
    View Code

    并且相对接近使用者层的ServiceRegister对象,则是直接抛异常

            public void RegisterType(Type implementationType, Type serviceType, string key, ComponentLifeStyle lifeStyle)
            {
                if (this.option.Value.Unabled)
                    throw new InvalidException("the builder is builded,can not update rules");
    
                var rule = new RegisterRuleCollector(1);
                rule.RegisterType(implementationType, serviceType, key, lifeStyle);
                register.Update(rule);
            }
    View Code

    还记得Autofac里面ContainerBuilder的Update方法?官方目前已经被标识为废弃方法,大伙能够讨论一下为何会这样。

环境的自动注入

可能懒惰的缘由,咱们不用每一次都手动注入<AA,IA>,<BA,IA>这种规则,因此咱们能够定义扫描程序集去找到AA,BA后注入。举个栗子,程序C我不想BA注入,程序D又想只用BA,程序E二者均可以,所以不一样环境下扫描程序集后想要注入AA和BA也要有策略。

假设<AA,IA>规则是单例 + 运行环境是"programc",<BA,IA>规则是做用域 + 运行环境是"programd",程序C的运行环境是“programc",规则扫描者是扫描单例的,而程序D运行环境是”programd",规则扫描者是扫描线程的,程序E的运行环境是""(可认为*,能够匹配全部环境),规则扫描者是扫描线程的+扫描单例的。在这三种环境中,能够得出,程序C环境匹配 + 单例扫描者扫描到AA,能够注入<AA,IA>,单例扫描者扫描不到BA这个类型(为何描述不到?一个是环境,一个是单例只匹配单例,不匹配做用域+短暂),因此不会注入<BA,IA>,程序E则能够注入<BA,IA>,<AA,IA>,而程序D自己环境不是”grogramc",直接环境不匹配<BA,IA>。系统默认实现了3个扫描者:

  1. ScopedAutoInjectingEnvironmentProvider + ScopedAutoInjectingAttribute  对带有ScopedAutoInjectingAttribute特性的对象,若是Env跟ScopedAutoInjectingEnvironmentProvider相匹配,那么这个对象就被注入为做用域周期。
  2. SingletonAutoInjectingEnvironmentProvider +  SingletonAutoInjectingAttribute 对带有SingletonAutoInjectingAttribute 特性的对象,若是Env跟SingletonAutoInjectingEnvironmentProvider相匹配,那么这个对象就被注入为单例周期。
  3. TransientAutoInjectingEnvironmentProvider +  TransientAutoInjectingAttribute 对带有TransientAutoInjectingAttribute 特性的对象,若是Env跟TransientAutoInjectingEnvironmentProvider相匹配,那么这个对象就被注入为短暂周期。

在代码中咱们发现IAutoInjectingEnvironmentRuleCollector定义,这个接口的做用是什么?里面方法Register参数中的collector是什么对象?

当前咱们有好几个IoC工具,第一种工具都有本身的实现方法,特别是其Container的核心设计,这个核心有些的方法咱们想用的话,构架就要将其暴露出去,只不过构架要抽象出来方便作适配,所以IAutoInjectingEnvironmentRuleCollector接口可让不一样的工具作适配工具而已。

何时会调用这个自动注入的接口方法?

void Call(AutoInjectingGroupInfo[] groups, IContainerStartupEventArgs eventArgs)

咱们先去看看扩展方法Never.StartupExtension.UseAutoInjectingAttributeUsingIoC方法,第二个参数接受的是IAutoInjectingEnvironmentProvider[] providers,一个数组,说明咱们环境能够有多个扫描规则者

        /// <summary>
        /// 在sampleioc中自动使用属性发现注入
        /// </summary>
        /// <param name="startup"></param>
        /// <param name="providers"></param>
        /// <returns></returns>
        public static ApplicationStartup UseAutoInjectingAttributeUsingIoC(this ApplicationStartup startup, IAutoInjectingEnvironmentProvider[] providers)
        {
            startup.RegisterStartService(new AutoInjectingStartupService(providers));
            return startup;
        }
View Code

跟踪到里面的AutoInjectingStartupService类型,咱们发现环境自动注入是使用了IContainerStartup接口的OnStarting事件,IContainerStartup接口则是定义了IContainer的启动过程,OnStarting事件一定是Container里面调用的,咱们也发现IContainerStartupEventArgs对象的属性Collector被设定为object类型,跟咱们上面说的IAutoInjectingEnvironmentRuleCollector接口方法Register参数的collector同样的设计。

    /// <summary>
    /// 容器初始化过程事件
    /// </summary>
    public class IContainerStartupEventArgs : EventArgs
    {
        /// <summary>
        /// 类型发现者
        /// </summary>
        public ITypeFinder TypeFinder { get; }

        /// <summary>
        /// 程序集
        /// </summary>
        public IEnumerable<Assembly> Assemblies { get; }

        /// <summary>
        /// app
        /// </summary>
        public object Collector { get; }
    }
View Code

实际上不管是OnIniting事件仍是OnStarting事件,咱们会将Collector对象设计为每种IoC技术方案的规则容器,好比Autofac的是Autofac.ContainerBuilder类型,StructureMap的是StructureMap.Container类型,都只是让使用者能够直接使用Autofac.ContainerBuilder或StructureMap.Container的友好特性而已,固然前提你要知道你当前使用的是Autofac,仍是StructureMap或者是EasyIoC。

其余IoC的结合使用方案

若是我先使用autofac来替换easyioc怎么办?先去github下载never的扩展信息

咱们能够打开Never.IoC.Autofac项目代码发现,实际上也是实现了上面说到的IContainer,IServiceLocator,IServiceActivator,IServiceRegister,ILifetimeScope5个核心接口,而后在Startup对象中ApplicationStartup实例使用.UseAutofac()方法就能够了。

而环境的自动注入解决方案:实现IAutoInjectingEnvironmentRuleCollector接口,传入到TransientAutoInjectingEnvironmentProvider构造就能够了,当前组件要本身实现哦,看着Never下面的AutoInjectingEnvironmentCollector对象就能够了

 

文章导航:

  1. never框架
  2. sqlcient 一套容易上手性能又不错的sqlhelper
  3. easySql使用xml管理带事务的orm
相关文章
相关标签/搜索