生命周期

当前分单例,作用域(范围),短暂。单例是整个服务中只有一个实例,短暂则是每一次得到的都是新的实例,作用域就是在该一套行动中内得到的是同一个实例,该行动中指的是什么?我们看看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"));
}

这里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();
}

当前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

    可以看到,该对象始终都会开启范围,由于参数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();
    }

    使用了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();
    }
    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;
    }
    });
    }

    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();
rule.RegisterCallBack(key, lifeStyle, mission);
register.Update(rule);
}

所有的注册规则要遵守:

  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;
    }

    在这里我们加上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);
    }
  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);
    }

    可以扩展一下:在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;
    }

    代码说明:target参数指的是目标对象,curren指得是目标对象构造方法里面的参数。为什么要遵守该规则?2个例子:(1)比如单例注入短暂的参数,很明显短暂有可能只能依赖HttpContent,但是在单例中,在非Web执行环境中,这个短暂的实例就会有HttpContent为空的错误。(2)短暂参数被设计为构造的时候开启事务 + 被disponse的时候释放,被注入到单例对象后这个事务一直开户并且造成不disponse的后果。

  2. 其他工具的生命周期的相容性,对于autofac,netcore的provider,似乎对上面的相容性没有那么大的限制,因此在easyioc中使用Resolveoptional则可以不用遵守上述相容规则:就是处理过程中优先遵守规则,出现问题至少使用一个规则,这样可以保证Resolve可正常得到对象。
  3. 构造者会检查循环引用,一旦发现有死循环引用,则抛异常
                /*选择策略*/
    if (level > )
    {
    /*递归检查*/
    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 - ] != null)
    {
    RuleMatchUsingNegativeSort(rule, recursion[recursion.Count - ]);
    }
    }

    代码中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; }
}
  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;
    ....
    }

    而且相对接近使用者层的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();
    rule.RegisterType(implementationType, serviceType, key, lifeStyle);
    register.Update(rule);
    }

    还记得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;
}

跟踪到里面的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; }
}

实际上无论是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

never下ioc的更多相关文章

  1. [转载]Spring下IOC容器和DI(依赖注入) @Bean及@Autowired

    Spring下IOC容器和DI(依赖注入) @Bean及@Autowired自动装配 bean是什么 bean在spring中可以理解为一个对象.理解这个对象需要换一种角度,即可将spring看做一门 ...

  2. .net core2.0下Ioc容器Autofac使用

    .net core发布有一段时间了,最近两个月开始使用.net core2.0开发项目,大大小小遇到了一些问题.准备写个系列介绍一下是如何解决这些问题以及对应技术.先从IOC容器Autofac开始该系 ...

  3. C#下IOC/依赖注入框架Grace介绍

    对依赖注入或控制反转不了解的童鞋请先自行学习一下这一设计,这里直接介绍项目和实现步骤. Grace是一个开源.轻巧.易用同时特性丰富.性能优秀的依赖注入容器框架.从这篇IOC容器评测文章找到的Grac ...

  4. Delphi下IOC 模式的实现(反转模式,即Callback模式)

    IOC英文为 Inversion of Control,即反转模式,这里有著名的好莱坞理论:你呆着别动,到时我会找你.Ioc模式是解决调用者和被调用者之间关系的模式,可以有效降低软件的耦合度,并适合团 ...

  5. Spring框架IOC容器和AOP解析

    主要分析点: 一.Spring开源框架的简介  二.Spring下IOC容器和DI(依赖注入Dependency injection) 三.Spring下面向切面编程(AOP)和事务管理配置  一.S ...

  6. 我理解的IOC技术在Java和C#中比较分析

    一直想用心写这个系列的文章,其实看得越多,也就越觉得自己在这方面的功力太浅,也就越不想班门弄斧啦,作为一个开篇,我想把这个技术深层次化,在之前的.net的一个MVC系列文章其实已经涉及到了,只是.ne ...

  7. 设计模式——Spring IoC中用到的模板方法模式

    基本概念 什么是模板方法(Template method):父类定义了骨架(调用哪些方法及顺序),某些特定方法由子类实现. 最大的好处:代码复用,减少重复代码.除了子类要实现的特定方法,其他方法及方法 ...

  8. Spring IoC源码解读——谈谈bean的几种状态

    阅读Spring IoC部分源码有一段时间了,经过不断的单步调试和参阅资料,对Spring容器中bean管理有了一定的了解.这里从bean的几个状态的角度出发,研究下IoC容器. 一.原材料 Xml中 ...

  9. 对依赖倒置原则(DIP)及Ioc、DI、Ioc容器的一些理解

    1.概述 所谓依赖倒置原则(Dependence Inversion Principle)就是要依赖于抽象,不要依赖于具体.简单的说就是要求对抽象进行编程,不要对实现进行编程,这样就降低了客户与实现模 ...

随机推荐

  1. Qt 自定义事件(三种方法:继承QEvent,然后Send Post就都可以了,也可以覆盖customEvent函数,也可覆盖event()函数)

    Qt 自定义事件很简单,同其它类库的使用很相似,都是要继承一个类进行扩展.在 Qt 中,你需要继承的类是 QEvent. 继承QEvent类,你需要提供一个QEvent::Type类型的参数,作为自定 ...

  2. 使用WPF创建画图箭头

    原文:使用WPF创建画图箭头 今天要给leader line画个箭头,所以就google一下,找到下面的文章,写的不错,可以实现我的需求,所以就摘录下来. 我把源代码中的arraw.cs加入到我的工程 ...

  3. explanatory variable(independent vs dependent)、design matrix

    design matrix(设计矩阵) 是统计学上的概念,一般标记为 X,是由一组对象的解释变量(explanatory variables)构成的矩阵. 1. explanatory variabl ...

  4. WPF范围选择控件(RangeSelector)

    原文:WPF范围选择控件(RangeSelector) 版权声明:本文为博主原创文章,转载请注明作者和出处 https://blog.csdn.net/ZZZWWWPPP11199988899/art ...

  5. 从入门机器学习的零单排:OctaveMatlab经常使用绘图知识

    OctaveMatlab经常使用绘图知识 之前一段时间在coursera看了Andrew ng的机器学习的课程,感觉还不错,算是入门了.这次打算以该课程的作业为主线,对机器学习基本知识做一下总结.小弟 ...

  6. WPF不明内存泄露已解决,白头发也没了

    原文:WPF不明内存泄露已解决,白头发也没了 在使用OpenExpressApp进行WPF应用开发过程中遇到多个内存泄漏的地方,在上一篇中求助了一个内存泄露问题[WPF不明内存泄露原因,头发都白了几根 ...

  7. 向WPF的Grid里面增加控件

    在c#中,有时需要通过代码创建面板控件,但是对于面板的操作与之前的控件并不是很直观,因此记下方法,以供后来复习使用: 创建两行两列的表格: 第一种方法: Grid grid = new Grid(); ...

  8. 图片处理拓展篇 : 图片转字符画(ascii)

    首先要明确思路, 图片是由像素组成的, 不同的像素有不同的颜色(rgb), 那么既然我们要转化为字符画, 最直接的办法就是利用字符串来替代像素, 也就是用不同的字符串来代表不同的像素. 另外图片一般来 ...

  9. WAMP采用别名时ThinkPHP5项目设置方法

    打开public目录下的.htaccess文件,并在其中添加 RewriteBase /xxx<IfModule mod_rewrite.c> Options +FollowSymlink ...

  10. android Choose library dependency 搜索不到目标库

    问题:Choose library dependency 搜索不到目标库,百度了一下,发现尽是废话,无解,反正就是升级ide,我是 android studio是2.3.3(网上说升级到3.+就好了, ...