一、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()
}
  1. public interface IManualContainer
  2. {
  3. void Register<TFrom, To>(string servicesName = null) where To : TFrom;
  4. Tinterface Resolve<Tinterface>(string servicesName = null);
  5. }
2.要实现的功能

1.基本对象构造

2.构造函数注入

3.多级依赖和多构造函数及自定义注入

4.属性注入&方法注入

5.单接口多实现

三、编码实现及思路剖析

1.实现构造对象(单接口注入)

1.首先实现接口来进行编码私有字段 container用来存储注册的类型,key是对应接口的完整名称,Value是需要Resolve的类型。

2.泛型约束保证需要被Resolve类型 (To) 实现或者继承自注册类型 (TFrom)

  1. public class ManualContainer : IManualContainer
  2. {
  3. //存储注册类型
  4. private static Dictionary<string, Type> container =
  5. new Dictionary<string, Type>();
  6. //注册
  7. public void Register<TFrom, To>(string servicesName = null) where To : TFrom
  8. {
  9. string Key = $"{typeof(TFrom).FullName}{servicesName}";
  10. if (!container.ContainsKey(Key))
  11. {
  12. container.Add(Key, typeof(To));
  13. }
  14. }

1.实现构造对象,首先需要传入被构造的类型的抽象接口T

2.在Resolve中根据T作为Key,在存储容器中找到注册时映射的类型,并通过反射构造对象

  1. //构建对象
  2. public TFrom Resolve<TFrom>(string servicesName = null)
  3. {
  4. string Key = $"{typeof(TFrom).FullName}{servicesName}";
  5. container.TryGetValue(key, out Type target);
  6. if(target is null)
  7. {
  8. return default(TFrom);
  9. }
  10. object t = Activator.CreateInstance(target);
  11. }
  12. }

1.首先我们准备需要的接口(ITestA)和实例(TestA)来利用容器来构造对象

  1. public interface ITestA
  2. {
  3. void Run();
  4. }
  5. public class TestA : ITestA
  6. {
  7. public void Run()=> Console.WriteLine("这是接口ITestA的实现");
  8. }

2.调用IoC容器来创建对象

  1. IManualContainer container = new ManualContainer();
  2. //注册到容器中
  3. container.Register<ITestA, TestA>();
  4. ITestA instance = container.Resolve<ITestA>();
  5. instance.Run();
  6. //out put "这是接口ITestA的实现"
2.构造函数注入

1.假设我们的TestA类中需要ITestB接口的实例或者其他更多类型的实例,并且需要通过构造函数注入,我们应该如何去完善我们的IoC容器呢?

  1. public class TestA : ITestA
  2. {
  3. private ITestB testB = null;
  4. //构造函数
  5. public TestA(ITestB testB)=> this.testB = testB;
  6. public void Run()
  7. {
  8. this.testB.Run();
  9. Console.WriteLine("这是接口ITestA的实现");
  10. }
  11. }

2.我们按照上面的步骤照常注册和构造对象,发现报错了,在Resolve()的时候,经过调试知道是使用反射构造的时候报错了,因为在构造TestA缺少构造参数,那么我们就需要在反射构造时加入参数。

  1. 先定义List<object>集合存储对象构造时需要的参数列表

  2. 通过需要被实例的目标类型找到类中的构造函数,暂不考虑多构造函数case

  3. 找到构造函数参数及类型,然后创建参数的实例加入List中,在反射构造时传入参数就解决了

  1. //完善Resolve构建对象函数
  2. public TFrom Resolve<TFrom>(string servicesName = null)
  3. {
  4. string Key = $"{typeof(TFrom).FullName}{servicesName}";
  5. container.TryGetValue(key, out Type target);
  6. if(target is null)
  7. {
  8. return default(TFrom);
  9. }
  10. //存储参数列表
  11. List<object> paramList = new List<object>();
  12. //找到目标类型的构造函数,暂不考虑多构造函数case
  13. var ctor = target.GetConstructors().FirstOrDefault();
  14. //找到参数列表
  15. var ctorParams = ctor.GetParameters();
  16. foreach (var item in ctorParams)
  17. {
  18. //参数类型
  19. Type paramType = item.ParameterType;
  20. string paramKey = paramType.FullName;
  21. //找到参数注册时映射的实例
  22. container.TryGetValue(paramKey, out Type ParamType);
  23. //构造出实例然后加入参数列表
  24. paramList.Add(Activator.CreateInstance(ParamType));
  25. }
  26. object t = Activator.CreateInstance(target,paramList);
  27. }
  28. }
3.多级依赖(递归)

根据上面我们目前实现的结果来看,这是解决了构造函数和多参数注入以及基本的构造对象问题,那现在问题又来了

  1. 如果是很多层的依赖该怎么办?

  2. 例如多个构造函数怎么办呢?

  3. 在多个构造函数中用户想自定义需要被注入的构造函数怎么办?

总结3点问题

  • 1.多级依赖问题

例如ITestB 的实例中依赖ITestC,一直无限依赖我们怎么解决呢?毫无疑问,做同样的事情,但是要无限做下去,就使用 递归,下面我们来改造我们的方法

  • 2.多个构造函数

    1. 取参数最多的方式注入 (AutoFac)

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

  • 3.自定义注入

    我们可以使用特性标记的方式来实现,用户在需要被选择注入的构造函数上加入特性标签来完成


1.创建私有递归方法,这个方法的作用就是创建对象用

  1. private object CreateInstance(Type type,string serviceName = null)
  2. {
  3. }

2.我们选择第一种方式实现,修改之前获取第一个构造函数的代码,选择最多参数注入

  1. //找到目标类型的构造函数,找参数最多的构造函数
  2. ConstructorInfo ctor = null;
  3. var ctors = target.GetConstructors();
  4. ctor = ctors.OrderByDescending(x => x.GetParameters().Length).First();

3.自定义特性CtorInjection,可以使用户自定义选择

  1. //自定义构造函数注入标记
  2. [AttributeUsage(AttributeTargets.Constructor)]
  3. public class CtorInjectionAttribute : Attribute
  4. {
  5. }

4.最终代码

  1. private object CreateInstance(Type type,string serviceName = null)
  2. {
  3. string key = $"{ type.FullName }{serviceName}";
  4. container.TryGetValue(key, out Type target);
  5. //存储参数列表
  6. List<object> paramList = new List<object>();
  7. ConstructorInfo ctor = null;
  8. //找到被特性标记的构造函数作为注入目标
  9. ctor = target.GetConstructors().FirstOrDefault(x => x.IsDefined(typeof(CtorInjectionAttribute), true));
  10. //如果没有被特性标记,那就取构造函数参数最多的作为注入目标
  11. if (ctor is null)
  12. {
  13. ctor = target.GetConstructors().OrderByDescending(x => x.GetParameters().Length).First();
  14. }
  15. //找到参数列表
  16. var ctorParams = ctor.GetParameters();
  17. foreach (var item in ctorParams)
  18. {
  19. //参数类型
  20. Type paramType = item.ParameterType;
  21. //递归调用构建对象
  22. object paramInstance = CreateInstance(paramType);
  23. //构造出实例然后加入参数列表
  24. paramList.Add(paramInstance);
  25. }
  26. object t = Activator.CreateInstance(target);
  27. return t;
  28. }
  29. public TFrom Resolve<TFrom>()
  30. {
  31. return (TFrom)this.CreateInstance(typeof(TFrom));
  32. }
4.属性注入&方法注入

1.自定义特性PropInjection,可以使用户自定义选择

  1. //自定义构造属性注入标记
  2. [AttributeUsage(AttributeTargets.Property)]
  3. public class PropInjectionAttribute : Attribute
  4. {
  5. }
  6. //自定义构造方法注入标记
  7. [AttributeUsage(AttributeTargets.Method)]
  8. public class MethodInjectionAttribute : Attribute
  9. {
  10. }

2.遍历实例中被标记特性的属性。

3.获取属性的类型,调用递归构造对象函数。

4.设置目标对象的属性值。

5.方法注入也是同理,换汤不换药而已

  1. private object CreateInstance(Type type,string serviceName = null)
  2. {
  3. string key = $"{ type.FullName }{serviceName}";
  4. container.TryGetValue(key, out Type target);
  5. //存储参数列表
  6. List<object> paramList = new List<object>();
  7. ConstructorInfo ctor = null;
  8. //找到被特性标记的构造函数作为注入目标
  9. ctor = target.GetConstructors().FirstOrDefault(x => x.IsDefined(typeof(CtorInjectionAttribute), true));
  10. //如果没有被特性标记,那就取构造函数参数最多的作为注入目标
  11. if (ctor is null)
  12. {
  13. ctor = target.GetConstructors().OrderByDescending(x => x.GetParameters().Length).First();
  14. }
  15. //找到参数列表
  16. var ctorParams = ctor.GetParameters();
  17. foreach (var item in ctorParams)
  18. {
  19. //参数类型
  20. Type paramType = item.ParameterType;
  21. //递归调用构建对象
  22. object paramInstance = CreateInstance(paramType);
  23. //构造出实例然后加入参数列表
  24. paramList.Add(paramInstance);
  25. }
  26. object t = Activator.CreateInstance(target);
  27. //获取目标类型的被特性标记的属性<属性注入>
  28. var propetys = target.GetProperties().Where(x => x.IsDefined(typeof(PropInjectionAttribute), true));
  29. foreach (var item in propetys)
  30. {
  31. //获取属性类型
  32. Type propType = item.PropertyType;
  33. object obj = this.CreateInstance(propType);
  34. //设置值
  35. item.SetValue(t, obj);
  36. }
  37. //获取目标类型的被特性标记的方法<方法注入>
  38. var methods = target.GetMethods().Where(x => x.IsDefined(typeof(MethodInjectionAttribute), true)).ToList();
  39. foreach (var item in methods)
  40. {
  41. List<object> methodParams = new List<object>();
  42. foreach (var p in item.GetParameters())
  43. {
  44. //获取方法参数类型
  45. Type propType = p.ParameterType;
  46. object obj = this.CreateInstance(propType);
  47. methodParams.Add(obj);
  48. }
  49. item.Invoke(t, methodParams);
  50. }
  51. return t;
  52. }
  53. public TFrom Resolve<TFrom>()
  54. {
  55. return (TFrom)this.CreateInstance(typeof(TFrom));
  56. }
5.单接口多实现

假设我们一个接口被多个实例实现,我们需要注入怎么操作呢?

毫无疑问我们应该想到在注入时取一个别名,没错正是这种方法,但是也会存在一个问题,我们取了别名之后只能解决注入的场景?那依赖接口的地方如何知道注入哪一个实例呢?

1.注册单接口多实例

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

2.创建标记特性,用来标记目标对象

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

3.我们需要在依赖“单接口多实例的类中”使用时告诉参数,我们需要的实例,依然使用参数特性标记

  1. public class use : IUse
  2. {
  3. private ITest _Test = null;
  4. //告诉构造函数依赖ITest接口时使用别名为test1的实例
  5. public BLL1([ParamterInjectAttribute("test1")] ITest接口时使用别名为test1的实例 Test)
  6. {
  7. this._Test = Test;
  8. }
  9. }

4.在构造对象时查找是否被特性标记,然后构造对象

  1. foreach (var item in ctor.GetParameters())
  2. {
  3. Type paramType = item.ParameterType;
  4. string nickName = string.Empty;
  5. //查找构造函数的参数是否被特性标记
  6. if (item.IsDefined(typeof(ParamterInjectAttribute), true))
  7. {
  8. //找到被标记需要构造的实例别名
  9. nickName = item.GetCustomAttribute<ParamterInjectAttribute>().NickName;
  10. }
  11. //根据别名创建对象
  12. object instance = CreateInstance(paramType,nickName);
  13. if (instance != null)
  14. {
  15. paramList.Add(instance);
  16. }
  17. }

四、总结

目前还不是很完善,只是实现了属性,方法,以及构造函数注入,很多必要功能还没有,下一步将在现有代码基础上利用Emit的方式来创建对象,加入基本的验证环节以提高健壮性,加入生命周期管理,和AOP扩展。

第一版最终代码

  1. public class ManualContainer : IManualContainer
  2. {
  3. private static Dictionary<string, Type> container = new Dictionary<string, Type>();
  4. public void Register<TFrom, To>(string servicesName = null) where To : TFrom
  5. {
  6. string Key = $"{typeof(TFrom).FullName}{servicesName}";
  7. if (!container.ContainsKey(Key))
  8. {
  9. container.Add(Key, typeof(To));
  10. }
  11. }
  12. public Tinterface Resolve<Tinterface>(string serviceName = null)
  13. {
  14. return (Tinterface)this.CreateInstance(typeof(Tinterface), serviceName);
  15. }
  16. private object CreateInstance(Type type, string serviceName = null)
  17. {
  18. string key = $"{ type.FullName }{serviceName}";
  19. container.TryGetValue(key, out Type target);
  20. object instance = ctorInjection(target);
  21. propInjection(target, instance);
  22. methodInjection(target, instance);
  23. return instance;
  24. }
  25. /// <summary>
  26. /// 构造函数注入
  27. /// </summary>
  28. /// <param name="target">需要被创建的类型</param>
  29. /// <returns>被创建的实例</returns>
  30. private object ctorInjection(Type targetType)
  31. {
  32. List<object> paramList = new List<object>();
  33. ConstructorInfo ctor = null;
  34. ctor = targetType.GetConstructors().FirstOrDefault(x => x.IsDefined(typeof(CtorInjectionAttribute), true));
  35. if (ctor is null)
  36. {
  37. ctor = targetType.GetConstructors().OrderByDescending(x => x.GetParameters().Length).First();
  38. }
  39. foreach (var item in ctor.GetParameters())
  40. {
  41. Type paramType = item.ParameterType;
  42. string nickName = GetNickNameByAttribute(item);
  43. object instance = CreateInstance(paramType, nickName);
  44. if (instance != null)
  45. {
  46. paramList.Add(instance);
  47. }
  48. }
  49. object t = Activator.CreateInstance(targetType, paramList.ToArray());
  50. return t;
  51. }
  52. /// <summary>
  53. /// 属性注入
  54. /// </summary>
  55. /// <param name="targetType">需要被创建的类型</param>
  56. /// <param name="sourceType">根据需要被创建的类型构造出的实例</param>
  57. private void propInjection(Type targetType, object inststance)
  58. {
  59. var propetys = targetType.GetProperties().Where(x => x.IsDefined(typeof(PropInjectionAttribute), true));
  60. foreach (var item in propetys)
  61. {
  62. Type propType = item.PropertyType;
  63. string nickName = GetNickNameByAttribute(item);
  64. object obj = this.CreateInstance(propType, nickName);
  65. item.SetValue(inststance, obj);
  66. }
  67. }
  68. /// <summary>
  69. /// 方法注入
  70. /// </summary>
  71. /// <param name="targetType">需要被创建的类型</param>
  72. /// <param name="sourceType">根据需要被创建的类型构造出的实例</param>
  73. private void methodInjection(Type targetType, object inststance)
  74. {
  75. List<object> methodParams = new List<object>();
  76. var methods = targetType.GetMethods().Where(x => x.IsDefined(typeof(MethodInjectionAttribute), true)).ToList();
  77. foreach (var item in methods)
  78. {
  79. foreach (var p in item.GetParameters())
  80. {
  81. Type propType = p.ParameterType;
  82. string nickName = GetNickNameByAttribute(item);
  83. object obj = this.CreateInstance(propType, nickName);
  84. methodParams.Add(obj);
  85. }
  86. item.Invoke(inststance, methodParams.ToArray());
  87. }
  88. }
  89. private string GetNickNameByAttribute(ICustomAttributeProvider provider)
  90. {
  91. if (provider.IsDefined(typeof(ParamterInjectAttribute), true))
  92. {
  93. ParamterInjectAttribute attribute = provider.GetCustomAttributes(typeof(ParamterInjectAttribute), true)[0] as ParamterInjectAttribute;
  94. return attribute.NickName;
  95. }
  96. return string.Empty;
  97. }
  98. }

手写IOC实践的更多相关文章

  1. 手写IOC实现过程

    一.手写ioc前基础知识 1.什么是IOC(Inversion of Control 控制反转)? IoC不是一种技术,只是一种思想,一个重要的面向对象编程的法则,它能指导我们如何设计出松耦合.更优良 ...

  2. 闭关修炼180天--手写IOC和AOP(xml篇)

    闭关修炼180天--手写IOC和AOP(xml篇) 帝莘 首先先分享一波思维导图,涵盖了一些Spring的知识点,当然这里并不全面,后期我会持续更新知识点. 在手写实现IOC和AOP之前(也就是打造一 ...

  3. 手写HashMap实践

    1.什么是HashMap 2.源码分析 3.手写实现 4.不足 一.什么是HashMap hash散列 将一个任意长度通过某种算法(hash函数算法)换成一个固定值 map: 地图x,y 存储 总结: ...

  4. 初学源码之——银行案例手写IOC和AOP

    手写实现lOC和AOP 上一部分我们理解了loC和AOP思想,我们先不考虑Spring是如何实现这两个思想的,此处准备了一个『银行转账」的案例,请分析该案例在代码层次有什么问题?分析之后使用我们已有知 ...

  5. 第三节:工厂+反射+配置文件(手写IOC)对缓存进行管理。

    一. 章前小节 在前面的两个章节,我们运用依赖倒置原则,分别对 System.Web.Caching.Cache和 System.Runtime.Cacheing两类缓存进行了封装,并形成了ICach ...

  6. 手写IOC容器

    IOC(控制翻转)是程序设计的一种思想,其本质就是上端对象不能直接依赖于下端对象,要是依赖的话就要通过抽象来依赖.这是什么意思呢?意思就是上端对象如BLL层中,需要调用下端对象的DAL层时不能直接调用 ...

  7. 手写IOC框架

    1.IOC框架的设计思路 ① 哪些类需要我们的容器进行管理 ②完成对象的别名和对应实例的映射装配 ③完成运行期对象所需要的依赖对象的依赖

  8. 我自横刀向天笑,手写Spring IOC容器,快来Look Look!

    目录 IOC分析 IOC是什么 IOC能够带来什么好处 IOC容器是做什么工作的 IOC容器是否是工厂模式的实例 IOC设计实现 设计IOC需要什么 定义接口 一:Bean工厂接口 二:Bean定义的 ...

  9. 手写IOC-SPRINGPMVC-CONNPOOL

    (一)  手写IOC思路 1.扫包,将所有class文件加载到内存,判断类上是否加了ExtService注解,有就添加入map中 ,  map<String ,Object>:  key是 ...

随机推荐

  1. Databend 设计概述 | 白皮书

    Databend 是一个开源的.完全面向云架构的新式数仓,它提供快速的弹性扩展能力,并结合云的弹性.简单性和低成本,使 Data Cloud 构建变得更加容易. Databend 把数据存储在像 AW ...

  2. Vue安装Vue Devtools调试工具提示 Error: Cannot find module '@vue-devtools/build-tools' 解决办法

    我看网络上面安装Vue Devtools 调试工具的步骤几乎都是按照文章链接里的步骤进行安装, 但是在最终执行编译命令的时候 :npm run build ,提示如下错误: 尝试了很多方法,都不能解决 ...

  3. 微信和QQ这么多群,该如何管理好友关系?

    本文节选自<设计模式就该这样学> 1 中介者模式的应用场景 在现实生活中,中介者的存在是不可缺少的,如果没有了中介者,我们就不能与远方的朋友进行交流.各个同事对象将会相互进行引用,如果每个 ...

  4. restTemplate的问题-feign的项目

    restTemplate的问题  1.场景描述 在使用feign的项目中,偶然的使用到了restTemplate 在普通方法调用是可以访问的,一旦使用了restTemplate,出现报错 比如: 百度 ...

  5. 初识XSS攻击

    初识XSS攻击 本文参考于<白帽子讲Web安全>第3章跨站脚本攻击(XSS),该书出版于2014年,因而现在可能存在一些新场景或新技术而未被提及,但本文对学习和了解XSS攻击仍具有重要价值 ...

  6. [ARC098B] Xor Sum 2

    关于异或运算和代数和运算有很不错的性质: \(xor_{i = 1} ^ {n}a_i \leq \sum_{i = 1} ^ n a_i\) 所以我们考虑一段区间按题目来说是合法的,即 \(xor_ ...

  7. 洛谷 P3711 - 仓鼠的数学题(多项式)

    洛谷题面传送门 提供一种不太一样的做法. 假设要求的多项式为 \(f(x)\).我们考察 \(f(x)-f(x-1)\),不难发现其等于 \(\sum\limits_{i=0}^na_ix^i\) 考 ...

  8. Codeforces 348C - Subset Sums(根号分治)

    题面传送门 对于这类不好直接维护的数据结构,第一眼应该想到-- 根号分治! 我们考虑记[大集合]为大小 \(\geq\sqrt{n}\) 的集合,[小集合]为大小 \(<\sqrt{n}\) 的 ...

  9. Codeforces 446D - DZY Loves Games(高斯消元+期望 DP+矩阵快速幂)

    Codeforces 题目传送门 & 洛谷题目传送门 神仙题,%%% 首先考虑所有格子都是陷阱格的情况,那显然就是一个矩阵快速幂,具体来说,设 \(f_{i,j}\) 表示走了 \(i\) 步 ...

  10. Bebug与Release版本

    如果调试过程无调试信息,检查编译选项是否切换到了release下 比如Cfree5等编译器 ms为了方便调试才诞生了DEBUG版. 这也导致了MFC有两个功能一至但版本不同的类库,一个为DEBUG版, ...