一、介绍
1、介绍
最近无聊,也没什么事做,没事做总是要给自己找点事情做吧,毕竟人的生活在与折腾。于是,决定自己手动写一个 IOC 的框架。我们知道在 NetCore 的版本里面已经内置了 IOC 容器,它就是 ServiceCollection,一般情况下,该容器还是够用的,但是有时候还会有力不从心的时候,比如:我想要实现属性注入或者方法注入,NetCore 内置的框架就不可以实现。还有情况是,我们要实现对同一接口的多实例注入也没办法实现。当然还有其他情况,比如,没有实现 AOP 的功能。最近正好无事可做,正好利用这段时间,自己亲手写一套 IOC 的框架,当然了,要重写,肯定要比 NetCore 内置的要强,否则,也就没有写的必要了,说干就干。
2、开发环境
1)、操作系统:Windows 10 专业版本。
2)、开发工具:Visual Studio 2019 社区版,16.8.3
3)、开发语言:C#
4)、框架版本:Net 5.0,该版本是跨平台版本,但是不是长期版本,6.0 是 LTS 版本。
3、实现目标
1)、该框架可以实现构造函数注入。
2)、该框架可以实现方法注入。
3)、该框架可以实现属性注入。
4)、该框架可以实现无限层级激活。
5)、该框架可以实现注册服务的多种声明周期,分别是:Transient,Singleton,Scoped,PerThread
6)、该框架可以实现针对同一接口的多实例注册。
7)、当一个类型在实例化的时候可以增加参数。
以上就是该框架的目标,应该还不错吧。毕竟我们自己手写了框架代码,让我们会更加了解 IOC 的定义和实现。
二、手写框架
1、我先把该框架的主要类型的代码贴出来,这个类型是核心类型,实现了我们上面定义的目标。
首先、我们为 IOC 容器定义接口,面向接口编程嘛,可不要忘记了,所以,我们先顶一个接口,类型名称:ICustomContainer。
1 ///
4 public interface ICustomContainer
5 {
6 ///
9 ///
10 ///
11 /// 要注册服务的名称。
12 /// 要注册的服务的生命周期。
13 /// 要注册的服务在构建实例时需要的非注入参数。
14 void Register
15
16 ///
19 ///
20 /// 要解析实例的名称。
21 ///
22 TFrom Resolve
23 }
再者,就是该接口的实现类型,该类型也是该容器的核心代码。代码不是很难,大家直接看吧。
类型名称:PatrickContainer
1 using System;
2 using System.Collections.Concurrent;
3 using System.Collections.Generic;
4 using System.Linq;
5 using System.Reflection;
6 using System.Threading;
7
8 namespace PatrickLiu.NetCore50.IOCFramework.Container
9 {
10 ///
13 public sealed class PatrickContainer: ICustomContainer
14 {
15 private readonly IDictionary
16 private readonly IDictionary
17 private readonly IDictionary
18
19
20 ///
23 public PatrickContainer()
24 {
25 _Container = new ConcurrentDictionary
26 _Parameters = new ConcurrentDictionary
27 _ScopedContainer = new Dictionary
28 }
29
30 ///
33 ///
34 public PatrickContainer CreateScoped()
35 {
36 return new PatrickContainer(_Container, _Parameters, new Dictionary
37 }
38
39 ///
42 ///
43 ///
44 ///
45 private PatrickContainer(IDictionary
46 {
47 this._Container = container;
48 this._Parameters = parameters;
49 this._ScopedContainer = scopedContainer;
50 }
51
52 ///
55 ///
56 ///
57 public void Register
58 {
59 Register
60 }
61
62 ///
65 ///
66 ///
67 /// 要注册的服务的生命周期。
68 public void Register
69 {
70 Register
71 }
72
73 ///
76 ///
77 ///
78 /// 要注册的服务在构建实例时需要的非注入参数。
79 public void Register
80 {
81 Register
82 }
83
84 ///
87 ///
88 ///
89 /// 要注册服务的名称。
90 public void Register
91 {
92 Register
93 }
94
95 ///
98 ///
99 ///
100 /// 要注册服务的名称。
101 /// 要注册的服务的生命周期。
102 public void Register
103 {
104 Register
105 }
106
107 ///
110 ///
111 ///
112 /// 要注册服务的名称。
113 /// 要注册的服务的生命周期。
114 /// 要注册的服务在构建实例时需要的非注入参数。
115 public void Register
116 {
117 string key;
118 if (string.IsNullOrEmpty(serviceName) || string.IsNullOrWhiteSpace(serviceName))
119 {
120 key = typeof(TFrom).FullName;
121 if (!_Container.ContainsKey(key))
122 {
123 _Container.Add(key, new ServiceMetadata() { ServiceType = typeof(TTo), Lifetime = lifetime });
124 }
125 }
126 else
127 {
128 key = string.Format("{0}_{1}", typeof(TFrom).FullName, serviceName);
129 if (!_Container.ContainsKey(key))
130 {
131 _Container.Add(key, new ServiceMetadata() { ServiceType = typeof(TTo), Lifetime = lifetime });
132 }
133 }
134 if (parameterValues != null && parameterValues.Length > 0)
135 {
136 _Parameters.Add(key, parameterValues);
137 }
138 }
139
140 ///
143 ///
144 ///
145 public TFrom Resolve
146 {
147 return Resolve
148 }
149
150 ///
153 ///
154 /// 要解析实例的名称。
155 ///
156 public TFrom Resolve
157 {
158 return (TFrom)Create(typeof(TFrom), serviceName);
159 }
160
161
162 ///
165 /// 服务的基类型。
166 /// 服务实例的名称。
167 ///
168 private object Create(Type baseType, string serviceName = null)
169 {
170 #region 处理关键字
171
172 string keyword;
173
174 if (string.IsNullOrEmpty(serviceName) || string.IsNullOrWhiteSpace(serviceName))
175 {
176 keyword = string.Format("{0}", baseType.FullName);
177 }
178 else
179 {
180 keyword = string.Format("{0}_{1}", baseType.FullName, serviceName);
181 }
182
183 #endregion
184
185 Type targetType = null; ServiceLifetime lifetime = ServiceLifetime.Transient;
186 if (_Container.ContainsKey(keyword))
187 {
188 targetType = _Container[keyword].ServiceType;
189 lifetime = _Container[keyword].Lifetime;
190 }
191 else if (keyword.IndexOf('_') != -1)
192 {
193 if (_Container.ContainsKey(keyword.Split('_')[0]))
194 {
195 keyword = keyword.Split('_')[0];
196 targetType = _Container[keyword].ServiceType;
197 lifetime = _Container[keyword].Lifetime;
198 }
199 }
200 else
201 {
202 throw new Exception("类型还未注册!");
203 }
204
205 #region 生命周期
206
207 switch (lifetime)
208 {
209 case ServiceLifetime.Transient:
210 break;
211 case ServiceLifetime.Singleton:
212 if (_Container[keyword].SingletonInstance != null)
213 {
214 return _Container[keyword].SingletonInstance;
215 }
216 break;
217 case ServiceLifetime.Scoped:
218 if (_ScopedContainer.ContainsKey(keyword))
219 {
220 return _ScopedContainer[keyword];
221 }
222 break;
223 case ServiceLifetime.PerThread:
224 var objInstance = CallContext.GetData($"{keyword}{Thread.CurrentThread.ManagedThreadId}");
225 if (objInstance != null)
226 {
227 return objInstance;
228 }
229 break;
230 default:
231 break;
232 }
233
234 #endregion
235
236 #region 选择构造函数
237
238 ConstructorInfo ctor = null;
239
240 //1、通过特性约束
241 ctor = targetType.GetConstructors().FirstOrDefault(c => c.IsDefined(typeof(SelectedConstructorAttribute), true));
242
243 if (ctor == null)
244 {
245 //2、参数最多的
246 ctor = targetType.GetConstructors().OrderByDescending(c => c.GetParameters().Length).First();
247 }
248
249 #endregion
250
251 #region 核心创建对象代码
252
253 IList
当然了,还有一些其他的辅助类型,这么大的框架,还是要需要一些辅助类型的,接下来我们一一介绍。
2、ConstantPatameterAttribute类型
该类型是一个标记特性,用于标注不需要注入而进行传递的参数,可以使用该属性。
1 using System;
2
3 namespace PatrickLiu.NetCore50.IOCFramework.Container
4 {
5 ///
8 [AttributeUsage(AttributeTargets.Parameter|AttributeTargets.GenericParameter,AllowMultiple =false,Inherited =true)]
9 public sealed class ConstantPatameterAttribute:Attribute
10 {
11 }
12 }
3、InjectionMethodAttribute 类型
该类型也是一个标记特性,用于标记方法,可以通过方法实现注入。
1 using System;
2
3 namespace PatrickLiu.NetCore50.IOCFramework.Container
4 {
5 ///
8 [AttributeUsage(AttributeTargets.Method,AllowMultiple =false,Inherited =true)]
9 public sealed class InjectionMethodAttribute:Attribute
10 {
11 }
12 }
4、InjectionPropertyAttribute 类型。
该类型也是一个标记特性,用于标记正在属性上,可以通过属性实现注入。
1 using System;
2
3 namespace PatrickLiu.NetCore50.IOCFramework.Container
4 {
5 ///
8 [AttributeUsage(AttributeTargets.Property, AllowMultiple = false, Inherited = true)]
9 public sealed class InjectionPropertyAttribute : Attribute
10 {
11 }
12 }
5、SelectedConstructorAttribute 类型
在我们构建类型实例的时候,可以通过该特性选择通过哪个构造函数创建实例。默认情况是选择参数最多的构造函数,也可以通过该特性选择构造函数。
1 using System;
2
3 namespace PatrickLiu.NetCore50.IOCFramework.Container
4 {
5 ///
8 [AttributeUsage(AttributeTargets.Constructor, AllowMultiple = false, Inherited = true)]
9 public sealed class SelectedConstructorAttribute : Attribute
10 {
11 }
12 }
6、ServiceLifetime 枚举类型。
我们可以实现对注册服务的生命周期的管理,类型简单,不多说了。
1 namespace PatrickLiu.NetCore50.IOCFramework.Container
2 {
3 ///
6 public enum ServiceLifetime
7 {
8 ///
11 Transient,
12
13 ///
16 Singleton,
17
18 ///
21 Scoped,
22
23 ///
26 PerThread
27 }
28 }
7、ServiceMetadata 类型。
用于定义注册服务的对象。
1 using System;
2
3 namespace PatrickLiu.NetCore50.IOCFramework.Container
4 {
5 ///
8 public sealed class ServiceMetadata
9 {
10 ///
13 public Type ServiceType { get; set; }
14
15 ///
18 public ServiceLifetime Lifetime { get; set; }
19
20 ///
23 public Object SingletonInstance { get; set; }
24 }
25 }
8、CallContext类型。
在NetCore 环境中,没有CallContext 类型,所以只能自己实现一个,可以实现基于线程来管理注册服务的生命周期。
1 using System.Collections.Concurrent;
2 using System.Threading;
3
4 namespace PatrickLiu.NetCore50.IOCFramework.Container
5 {
6 ///
9 public static class CallContext
10 {
11 private static ConcurrentDictionary
12
13 ///
16 /// The name with which to associate the new item in the call context.
17 /// The object to store in the call context.
18 public static void SetData(string name, object data) =>
19 state.GetOrAdd(name, _ => new AsyncLocal
20
21 ///
24 /// The name of the item in the call context.
25 ///
26 public static object GetData(string name) =>
27 state.TryGetValue(name, out AsyncLocal
28 }
29
30 ///
33 ///
34 public static class CallContext
35 {
36 static ConcurrentDictionary
37
38 ///
41 /// The name with which to associate the new item in the call context.
42 /// The object to store in the call context.
43 public static void SetData(string name, T data) =>
44 state.GetOrAdd(name, _ => new AsyncLocal
45
46 ///
49 ///
50 /// The name of the item in the call context.
51 ///
52 public static T GetData(string name) =>
53 state.TryGetValue(name, out AsyncLocal
54 }
55 }
**三、测试代码
**
我们为了更好的测试我们写的IOC容器,我另外建立两个独立的类库项目和一个控制台应用程序。当然了,引用关系别忘记了。
1、我们定义的接口类库,里面包含了测试用到的所有接口类型。很简单,不多说。
以下就是我们接口的代码了。
1 ///
4 public interface IServiceA
5 {
6 void Show();
7 }
1 public interface IServiceB
2 {
3 void Show();
4 }
1 public interface IServiceC
2 {
3 void Show();
4 }
1 public interface IServiceD
2 {
3 void Show();
4 }
1 public interface IServiceE
2 {
3 void Show();
4 }
1 public interface IServiceF
2 {
3 void Show();
4 }
2、第一步我们定义了接口类库,这里我们定义实现了接口类库的服务类库,很简单,不多说。
以下是实现代码,很简单,不多说。
1 ///
4 public class MyServiceA : IServiceA
5 {
6 public MyServiceA()
7 {
8 Console.WriteLine("MyServiceA is Created");
9 }
10
11 ///
14 public void Show()
15 {
16 Console.WriteLine("MyServiceA-show()");
17 }
18 }
1 ///
4 public sealed class MyServiceA2 : IServiceA
5 {
6 private IServiceF _IServiceF;
7
8 ///
11 public MyServiceA2()
12 {
13 Console.WriteLine("MyServiceA2 is created");
14 }
15
16 ///
19 ///
20 [InjectionMethod]
21 public void MethodInjection(IServiceF service)
22 {
23 _IServiceF = service;
24 }
25
26 ///
29 public void Show()
30 {
31 Console.WriteLine("MyServiceA2--Show()");
32 }
33 }
1 public class MyServiceA3 : IServiceA
2 {
3 ///
6 ///
7 ///
8 ///
9 public MyServiceA3([ConstantPatameter]int age, [ConstantPatameter] string name, [ConstantPatameter] string school)
10 {
11 Console.WriteLine($"{age}{name}{school} MyServiceA3 is created");
12 }
13
14
15 public void Show()
16 {
17 Console.WriteLine("MyServiceA3-Show() is executed!");
18 }
19 }
1 public class MyServiceA4 : IServiceA
2 {
3 private IServiceF _IServiceF;
4
5 public MyServiceA4()
6 {
7 Console.WriteLine("");
8 }
9
10 ///
13 /// 注入服务
14 ///
15 ///
16 [InjectionMethod]
17 public void MethodInjection(IServiceF service,[ConstantPatameter]int age,[ConstantPatameter]string name)
18 {
19 _IServiceF = service;
20 Console.WriteLine($"{name} 今年 {age} 岁了。");
21 }
22
23 public void Show()
24 {
25 Console.WriteLine("MyServiceA4--show() executing");
26 }
27 }
1 ///
4 public class MyServiceB : IServiceB
5 {
6 ///
9 ///
10 ///
11 public MyServiceB(IServiceC serviceC,IServiceE serviceE)
12 {
13 Console.WriteLine("MyServiceB is created");
14 }
15
16 ///
19 public void Show()
20 {
21 Console.WriteLine("MyServiceB-Show()");
22 }
23 }
1 ///
4 public class MyServiceC : IServiceC
5 {
6 ///
9 ///
10 public MyServiceC(IServiceD serviceD)
11 {
12 Console.WriteLine("MyServiceC is created");
13 }
14
15 ///
18 public void Show()
19 {
20 Console.WriteLine("MyServiceC-Show()");
21 }
22 }
1 ///
4 public class MyServiceD : IServiceD
5 {
6 ///
9 public MyServiceD()
10 {
11 Console.WriteLine("MyServiceD is created");
12 }
13
14 ///
17 public void Show()
18 {
19 Console.WriteLine("MyServiceD--show()");
20 }
21 }
1 ///
4 public class MyServiceE : IServiceE
5 {
6 ///
9 public MyServiceE()
10 {
11 Console.WriteLine("MyServiceE is created");
12 }
13
14 ///
17 [InjectionProperty]
18 public IServiceF ServiceF { get; set; }
19
20 ///
23 public void Show()
24 {
25 Console.WriteLine("MyServiceE-Show()");
26 }
27 }
1 ///
4 public sealed class MyServiceF : IServiceF
5 {
6 ///
9 public MyServiceF()
10 {
11 Console.WriteLine("MyServiceF is created!");
12 }
13
14 ///
17 public void Show()
18 {
19 Console.WriteLine("MyServiceF--Show()");
20 }
21 }
3、这个代码就是我们控制台项目,用来做测试的,很简单,不多说了。
1 class Program
2 {
3 static void Main(string[] args)
4 {
5 //最简单版本
6 {
7 PatrickContainer container = new PatrickContainer();
8 container.Register
9
10 var instance = container.Resolve
11 instance.Show();
12 }
13 //可以多层依赖,可以包含属性注入
14 {
15 PatrickContainer container = new PatrickContainer();
16 container.Register
17 container.Register
18 container.Register
19 container.Register
20 container.Register
21 container.Register
22
23 var instance = container.Resolve
24 instance.Show();
25 }
26 //单接口多实例,也包含方法注入
27 {
28 PatrickContainer container = new PatrickContainer();
29 container.Register
30 container.Register
31 container.Register
32
33 var instance = container.Resolve
34 instance.Show();
35 instance = container.Resolve
36 instance.Show();
37 }
38 //构造函数参数、方法参数
39 {
40 PatrickContainer container = new PatrickContainer();
41
42 container.Register
43
44 var instance = container.Resolve
45 instance.Show();
46 }
47 {
48 PatrickContainer container = new PatrickContainer();
49
50 container.Register
51 container.Register
52
53 var instance = container.Resolve
54 instance.Show();
55 }
56 //声明周期
57 {
58 //单例
59 PatrickContainer container = new PatrickContainer();
60 container.Register
61
62
63 var instance = container.Resolve
64 var instance2 = container.Resolve
65
66 Console.WriteLine(Object.ReferenceEquals(instance,instance2));//True
67 }
68 //
69 {
70 //瞬时
71 PatrickContainer container = new PatrickContainer();
72 container.Register
73
74
75 var instance = container.Resolve
76 var instance2 = container.Resolve
77
78 Console.WriteLine(Object.ReferenceEquals(instance, instance2));//False
79 }
80 {
81 //作用域
82 PatrickContainer container = new PatrickContainer();
83 container.Register
84
85
86 var instance = container.Resolve
87 var instance2 = container.Resolve
88
89 Console.WriteLine(Object.ReferenceEquals(instance, instance2));//True
90
91 var container2 = container.CreateScoped();
92 var objedd=container2.Resolve
93 var objedd2=container2.Resolve
94
95 /Console.WriteLine(Object.ReferenceEquals(objedd, objedd2));//True
96
97 Console.WriteLine(Object.ReferenceEquals(instance, objedd2));//False
98 Console.ReadLine();
99 }
100 {
101 //线程
102 PatrickContainer container = new PatrickContainer();
103 container.Register
104
105 IServiceA instance = null;
106 IServiceA instance2 = null;
107 IServiceA instance3 = null;
108 IServiceA instance4 = null;
109 IServiceA instance5 = null;
110 IServiceA instance6 = null;
111
112 Task.Run(()=> {
113 Console.WriteLine($"instance,当前线程的ID:{Thread.CurrentThread.ManagedThreadId}");
114 instance = container.Resolve
115 });
116
117 Task.Run(() => {
118 Console.WriteLine($"instance2,当前线程的ID:{Thread.CurrentThread.ManagedThreadId}");
119 instance2 = container.Resolve
120 });
121
122 Task.Run(() => {
123 Console.WriteLine($"instance3 和 instance4,当前线程的ID:{Thread.CurrentThread.ManagedThreadId}");
124 instance3 = container.Resolve
125 instance4 = container.Resolve
126 });
127
128 Task.Run(() => {
129 Console.WriteLine($"instance5,当前线程的ID:{Thread.CurrentThread.ManagedThreadId}");
130 instance5 = container.Resolve
131 }).ContinueWith(t=> {
132 Console.WriteLine($"instance6,当前线程的ID:{Thread.CurrentThread.ManagedThreadId}");
133 instance6 = container.Resolve
134 });
135
136 Thread.Sleep(1000);
137
138 Console.WriteLine(Object.ReferenceEquals(instance, instance2));//False
139 Console.WriteLine(Object.ReferenceEquals(instance, instance3));//False
140 Console.WriteLine(Object.ReferenceEquals(instance, instance4));//False
141 Console.WriteLine(Object.ReferenceEquals(instance2, instance3));//False
142 Console.WriteLine(Object.ReferenceEquals(instance2, instance4));//False
143 Console.WriteLine(Object.ReferenceEquals(instance3, instance4));//True
144
145 Console.WriteLine(Object.ReferenceEquals(instance5, instance6));//False
146 }
147
148 Console.Read();
149 }
150 }
四、结束 好了,今天就写到这里了,实话实说,这个有难度吗?其实没什么难度。这些代码都是可以直接使用的,我经过测试的,(我也有可能没有测试到的),如果大家感觉不错,可以拿去使用,好好的测试一下,也可以增加自己的东西,功能挺强大的,使用挺方便的。不忘初心,继续努力。
手机扫一扫
移动阅读更方便
你可能感兴趣的文章