手寫IOC實踐


一、IOC

1.什么是IOC?

控制反轉(英語:Inversion of Control,縮寫為IoC),是[面向對象編程]中的一種設計原則,可以用來減低計算機代碼之間的[耦合度]其中最常見的方式叫做依賴注入(Dependency Injection,簡稱DI),還有一種方式叫“依賴查找”(Dependency Lookup).

IoC:是一種設計模式

DI:是踐行控制反轉思想的一種方式

2.為什么要用IOC

因為IoC 控制反轉是依賴抽象,而抽象是穩定的,不依賴細節,因為細節還可能會依賴其他細節,為了屏蔽細節,需要使用依賴注入去解決無限層級的對象依賴。

3.Net中常用的IoC容器

目前用的最多的是AutoFac和Castle,在.Net Core中框架內置了IOC容器,Unity和ObjectBuilder是相對比較久遠的框架,用的比較少。

  1. AutoFac

  2. Castle

  3. Unity

  4. ObjectBuilder

二、如何手寫實現?

1.基本設計

核心思想: 工廠 + 反射

首先我們想自己實現一個IoC容器其實並不難,我們在使用現有的IoC容器都知道,在使用前,我們需要先注冊,然后才能使用

所以我們將工廠換成手動注冊的方式,因為寫一大堆if else 或者switch也不太美觀,根據主流IoC的使用方式來以葫蘆畫瓢,如果后期繼續完善功能加入程序集注入的話,還是得實現一個工廠,來省略手動注冊

但是這次目標是實現一個簡易版的IoC容器,我們先實現基礎功能,待后面一步一步去完善,再加入一些新的功能,即我們不考慮性能或者擴展度,目的是循序漸進,在寫之前我們先整理出 實現步驟和實現方式

  1. 方便接入和擴展,我們在這先定義一個容器接口 IManualContainer

  2. 定義ManualContainer繼承實現IManualContainer

  3. 聲明一個靜態字典對象存儲注冊的對象

  4. 利用反射構建對象,考慮到性能可以加入Expression或者Emit的方式來做一些優化

classDiagram IManualContainer <|-- ManualContainer IManualContainer: +Register<TFrom, To>() IManualContainer: +Resolve<Tinterface>() class ManualContainer{ -Dictionary<string, Type> container +Register() +Resolve() -CreateInstance() }
public interface IManualContainer
{
      void Register<TFrom, To>(string servicesName = null) where To : TFrom;

      Tinterface Resolve<Tinterface>(string servicesName = null);
}
2.要實現的功能

1.基本對象構造

2.構造函數注入

3.多級依賴和多構造函數及自定義注入

4.屬性注入&方法注入

5.單接口多實現

三、編碼實現及思路剖析

1.實現構造對象(單接口注入)

1.首先實現接口來進行編碼私有字段 container用來存儲注冊的類型,key是對應接口的完整名稱,Value是需要Resolve的類型。

2.泛型約束保證需要被Resolve類型 (To) 實現或者繼承自注冊類型 (TFrom)

public class ManualContainer : IManualContainer
{
   //存儲注冊類型
   private static Dictionary<string, Type> container = 
   new Dictionary<string, Type>();
   
   //注冊
    public void Register<TFrom, To>(string servicesName = null) where To : TFrom
    {
        string Key = $"{typeof(TFrom).FullName}{servicesName}";
        if (!container.ContainsKey(Key))
        {
            container.Add(Key, typeof(To));
        }
    }

1.實現構造對象,首先需要傳入被構造的類型的抽象接口T

2.在Resolve中根據T作為Key,在存儲容器中找到注冊時映射的類型,並通過反射構造對象

   //構建對象
   public TFrom Resolve<TFrom>(string servicesName = null)
   {
       string Key = $"{typeof(TFrom).FullName}{servicesName}";
       container.TryGetValue(key, out Type target);
       if(target is null)
       {
           return default(TFrom);
       }
       object t = Activator.CreateInstance(target);
   }
}

1.首先我們准備需要的接口(ITestA)和實例(TestA)來利用容器來構造對象

public interface ITestA
{
    void Run();
}
public class TestA : ITestA
{
   public void Run()=> Console.WriteLine("這是接口ITestA的實現");
}

2.調用IoC容器來創建對象

IManualContainer container = new ManualContainer();
//注冊到容器中
container.Register<ITestA, TestA>();
ITestA instance = container.Resolve<ITestA>();
instance.Run();
//out put "這是接口ITestA的實現"
2.構造函數注入

1.假設我們的TestA類中需要ITestB接口的實例或者其他更多類型的實例,並且需要通過構造函數注入,我們應該如何去完善我們的IoC容器呢?

public class TestA : ITestA
{ 
   private ITestB testB = null;
   //構造函數
   public TestA(ITestB testB)=> this.testB = testB;
   
   public void Run()
   {
      this.testB.Run();
      Console.WriteLine("這是接口ITestA的實現");
   }
}

2.我們按照上面的步驟照常注冊和構造對象,發現報錯了,在Resolve ()的時候,經過調試知道是使用反射構造的時候報錯了,因為在構造TestA缺少構造參數,那么我們就需要在反射構造時加入參數。

  1. 先定義List<object>集合存儲對象構造時需要的參數列表

  2. 通過需要被實例的目標類型找到類中的構造函數,暫不考慮多構造函數case

  3. 找到構造函數參數及類型,然后創建參數的實例加入List中,在反射構造時傳入參數就解決了

   //完善Resolve構建對象函數
   public TFrom Resolve<TFrom>(string servicesName = null)
   {
       string Key = $"{typeof(TFrom).FullName}{servicesName}";
       container.TryGetValue(key, out Type target);
       if(target is null)
       {
           return default(TFrom);
       }
       
       //存儲參數列表
       List<object> paramList = new List<object>();
       //找到目標類型的構造函數,暫不考慮多構造函數case
       var ctor = target.GetConstructors().FirstOrDefault();
       //找到參數列表
       var ctorParams = ctor.GetParameters();
       foreach (var item in ctorParams)
       {
           //參數類型
           Type paramType = item.ParameterType;
           string paramKey = paramType.FullName;
           //找到參數注冊時映射的實例
           container.TryGetValue(paramKey, out Type ParamType);
           //構造出實例然后加入參數列表
           paramList.Add(Activator.CreateInstance(ParamType));
       }     
       object t = Activator.CreateInstance(target,paramList);
   }
}
3.多級依賴(遞歸)

根據上面我們目前實現的結果來看,這是解決了構造函數和多參數注入以及基本的構造對象問題,那現在問題又來了

  1. 如果是很多層的依賴該怎么辦?

  2. 例如多個構造函數怎么辦呢?

  3. 在多個構造函數中用戶想自定義需要被注入的構造函數怎么辦?

總結3點問題

  • 1.多級依賴問題

例如ITestB 的實例中依賴ITestC,一直無限依賴我們怎么解決呢?毫無疑問,做同樣的事情,但是要無限做下去,就使用 遞歸,下面我們來改造我們的方法

  • 2.多個構造函數

    1. 取參數最多的方式注入 (AutoFac)

    2. 取並集方式注入 (ASP .NET Core)

  • 3.自定義注入

    我們可以使用特性標記的方式來實現,用戶在需要被選擇注入的構造函數上加入特性標簽來完成


1.創建私有遞歸方法,這個方法的作用就是創建對象用

private object CreateInstance(Type type,string serviceName = null)
{
}

2.我們選擇第一種方式實現,修改之前獲取第一個構造函數的代碼,選擇最多參數注入

 //找到目標類型的構造函數,找參數最多的構造函數
  ConstructorInfo ctor = null;
  var ctors = target.GetConstructors();
  ctor = ctors.OrderByDescending(x => x.GetParameters().Length).First();

3.自定義特性CtorInjection,可以使用戶自定義選擇

 //自定義構造函數注入標記
 [AttributeUsage(AttributeTargets.Constructor)]
 public class CtorInjectionAttribute : Attribute
 {
 }

4.最終代碼

private object CreateInstance(Type type,string serviceName = null)
{
   string key = $"{ type.FullName }{serviceName}";
   container.TryGetValue(key, out Type target);
   //存儲參數列表
   List<object> paramList = new List<object>();
  
   ConstructorInfo ctor = null;
   //找到被特性標記的構造函數作為注入目標
   ctor = target.GetConstructors().FirstOrDefault(x => x.IsDefined(typeof(CtorInjectionAttribute), true));

   //如果沒有被特性標記,那就取構造函數參數最多的作為注入目標
   if (ctor is null)
   {
      ctor = target.GetConstructors().OrderByDescending(x => x.GetParameters().Length).First();
   }
   //找到參數列表
   var ctorParams = ctor.GetParameters();
   foreach (var item in ctorParams)
   {
       //參數類型
       Type paramType = item.ParameterType;
       //遞歸調用構建對象
       object paramInstance = CreateInstance(paramType);
       //構造出實例然后加入參數列表
       paramList.Add(paramInstance);
   }    
   object t = Activator.CreateInstance(target);
   return t;
}

 public TFrom Resolve<TFrom>() 
 {
    return (TFrom)this.CreateInstance(typeof(TFrom));
 }
4.屬性注入&方法注入

1.自定義特性PropInjection,可以使用戶自定義選擇

 //自定義構造屬性注入標記
 [AttributeUsage(AttributeTargets.Property)]
 public class PropInjectionAttribute : Attribute
 {
 }
  //自定義構造方法注入標記
 [AttributeUsage(AttributeTargets.Method)]
 public class MethodInjectionAttribute : Attribute
 {
 }

2.遍歷實例中被標記特性的屬性。

3.獲取屬性的類型,調用遞歸構造對象函數。

4.設置目標對象的屬性值。

5.方法注入也是同理,換湯不換葯而已

private object CreateInstance(Type type,string serviceName = null)
{
  string key = $"{ type.FullName }{serviceName}";
   container.TryGetValue(key, out Type target);
   //存儲參數列表
   List<object> paramList = new List<object>();
  
   ConstructorInfo ctor = null;
   //找到被特性標記的構造函數作為注入目標
   ctor = target.GetConstructors().FirstOrDefault(x => x.IsDefined(typeof(CtorInjectionAttribute), true));

   //如果沒有被特性標記,那就取構造函數參數最多的作為注入目標
   if (ctor is null)
   {
      ctor = target.GetConstructors().OrderByDescending(x => x.GetParameters().Length).First();
   }
   //找到參數列表
   var ctorParams = ctor.GetParameters();
   foreach (var item in ctorParams)
   {
       //參數類型
       Type paramType = item.ParameterType;
       //遞歸調用構建對象
       object paramInstance = CreateInstance(paramType);
       //構造出實例然后加入參數列表
       paramList.Add(paramInstance);
   }    
   object t = Activator.CreateInstance(target);
   
   //獲取目標類型的被特性標記的屬性<屬性注入>
   var propetys = target.GetProperties().Where(x => x.IsDefined(typeof(PropInjectionAttribute), true));
   foreach (var item in propetys) 
   {
       //獲取屬性類型
       Type propType = item.PropertyType;
       object obj = this.CreateInstance(propType);
       //設置值
       item.SetValue(t, obj);
   }
   
   //獲取目標類型的被特性標記的方法<方法注入>
   var methods = target.GetMethods().Where(x => x.IsDefined(typeof(MethodInjectionAttribute), true)).ToList();
   foreach (var item in methods)
    {
        List<object> methodParams = new List<object>();
        foreach (var p in item.GetParameters())
        { 
            //獲取方法參數類型
            Type propType = p.ParameterType;
            object obj = this.CreateInstance(propType);
            methodParams.Add(obj);
        }
        item.Invoke(t, methodParams);
    }
   return t;
}

 public TFrom Resolve<TFrom>() 
 {
    return (TFrom)this.CreateInstance(typeof(TFrom));
 }
5.單接口多實現

假設我們一個接口被多個實例實現,我們需要注入怎么操作呢?

毫無疑問我們應該想到在注入時取一個別名,沒錯正是這種方法,但是也會存在一個問題,我們取了別名之后只能解決注入的場景?那依賴接口的地方如何知道注入哪一個實例呢?

1.注冊單接口多實例

container.Register<ITest, test1>("test1");
container.Register<ITest, test2>("test2");
ITest instance = container.Resolve<ITest>("test1");
ITest instance1 = container.Resolve<ITest>("test2");

2.創建標記特性,用來標記目標對象

[AttributeUsage(AttributeTargets.Parameter| AttributeTargets.Property)]
public class ParamterInjectAttribute : Attribute
{
   public ParamterInjectAttribute(string nickName) => NickName = nickName;
   public string NickName { get; private set; }
}

3.我們需要在依賴“單接口多實例的類中”使用時告訴參數,我們需要的實例,依然使用參數特性標記

public class use : IUse
{
    private ITest _Test = null;
    //告訴構造函數依賴ITest接口時使用別名為test1的實例
    public BLL1([ParamterInjectAttribute("test1")] ITest接口時使用別名為test1的實例 Test)
    {
        this._Test = Test;
    }
}

4.在構造對象時查找是否被特性標記,然后構造對象

foreach (var item in ctor.GetParameters())
{
    Type paramType = item.ParameterType;
    string nickName = string.Empty;
    //查找構造函數的參數是否被特性標記
    if (item.IsDefined(typeof(ParamterInjectAttribute), true))
    {
    //找到被標記需要構造的實例別名
        nickName = item.GetCustomAttribute<ParamterInjectAttribute>().NickName;
    }
    //根據別名創建對象
    object instance = CreateInstance(paramType,nickName);
    if (instance != null)
    {
        paramList.Add(instance);
    }
 }

四、總結

目前還不是很完善,只是實現了屬性,方法,以及構造函數注入,很多必要功能還沒有,下一步將在現有代碼基礎上利用Emit的方式來創建對象,加入基本的驗證環節以提高健壯性,加入生命周期管理,和AOP擴展。

第一版最終代碼

 public class ManualContainer : IManualContainer
 {
        private static Dictionary<string, Type> container = new Dictionary<string, Type>();
        public void Register<TFrom, To>(string servicesName = null) where To : TFrom
        {
            string Key = $"{typeof(TFrom).FullName}{servicesName}";
            if (!container.ContainsKey(Key))
            {
                container.Add(Key, typeof(To));
            }
        }

        public Tinterface Resolve<Tinterface>(string serviceName = null)
        {
            return (Tinterface)this.CreateInstance(typeof(Tinterface), serviceName);
        }

        private object CreateInstance(Type type, string serviceName = null)
        {
            string key = $"{ type.FullName }{serviceName}";
            container.TryGetValue(key, out Type target);
            object instance = ctorInjection(target);
            propInjection(target, instance);
            methodInjection(target, instance);
            return instance;
        }


        /// <summary>
        /// 構造函數注入
        /// </summary>
        /// <param name="target">需要被創建的類型</param>
        /// <returns>被創建的實例</returns>
        private object ctorInjection(Type targetType)
        {
            List<object> paramList = new List<object>();
            ConstructorInfo ctor = null;
            ctor = targetType.GetConstructors().FirstOrDefault(x => x.IsDefined(typeof(CtorInjectionAttribute), true));
            if (ctor is null)
            {
                ctor = targetType.GetConstructors().OrderByDescending(x => x.GetParameters().Length).First();
            }
            foreach (var item in ctor.GetParameters())
            {
                Type paramType = item.ParameterType;
                string nickName = GetNickNameByAttribute(item);
                object instance = CreateInstance(paramType, nickName);
                if (instance != null)
                {
                    paramList.Add(instance);
                }
            }
            object t = Activator.CreateInstance(targetType, paramList.ToArray());
            return t;
        }


        /// <summary>
        /// 屬性注入
        /// </summary>
        /// <param name="targetType">需要被創建的類型</param>
        /// <param name="sourceType">根據需要被創建的類型構造出的實例</param>
        private void propInjection(Type targetType, object inststance)
        {
            var propetys = targetType.GetProperties().Where(x => x.IsDefined(typeof(PropInjectionAttribute), true));
            foreach (var item in propetys)
            {
                Type propType = item.PropertyType;
                string nickName = GetNickNameByAttribute(item);
                object obj = this.CreateInstance(propType, nickName);
                item.SetValue(inststance, obj);
            }
        }

        /// <summary>
        /// 方法注入
        /// </summary>
        /// <param name="targetType">需要被創建的類型</param>
        /// <param name="sourceType">根據需要被創建的類型構造出的實例</param>
        private void methodInjection(Type targetType, object inststance)
        {
            List<object> methodParams = new List<object>();
            var methods = targetType.GetMethods().Where(x => x.IsDefined(typeof(MethodInjectionAttribute), true)).ToList();
            foreach (var item in methods)
            {
                foreach (var p in item.GetParameters())
                {
                    Type propType = p.ParameterType;
                    string nickName = GetNickNameByAttribute(item);
                    object obj = this.CreateInstance(propType, nickName);
                    methodParams.Add(obj);
                }
                item.Invoke(inststance, methodParams.ToArray());
            }
        }
        
        private string GetNickNameByAttribute(ICustomAttributeProvider provider)
        {
            if (provider.IsDefined(typeof(ParamterInjectAttribute), true))
            {
                ParamterInjectAttribute attribute = provider.GetCustomAttributes(typeof(ParamterInjectAttribute), true)[0] as ParamterInjectAttribute;
                return attribute.NickName;
            }
            return string.Empty;
        }
  }


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM