- A+
一、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是相对比较久远的框架,用的比较少。
-
AutoFac
-
Castle
-
Unity
-
ObjectBuilder
二、如何手写实现?
1.基本设计
核心思想:
工厂 + 反射
首先我们想自己实现一个IoC容器其实并不难,我们在使用现有的IoC容器都知道,在使用前,我们需要先注册,然后才能使用
所以我们将工厂换成手动注册的方式,因为写一大堆if else 或者switch也不太美观,根据主流IoC的使用方式来以葫芦画瓢,如果后期继续完善功能加入程序集注入的话,还是得实现一个工厂,来省略手动注册
但是这次目标是实现一个简易版的IoC容器,我们先实现基础功能,待后面一步一步去完善,再加入一些新的功能,即我们不考虑性能或者扩展度,目的是循序渐进,在写之前我们先整理出 实现步骤和实现方式
-
方便接入和扩展,我们在这先定义一个容器接口 IManualContainer
-
定义ManualContainer继承实现IManualContainer
-
声明一个静态字典对象存储注册的对象
-
利用反射构建对象,考虑到性能可以加入Expression或者Emit的方式来做一些优化
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
-
先定义List<object>集合存储对象构造时需要的参数列表
-
通过需要被实例的目标类型找到类中的构造函数,暂不考虑多构造函数case
-
找到构造函数参数及类型,然后创建参数的实例加入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.多级依赖(递归)
根据上面我们目前实现的结果来看,这是解决了构造函数和多参数注入以及基本的构造对象问题,那现在问题又来了
-
如果是很多层的依赖该怎么办?
-
例如多个构造函数怎么办呢?
-
在多个构造函数中用户想自定义需要被注入的构造函数怎么办?
总结3点问题
- 1.多级依赖问题
例如ITestB 的实例中依赖ITestC,一直无限依赖我们怎么解决呢?毫无疑问,做同样的事情,但是要无限做下去,就使用 递归
,下面我们来改造我们的方法
-
2.多个构造函数
-
取参数最多的方式注入 (AutoFac)
-
取并集方式注入 (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; } }