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
相關文章
相關標籤/搜索