16

ASP.NET Core中IOC容器的实现原理

 3 years ago
source link: http://mp.weixin.qq.com/s?__biz=MzU4Mjc4NzgyOQ%3D%3D&%3Bmid=2247486846&%3Bidx=1&%3Bsn=aca97b884ab9c799154901bf86a60887
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

IjEvUn6.gif!mobile

本章将和大家分享ASP.NET Core中IOC容器的实现原理。

首先我们需要了解什么是IOC,为什么要使用IOC容器?

一、依赖

类A用到了类B,我们就说类A依赖类B。

using System;

namespace MyIOCDI
{
    public class Test
    {
        public void Show()
        {
            MyDependency myDependency = new MyDependency(); //全是细节
            myDependency.Show();
            Console.WriteLine($"This is {this.GetType().FullName}");
        }
    }

    public class MyDependency
    {
        public void Show()
        {
            Console.WriteLine($"This is {this.GetType().FullName}");
        }
    }
}

上面的示例中,类Test就依赖了MyDependency类。

二、依赖倒置原则(Dependence Inversion Principle)

依赖倒置原则:高层模块不应该依赖于低层模块,二者都应该依赖于抽象。应该依赖于抽象,而不是依赖细节。

什么是高层模块?这里的使用者Test类就称为高层模块。什么是低层模块?被使用者MyDependency类就称为低层模块。上面的示例中我们的高层模块就依赖于我们的低层模块。

那么这样子有什么不好呢?

1、面向对象语言开发,就是类与类之间进行交互,如果高层直接依赖低层的细节,细节是多变的,那么低层的变化就导致上层的变化;

2、如果层数多了,低层的修改会直接水波效应传递到最上层,一点细微的改动都会导致整个系统从下往上的修改。

因此,上例按照依赖倒置原则修改如下:

using System;

namespace MyIOCDI
{
    public class Test
    {
        public void Show()
        {
            IDepenency myDependency = new MyDependency(); //左边抽象右边细节
            myDependency.Show();
            Console.WriteLine($"This is {this.GetType().FullName}");
        }
    }

    public class MyDependency : IDepenency
    {
        public void Show()
        {
            Console.WriteLine($"This is {this.GetType().FullName}");
        }
    }

    public interface IDepenency
    {
        void Show();
    }
}

三、IOC控制反转

控制反转是一种思想,所谓“控制反转”就是反转获得依赖对象的过程。

上面示例经过改造后虽然遵循了“依赖倒置原则”,但是违背了“开放封闭原则”,因为如果有一天想要修改变量myDependency为YourDependency类的实例,则需要修改Test类。

因此,我们需要反转这种创建对象的过程:

using System;

namespace MyIOCDI
{
    public class Test
    {
        private readonly IDepenency _myDependency;
        public Test(IDepenency myDependency)
        {
            this._myDependency = myDependency;
        }

        public void Show()
        {
            _myDependency.Show();
            Console.WriteLine($"This is {this.GetType().FullName}");
        }
    }

    public class MyDependency : IDepenency
    {
        public void Show()
        {
            Console.WriteLine($"This is {this.GetType().FullName}");
        }
    }

    public interface IDepenency
    {
        void Show();
    }
}

上例中,将 _myDependency 的创建过程“反转”给了调用者。

四、依赖注入(Dependency Injection)

依赖注入是一种在类及其依赖对象之间实现控制反转(IOC)思想的技术。

所谓依赖注入,就是由IOC容器在运行期间,动态地将某种依赖关系注入到对象之中。

依赖注入就是能做到构造某个对象时,将依赖的对象自动初始化并注入 。

IOC是目标是效果,需要DI依赖注入的手段。

三种注入方式:构造函数注入--属性注入--方法注入(按时间顺序)。

构造函数注入用的最多,默认找参数最多的构造函数,可以不用特性,可以去掉对容器的依赖。

五、IOC容器的实现原理

IOC容器的实现原理:

1、启动时保存注册信息。

2、在构造某个对象时,根据注册信息使用反射加特性,将依赖的对象自动初始化并注入。

3、对对象进行生命周期管理或者进行AOP扩展等。

下面我们重点来看下如何创建一个简易的IOC容器(当然,实际使用的IOC容器要比这复杂的多)。

首先来看下项目的目录结构:

E3MVV3Q.png!mobile

此处IOC容器中用到的自定义特性如下所示:

using System;

namespace TianYaSharpCore.IOCDI.CustomAttribute
{
    /// <summary>
    /// 构造函数注入特性
    /// </summary>
    [AttributeUsage(AttributeTargets.Constructor)]
    public class ConstructorInjectionAttribute : Attribute
    {

    }
}
using System;

namespace TianYaSharpCore.IOCDI.CustomAttribute
{
    /// <summary>
    /// 方法注入特性
    /// </summary>
    [AttributeUsage(AttributeTargets.Method)]
    public class MethodInjectionAttribute : Attribute
    {

    }
}
using System;

namespace TianYaSharpCore.IOCDI.CustomAttribute
{
    /// <summary>
    /// 常量
    /// </summary>
    [AttributeUsage(AttributeTargets.Parameter)]
    public class ParameterConstantAttribute : Attribute
    {

    }
}
using System;

namespace TianYaSharpCore.IOCDI.CustomAttribute
{
    /// <summary>
    /// 简称(别名)
    /// </summary>
    [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Property)]
    public class ParameterShortNameAttribute : Attribute
    {
        public string ShortName { get; private set; }
        public ParameterShortNameAttribute(string shortName)
        {
            this.ShortName = shortName;
        }
    }
}
using System;

namespace TianYaSharpCore.IOCDI.CustomAttribute
{
    /// <summary>
    /// 属性注入特性
    /// </summary>
    [AttributeUsage(AttributeTargets.Property)]
    public class PropertyInjectionAttribute : Attribute
    {

    }
}

创建一个简易的IOC容器,如下所示:

using System;

namespace TianYaSharpCore.IOCDI.CustomContainer
{
    public class IOCContainerRegistModel
    {
        public Type TargetType { get; set; }

        /// <summary>
        /// 生命周期
        /// </summary>
        public LifetimeType Lifetime { get; set; }

        /// <summary>
        /// 仅限单例
        /// </summary>
        public object SingletonInstance { get; set; }
    }

    /// <summary>
    /// 生命周期
    /// </summary>
    public enum LifetimeType
    {
        Transient, //瞬时
        Singleton,
        Scope, //作用域
        PerThread //线程单例
        //外部可释放单例
    }
}
using System;

namespace TianYaSharpCore.IOCDI.CustomContainer
{
    /// <summary>
    /// IOC容器接口
    /// </summary>
    public interface ITianYaIOCContainer
    {
        void Register<TFrom, TTo>(string shortName = null, object[] paraList = null, LifetimeType lifetimeType = LifetimeType.Transient)
            where TTo : TFrom;
        TFrom Resolve<TFrom>(string shortName = null);
        ITianYaIOCContainer CreateChildContainer();
    }
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;

using TianYaSharpCore.IOCDI.CustomAttribute;

namespace TianYaSharpCore.IOCDI.CustomContainer
{
    /// <summary>
    /// IOC容器
    /// </summary>
    public class TianYaIOCContainer : ITianYaIOCContainer
    {
        #region 字段或者属性

        /// <summary>
        /// 保存注册信息
        /// </summary>
        private Dictionary<string, IOCContainerRegistModel> _tianYaContainerDictionary = new Dictionary<string, IOCContainerRegistModel>();

        /// <summary>
        /// 保存常量的值
        /// </summary>
        private Dictionary<string, object[]> _tianYaContainerValueDictionary = new Dictionary<string, object[]>();

        /// <summary>
        /// 作用域单例的对象
        /// </summary>
        private Dictionary<string, object> _tianYaContainerScopeDictionary = new Dictionary<string, object>();

        #endregion 字段或者属性

        #region 构造函数

        /// <summary>
        /// 无参构造行数
        /// </summary>
        public TianYaIOCContainer()
        {

        }

        /// <summary>
        /// 主要在创建子容器的时候使用
        /// </summary>
        private TianYaIOCContainer(Dictionary<string, IOCContainerRegistModel> tianYaContainerDictionary,
            Dictionary<string, object[]> tianYaContainerValueDictionary, Dictionary<string, object> tianYaContainerScopeDictionary)
        {
            this._tianYaContainerDictionary = tianYaContainerDictionary;
            this._tianYaContainerValueDictionary = tianYaContainerValueDictionary;
            this._tianYaContainerScopeDictionary = tianYaContainerScopeDictionary;
        }

        #endregion 构造函数

        /// <summary>
        /// 创建子容器
        /// </summary>
        public ITianYaIOCContainer CreateChildContainer()
        {
            return new TianYaIOCContainer(this._tianYaContainerDictionary, this._tianYaContainerValueDictionary,
                new Dictionary<string, object>()); //没有注册关系,最好能初始化进去
        }

        /// <summary>
        /// 获取键
        /// </summary>
        private string GetKey(string fullName, string shortName) => $"{fullName}___{shortName}";

        /// <summary>
        /// 加个参数区分生命周期--而且注册关系得保存生命周期
        /// </summary>
        /// <typeparam name="TFrom">要添加的服务的类型</typeparam>
        /// <typeparam name="TTo">要使用的实现的类型</typeparam>
        /// <param name="shortName">简称(别名)(主要用于解决单接口多实现)</param>
        /// <param name="paraList">常量参数</param>
        /// <param name="lifetimeType">生命周期</param>
        public void Register<TFrom, TTo>(string shortName = null, object[] paraList = null, LifetimeType lifetimeType = LifetimeType.Transient)
            where TTo : TFrom
        {
            this._tianYaContainerDictionary.Add(this.GetKey(typeof(TFrom).FullName, shortName), new IOCContainerRegistModel()
            {
                Lifetime = lifetimeType,
                TargetType = typeof(TTo)
            });

            if (paraList != null && paraList.Length > 0)
            {
                this._tianYaContainerValueDictionary.Add(this.GetKey(typeof(TFrom).FullName, shortName), paraList);
            }
        }

        /// <summary>
        /// 获取对象
        /// </summary>
        public TFrom Resolve<TFrom>(string shortName = null)
        {
            return (TFrom)this.ResolveObject(typeof(TFrom), shortName);
        }

        /// <summary>
        /// 递归--可以完成不限层级的对象创建
        /// </summary>
        private object ResolveObject(Type abstractType, string shortName = null)
        {
            string key = this.GetKey(abstractType.FullName, shortName);
            var model = this._tianYaContainerDictionary[key];

            #region 生命周期

            switch (model.Lifetime)
            {
                case LifetimeType.Transient:
                    Console.WriteLine("Transient Do Nothing Before");
                    break;
                case LifetimeType.Singleton:
                    if (model.SingletonInstance == null)
                    {
                        break;
                    }
                    else
                    {
                        return model.SingletonInstance;
                    }
                case LifetimeType.Scope:
                    if (this._tianYaContainerScopeDictionary.ContainsKey(key))
                    {
                        return this._tianYaContainerScopeDictionary[key];
                    }
                    else
                    {
                        break;
                    }
                default:
                    break;
            }

            #endregion 生命周期

            Type type = model.TargetType;

            #region 选择合适的构造函数

            ConstructorInfo ctor = null;
            //标记特性
            ctor = type.GetConstructors().FirstOrDefault(c => c.IsDefined(typeof(ConstructorInjectionAttribute), true));
            if (ctor == null)
            {
                //参数个数最多
                ctor = type.GetConstructors().OrderByDescending(c => c.GetParameters().Length).First();
            }
            //ctor = type.GetConstructors()[0]; //直接第一个

            #endregion 选择合适的构造函数

            #region 准备构造函数的参数

            List<object> paraList = new List<object>();
            object[] paraConstant = this._tianYaContainerValueDictionary.ContainsKey(key) ? this._tianYaContainerValueDictionary[key] : null; //常量找出来
            int iIndex = 0;
            foreach (var para in ctor.GetParameters())
            {
                if (para.IsDefined(typeof(ParameterConstantAttribute), true))
                {
                    paraList.Add(paraConstant[iIndex]);
                    iIndex++;
                }
                else
                {
                    Type paraType = para.ParameterType; //获取参数的类型
                    string paraShortName = this.GetShortName(para);
                    object paraInstance = this.ResolveObject(paraType, paraShortName);
                    paraList.Add(paraInstance);
                }
            }

            #endregion 准备构造函数的参数

            object oInstance = null;
            oInstance = Activator.CreateInstance(type, paraList.ToArray()); //创建对象,完成构造函数的注入

            #region 属性注入

            foreach (var prop in type.GetProperties().Where(p => p.IsDefined(typeof(PropertyInjectionAttribute), true)))
            {
                Type propType = prop.PropertyType;
                string paraShortName = this.GetShortName(prop);
                object propInstance = this.ResolveObject(propType, paraShortName);
                prop.SetValue(oInstance, propInstance);
            }

            #endregion 属性注入

            #region 方法注入

            foreach (var method in type.GetMethods().Where(m => m.IsDefined(typeof(MethodInjectionAttribute), true)))
            {
                List<object> paraInjectionList = new List<object>();
                foreach (var para in method.GetParameters())
                {
                    Type paraType = para.ParameterType;//获取参数的类型 IUserDAL
                    string paraShortName = this.GetShortName(para);
                    object paraInstance = this.ResolveObject(paraType, paraShortName);
                    paraInjectionList.Add(paraInstance);
                }
                method.Invoke(oInstance, paraInjectionList.ToArray());
            }

            #endregion 方法注入

            #region 生命周期

            switch (model.Lifetime)
            {
                case LifetimeType.Transient:
                    Console.WriteLine("Transient Do Nothing After");
                    break;
                case LifetimeType.Singleton:
                    model.SingletonInstance = oInstance;
                    break;
                case LifetimeType.Scope:
                    this._tianYaContainerScopeDictionary[key] = oInstance;
                    break;
                default:
                    break;
            }

            #endregion 生命周期

            //return oInstance.AOP(abstractType); //AOP扩展
            return oInstance;
        }

        /// <summary>
        /// 获取简称(别名)
        /// </summary>
        private string GetShortName(ICustomAttributeProvider provider)
        {
            if (provider.IsDefined(typeof(ParameterShortNameAttribute), true))
            {
                var attribute = (ParameterShortNameAttribute)(provider.GetCustomAttributes(typeof(ParameterShortNameAttribute), true)[0]);
                return attribute.ShortName;
            }
            else
            {
                return null;
            }
        }
    }
}

至此,我们就创建完了一个简易的IOC容器。

下面我们来添加一些用于测试的接口,如下所示:

using System;

namespace MyIOCDI.IService
{
    public interface ITestServiceA
    {
        void Show();
    }
}
using System;

namespace MyIOCDI.IService
{
    public interface ITestServiceB
    {
        void Show();
    }
}
using System;

namespace MyIOCDI.IService
{
    public interface ITestServiceC
    {
        void Show();
    }
}
using System;

namespace MyIOCDI.IService
{
    public interface ITestServiceD
    {
        void Show();
    }
}

接口对应的实现,如下所示:

using System;

using MyIOCDI.IService;

namespace MyIOCDI.Service
{
    public class TestServiceA : ITestServiceA
    {
        public TestServiceA()
        {
            Console.WriteLine($"{this.GetType().Name}被构造。。。");
        }

        public void Show()
        {
            Console.WriteLine($"This is {this.GetType().Name} Show");
        }
    }
}
using System;

using MyIOCDI.IService;

namespace MyIOCDI.Service
{
    public class TestServiceB : ITestServiceB
    {
        public TestServiceB()
        {
            Console.WriteLine($"{this.GetType().Name}被构造。。。");
        }

        public void Show()
        {
            Console.WriteLine($"This is {this.GetType().Name} Show");
        }
    }
}
using System;

using MyIOCDI.IService;

namespace MyIOCDI.Service
{
    public class TestServiceC : ITestServiceC
    {
        public TestServiceC()
        {
            Console.WriteLine($"{this.GetType().Name}被构造。。。");
        }

        public void Show()
        {
            Console.WriteLine($"This is {this.GetType().Name} Show");
        }
    }
}
using System;

using MyIOCDI.IService;
using TianYaSharpCore.IOCDI.CustomAttribute;

namespace MyIOCDI.Service
{
    public class TestServiceD : ITestServiceD
    {
        /// <summary>
        /// 属性注入
        /// </summary>
        [PropertyInjection]
        public ITestServiceA TestServiceA { get; set; }

        /// <summary>
        /// 带有别名的属性注入
        /// </summary>
        [ParameterShortName("ServiceB")]
        [PropertyInjection]
        public ITestServiceB TestServiceB { get; set; }

        public TestServiceD()
        {
            Console.WriteLine($"{this.GetType().Name}被构造。。。");
        }

        #region 构造函数注入

        private readonly ITestServiceA _testServiceA;
        private readonly ITestServiceB _testServiceB;
        [ConstructorInjection] //优先选择带有构造函数注入特性的
        public TestServiceD(ITestServiceA testServiceA, [ParameterConstant] string sValue, ITestServiceB testServiceB, [ParameterConstant] int iValue)
        {
            Console.WriteLine($"{this.GetType().Name}--{sValue}--{iValue}被构造。。。");
            _testServiceA = testServiceA;
            _testServiceB = testServiceB;
        }

        #endregion 构造函数注入

        #region 方法注入

        private ITestServiceC _testServiceC;
        [MethodInjection]
        public void Init(ITestServiceC testServiceC)
        {
            _testServiceC = testServiceC;
        }

        #endregion 方法注入

        public void Show()
        {
            Console.WriteLine($"This is {this.GetType().Name} Show");
        }
    }
}

最后来看下IOC容器的使用及其运行结果:

using System;

using TianYaSharpCore.IOCDI.CustomContainer;
using MyIOCDI.IService;
using MyIOCDI.Service;

namespace MyIOCDI
{
    class Program
    {
        static void Main(string[] args)
        {
            ITianYaIOCContainer container = new TianYaIOCContainer();
            {
                //注册
                container.Register<ITestServiceA, TestServiceA>(); //将ITestServiceA注册到TestServiceA
                container.Register<ITestServiceB, TestServiceB>();
                container.Register<ITestServiceB, TestServiceB>(shortName: "ServiceB");
                container.Register<ITestServiceC, TestServiceC>();
                container.Register<ITestServiceD, TestServiceD>(paraList: new object[] { "浪子天涯", 666 }, lifetimeType: LifetimeType.Singleton);

                ITestServiceD d1 = container.Resolve<ITestServiceD>(); //创建对象交给IOC容器
                ITestServiceD d2 = container.Resolve<ITestServiceD>();
                d1.Show();
                Console.WriteLine($"object.ReferenceEquals(d1, d2) = {object.ReferenceEquals(d1, d2)}");
            }

            Console.ReadKey();
        }
    }
}

运行结果如下:

IjEzYz3.png!mobile

生命周期为作用域的,其实就是子容器单例,如下所示:

using System;

using TianYaSharpCore.IOCDI.CustomContainer;
using MyIOCDI.IService;
using MyIOCDI.Service;

namespace MyIOCDI
{
    class Program
    {
        static void Main(string[] args)
        {
            ITianYaIOCContainer container = new TianYaIOCContainer();
            //{
            //    //注册
            //    container.Register<ITestServiceA, TestServiceA>(); //将ITestServiceA注册到TestServiceA
            //    container.Register<ITestServiceB, TestServiceB>();
            //    container.Register<ITestServiceB, TestServiceB>(shortName: "ServiceB");
            //    container.Register<ITestServiceC, TestServiceC>();
            //    container.Register<ITestServiceD, TestServiceD>(paraList: new object[] { "浪子天涯", 666 }, lifetimeType: LifetimeType.Singleton);

            //    ITestServiceD d1 = container.Resolve<ITestServiceD>(); //创建对象交给IOC容器
            //    ITestServiceD d2 = container.Resolve<ITestServiceD>();
            //    d1.Show();
            //    Console.WriteLine($"object.ReferenceEquals(d1, d2) = {object.ReferenceEquals(d1, d2)}");
            //}

            {
                //生命周期:作用域
                //就是Http请求时,一个请求处理过程中,创建都是同一个实例;不同的请求处理过程中,就是不同的实例;
                //得区分请求,Http请求---Asp.NetCore内置Kestrel,初始化一个容器实例;然后每次来一个Http请求,就clone一个,
                //或者叫创建子容器(包含注册关系),然后一个请求就一个子容器实例,那么就可以做到请求单例了(其实就是子容器单例)
                //主要可以去做DbContext  Repository
                container.Register<ITestServiceA, TestServiceA>(lifetimeType: LifetimeType.Scope);
                ITestServiceA a1 = container.Resolve<ITestServiceA>();
                ITestServiceA a2 = container.Resolve<ITestServiceA>();

                Console.WriteLine(object.ReferenceEquals(a1, a2)); //T

                ITianYaIOCContainer container1 = container.CreateChildContainer();
                ITestServiceA a11 = container1.Resolve<ITestServiceA>();
                ITestServiceA a12 = container1.Resolve<ITestServiceA>();

                ITianYaIOCContainer container2 = container.CreateChildContainer();
                ITestServiceA a21 = container2.Resolve<ITestServiceA>();
                ITestServiceA a22 = container2.Resolve<ITestServiceA>();

                Console.WriteLine(object.ReferenceEquals(a11, a12)); //T
                Console.WriteLine(object.ReferenceEquals(a21, a22)); //T

                Console.WriteLine(object.ReferenceEquals(a11, a21)); //F
                Console.WriteLine(object.ReferenceEquals(a11, a22)); //F
                Console.WriteLine(object.ReferenceEquals(a12, a21)); //F
                Console.WriteLine(object.ReferenceEquals(a12, a22)); //F
            }

            Console.ReadKey();
        }
    }
}

运行结果如下:

bQf6z2u.png!mobile

至此本文就全部介绍完了,如果觉得对您有所启发请记得点个赞哦!!!

Demo源码:

链接:https://pan.baidu.com/s/15xpmWbEDbkm7evpr4iIZNg
提取码:ckes

B7nYBzr.jpg!mobile

往期 精彩 回顾

【推荐】.NET Core开发实战视频课程   ★★★

Redis基本使用及百亿数据量中的使用技巧分享(附视频地址及观看指南)

.NET Core中的一个接口多种实现的依赖注入与动态选择看这篇就够了

10个小技巧助您写出高性能的ASP.NET Core代码

现身说法:实际业务出发分析百亿数据量下的多表查询优化

关于C#异步编程你应该了解的几点建议

C#异步编程看这篇就够了


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK