zoukankan      html  css  js  c++  java
  • never下ioc

    生命周期

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

                    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块代码就是我们使用了一个作用域的例子,所以作用域应该是指一件事的整个过程(这件事里面拆分了几个子事件,每个子事件又可以是一个作用域)。

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

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

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

        /// <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三个作用域跟踪者。

    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>的来源。

    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
  • 相关阅读:
    ASP.NET 2.0 中的新增服务、控件与功能概述作者:Jeff Prosise
    js学习笔记(二)
    js学习笔记(四)表达式和运算符
    js技巧1(判断浏览器)
    css点滴
    after effect 学习笔记(一)
    js学习笔记(六)函数
    js学习笔记(八)数组
    js学习笔记(三)变量
    js学习笔记(一)
  • 原文地址:https://www.cnblogs.com/shelldudu/p/11052200.html
Copyright © 2011-2022 走看看