白话系列之IOC,三个类实现简单的Ioc
前言:博客园上已经有很多IOC的博客.而且很多写的很好,达到开源的水平,但是对于很多新人来说,只了解ioc的概念,以及怎么去使用ioc.然后想更进一步去看源码,但是大部分源码都比较困难,当不知道一个框架整体时候,从每一个片段去推理,其实很耗费时间,所以这篇博客,从autofac及.netcore自带的ioc的源码中抽象出最核心代码,先了解整个ioc的实现方式,其他的所有好的ioc,只是在这个框架上面进行缝缝补补.
友情提示下,这个ioc虽然能够使用,但是只是为了做例子,所以只保留最核心代码,要使用还是使用autofac或成熟的ioc框架.
一:老生常谈
问:什么是ioc?
答:依赖注入,是一种思想,由于过分模糊,所以提出DI的观点:被注入对象依赖IOC容器配置依赖对象
问:有什么用?
答:解决高耦合问题,以前没有ioc的时候,每次都执行的是new操作,这没什么不好,但是假设,本来使用sqlserver,通过IConnection conn = new Sqlserver();方式初始化所有的连接操作,但是现在老板要求改成mysql当做数据库,如果按照new的方式,得一个个去改,全局搜索,全局替换,其实也是可以的,无非是人累点,还需要一遍遍去检查,看哪里漏了,这时候就怀念Ioc的好处了,只需在容器内改变一处,便全局改变.当然,这里并不是少写了几行new代码,代码还是一样的多,只不过new的操作让容器去处理了.拟人化的方式就是,new的方式就相当于以前没群的时候,你本来是密令是10, 你一个个去通知你所想要通知的人即new,但是现在呢,密令被敌人偷听去了,你需要更改,这次改成20,你就得一个个通知,但是现在你每次联系别人都是通过手机去联系,你不需要管手机是怎么发送给对方的,只需要知道你给手机一个通知,其他人都可以立马收到,那么手机在这里扮演的就是容器的概念,一次更改,全部获悉
二:理论结束,开始思考准备ioc之前需要准备的东西
1.首先建立一个收集器,收集可能需要new的对象,那么会有几种生命周期去new一个对象?
常用的就是单例模式(singleton), 每次直接new对象,即用即抛(Transient),还有当前请求的主线程中只会创建一个对象(Scope,注意,单例是所有请求都会公用一个对象),所以,先定义接口,如下,命名即功能
public interface IServiceCollection
{
IServiceCollection AddTransient<T1, T2>() where T2 : T1;
IServiceCollection AddTransient<T1>(T1 t2);
IServiceCollection AddSingleton<T1, T2>() where T2 : T1;
IServiceCollection AddSingleton<T1>(T1 t2);
IServiceCollection AddScoped<T1, T2>() where T2 : T1;
IServiceCollection AddScoped<T1>(T1 t2);
IServiceProvider BuildServiceProvider();
}
2.其次,建立一个对象提供器,获取容器内的可以获取的对象
越简单越好,直接通过类型获取对应的对象,同样,接口定义如下:
public interface IServiceProvider
{
T GetRequiredService<T>();
Object GetRequiredService(Type type);
}
3.Collection对收集的对象进行保存,并且需要对每个对象进行区分是Singleton,scoped,还是transient的
注意:我觉得在设计一个好的代码时候,得弄清楚当前类型具体的作用,然后如果作用不一样,那么得重新创建一个类型,当然如果后期发现没必要,可以合并,但是前期还是得分清楚点,就如sql中的范式及反范式.
3.1:首先定义枚举,区分当前的类型需要new的类型,与上文中的一致
public enum ServiceLifetime
{
Singleton = ,
Transient = ,
Scoped =
}
3.2:其次需要保存注入进去的类型及周期,因为不去考虑架构,只考虑那ioc的意思,就尽量简化代码

三:直接开始撸代码
1.通过Type创建对象,先默认只创建当前无参构造器,代码很简单
public Object GetCache(IDictionary<Type, IServiceCache> typePairs)
{
if (_obj == null)
{
_obj = Activator.CreateInstance(_type);
}
switch (_typeEnum)
{
case ServiceLifetime.Transient:
return Activator.CreateInstance(_type);
case ServiceLifetime.Singleton:
return _obj;
case ServiceLifetime.Scoped:
throw new Exception("目前不支持scoped");
default:
throw new Exception("请传递正确生命周期");
}
}
DeepClone的写法就是通过序列化的方式实现的,JsonConvert
public static Object DeepClone(this Object obj, Type type)
{
return JsonConvert.DeserializeObject(JsonConvert.SerializeObject(obj), type);
}
2.collection保存对应的对象,继承IServiceCollection接口
public class ServiceCollection : IServiceCollection
{
private ConcurrentDictionary<Type, IServiceCache> _typePairs;
public ServiceCollection()
{
_typePairs = new ConcurrentDictionary<Type, IServiceCache>();
}
public IServiceCollection AddScoped<T1, T2>() where T2 : T1{}
public IServiceCollection AddScoped<T1>(T1 t2){}
public IServiceCollection AddSingleton<T1, T2>() where T2 : T1{}
public IServiceCollection AddTransient<T1, T2>() where T2 : T1{}
public IServiceCollection AddSingleton<T>(T t2){}
public IServiceProvider BuildServiceProvider(){}
}
实现Singleton及Transient,此处Scoped有些额外的语法糖,等后期会猜想实现
public IServiceCollection AddSingleton<T1, T2>() where T2 : T1
{
Type t1 = typeof(T1);
Type t2 = typeof(T2);
ServiceTypeCache service = new ServiceTypeCache(t2, ServiceLifetime.Singleton);
if (!_typePairs.TryAdd(t1, service))
{
throw new Exception("在注入对象时,有相同对象存在");
}
return this;
}
public IServiceCollection AddTransient<T1, T2>() where T2 : T1
{
Type t1 = typeof(T1);
Type t2 = typeof(T2);
ServiceTypeCache service = new ServiceTypeCache(t2, ServiceLifetime.Transient);
if (!_typePairs.TryAdd(t1, service))
{
throw new Exception("在注入对象时,有相同对象存在");
}
return this;
}
3:实现IServiceProvider接口,就是从Cache中获取对应的对象
public class ServiceProvider : IServiceProvider
{
private IDictionary<Type, IServiceCache> _cache;
public ServiceProvider(IDictionary<Type, IServiceCache> valuePairs)
{
_cache = valuePairs;
}
public T GetRequiredService<T>()
{
Type t = typeof(T);
return (T)GetRequiredService(t);
}
public object GetRequiredService(Type type)
{
IServiceCache service = null;
if (!_cache.TryGetValue(type, out service))
{
throw new Exception("获取参数对象没有注入");
}
return service.GetCache();
}
}
4:将Collection转变为ServiceProvider
public IServiceProvider BuildServiceProvider()
{
return new ServiceProvider(_typePairs);
}
5:OK,现在来试试这种简单注入
public interface ITestTransient
{
void Write();
}
public class TestATransient : ITestTransient
{
public void Write()
{
Console.WriteLine("----------------A----------------");
}
}
public class TestBTransient : ITestTransient
{
public void Write()
{
Console.WriteLine("----------------B----------------");
}
}
class Program
{
static void Main(string[] args)
{
InitA();
InitB();
Console.Read();
}
public static void InitA()
{
IServiceCollection collection = new ServiceCollection();
collection.AddTransient<ITestTransient, TestATransient>();
IServiceProvider provider = collection.BuildServiceProvider();
provider.GetRequiredService<ITestTransient>().Write();
}
public static void InitB()
{
IServiceCollection collection = new ServiceCollection();
collection.AddTransient<ITestTransient, TestBTransient>();
IServiceProvider provider = collection.BuildServiceProvider();
provider.GetRequiredService<ITestTransient>().Write();
}
}

测试OK,只要在后面的代码中使用同一个provider,那么从IOC容器中获取的实例都是相同,改一处便全部都能修改
6.延伸,现在通过构造器注入其他代码,比如
class A{}
class B
{
public B(A a) { }
}
猜想下,遇到这种构造器注入时候,怎么去处理,其实和创建Type对象一直,通过CreateInstance(Type, Object[] param);去创建,param是每个需要注入的类型对象
OK,那我们来改下代码,将获取Object对象的方法添加参数,因为构造器里面注入的参数都是从IOC里面获取
public interface IServiceCache
{
Object GetCache(IDictionary<Type, IServiceCache> typePairs);
}
获取当前Type类型的构造器,默认获取参数最多的,参数一样多的,获取最后一个,注:这里可以添加一个特性,标明优先构造这个构造器,自己添加就好,写法尽量简单
private List<Type> GetConstructor()
{
ConstructorInfo[] a = _type.GetConstructors();
ConstructorInfo b = null;
Int32 length = ;
foreach (ConstructorInfo info in a)
{
if (info.GetParameters().Length >= length)
{
b = info;
}
}
ParameterInfo[] pa = b.GetParameters();
List<Type> list = new List<Type>();
foreach (var p in pa)
{
list.Add(p.ParameterType);
}
return list;
}
构造器参数,就需要从typePairs里面获取,注意,这里的所有参数都必须从IOC容器中获取,当然这里会有一个问题就是相互引用,这时候就需要注意下
public Object GetCache(IDictionary<Type, IServiceCache> typePairs)
{
if (_obj == null)
{
//这里实际是构建一个表达式树,这样就不需要每次去通过反射创建对象了
List<Type> types = GetConstructor();
Object[] paramters = types.ConvertAll(item => typePairs[item].GetCache(typePairs)).ToArray();
_obj = Activator.CreateInstance(_type, paramters);
}
switch (_typeEnum){...}
}
7.测试
public class ConstructorIOCTest
{
private readonly ITestTransient m_test;
public ConstructorIOCTest(ITestTransient test)
{
m_test = test;
}
public void WriteTestTransient()
{
m_test.Write();
Console.WriteLine("--------------ConstructorIOCTest-----------");
}
}
class Program
{
static void Main(string[] args)
{
InitA();
InitB();
Console.Read();
}
public static void InitA()
{
IServiceCollection collection = new ServiceCollection();
collection.AddTransient<ITestTransient, TestATransient>();
collection.AddTransient<ConstructorIOCTest, ConstructorIOCTest>();
IServiceProvider provider = collection.BuildServiceProvider();
provider.GetRequiredService<ConstructorIOCTest>().WriteTestTransient();
}
public static void InitB()
{
IServiceCollection collection = new ServiceCollection();
collection.AddTransient<ITestTransient, TestBTransient>();
collection.AddTransient<ConstructorIOCTest, ConstructorIOCTest>();
IServiceProvider provider = collection.BuildServiceProvider();
provider.GetRequiredService<ConstructorIOCTest>().WriteTestTransient();
}
}

可以看出来,所有的IOC都是从构造器出发,这样就避免到处修改的尴尬了
总结:
1.这是一个简单的IOC代码,里面我尽量采用最简单的小白的方式去实现,没有使用设计模式(本身最多有个工厂模式),没有表达式树,没有锁(锁是非常重要的,后期我会花几个章节去介绍各种锁)
2.IOC其实就是一个概念,理解之后,在构造的时候添加几个特性,比如属性注入,方法注入,其实无非就是在ServiceTypeCache类中添加构造器,方法,属性筛选之类的语法糖而已
3.这里没有时间Scopd的生命周期,因为我并不是很确定.net core中这个的写法,对我来说有2种,一种是在GetService时候,HttpContext注入,一种是将ServiceProvider里面进行包装一层Guid,相同的Guid的Scopd相同
4.希望大家可以去看看源码,尤其是推荐微软开源的几个框架,代码之精华,越看越觉得代码之美,虽然里面很多代码就是在打补丁,坑死人
5.https://github.com/BestHYC/IOCSolution.git,源码,代码的话我就不加工了,因为没什么好加工的,毕竟IOC实在太成熟了
白话系列之IOC,三个类实现简单的Ioc的更多相关文章
- Spring5源码解析系列一——IoC容器核心类图
基本概念梳理 IoC(Inversion of Control,控制反转)就是把原来代码里需要实现的对象创建.依赖,反转给容器来帮忙实现.我们需要创建一个容器,同时需要一种描述来让容器知道要创建的对象 ...
- .NET CORE学习笔记系列(2)——依赖注入【1】控制反转IOC
原文:https://www.cnblogs.com/artech/p/net-core-di-01.html 一.流程控制的反转 IoC的全名Inverse of Control,翻译成中文就是“控 ...
- 框架源码系列六:Spring源码学习之Spring IOC源码学习
Spring 源码学习过程: 一.搞明白IOC能做什么,是怎么做的 1. 搞明白IOC能做什么? IOC是用为用户创建.管理实例对象的.用户需要实例对象时只需要向IOC容器获取就行了,不用自己去创建 ...
- 【转】Spring Boot干货系列:(三)启动原理解析
前言 前面几章我们见识了SpringBoot为我们做的自动配置,确实方便快捷,但是对于新手来说,如果不大懂SpringBoot内部启动原理,以后难免会吃亏.所以这次博主就跟你们一起一步步揭开Sprin ...
- Spring Boot干货系列:(三)启动原理解析
Spring Boot干货系列:(三)启动原理解析 2017-03-13 嘟嘟MD 嘟爷java超神学堂 前言 前面几章我们见识了SpringBoot为我们做的自动配置,确实方便快捷,但是对于新手来说 ...
- 【DWR系列03】- DWR主要类详解
img { border: 1px solid black } 一.简介 首先应该了解这个jar包主要的类,了解了类,就了解了DWR.DWR的在线javadoc:http://directwebrem ...
- 《Entity Framework 6 Recipes》中文翻译系列 (11) -----第三章 查询之异步查询
翻译的初衷以及为什么选择<Entity Framework 6 Recipes>来学习,请看本系列开篇 第三章 查询 前一章,我们展示了常见数据库场景的建模方式,本章将向你展示如何查询实体 ...
- 框架源码系列二:手写Spring-IOC和Spring-DI(IOC分析、IOC设计实现、DI分析、DI实现)
一.IOC分析 1. IOC是什么? IOC:Inversion of Control控制反转,也称依赖倒置(反转) 问题:如何理解控制反转? 反转:依赖对象的获得被反转了.由自己创建,反转为从IOC ...
- TiKV 源码解析系列文章(三)Prometheus(上)
本文为 TiKV 源码解析系列的第三篇,继续为大家介绍 TiKV 依赖的周边库 rust-prometheus,本篇主要介绍基础知识以及最基本的几个指标的内部工作机制,下篇会介绍一些高级功能的实现原理 ...
随机推荐
- SQL 分组后,获取每组中的最大值对应的数据
select gr,num,dt,(select bys from test where gr=b.gr and dt=b.dt) bys from ( select gr,count(0) num, ...
- 数据结构与算法—二叉排序树(java)
前言 前面介绍学习的大多是线性表相关的内容,把指针搞懂后其实也没有什么难度.规则相对是简单的. 再数据结构中树.图才是数据结构标志性产物,(线性表大多都现成api可以使用),因为树的难度相比线性表大一 ...
- 积性函数&线性筛&欧拉函数&莫比乌斯函数&因数个数&约数个数和
只会搬运YL巨巨的博客 积性函数 定义 积性函数:对于任意互质的整数a和b有性质f(ab)=f(a)f(b)的数论函数. 完全积性函数:对于任意整数a和b有性质f(ab)=f(a)f(b)的数论函数 ...
- 浏览器DOM渲染及阻塞问题
在准备面试,然后复习到了计网的知识点,紧接着又扯到了url从输入到浏览器渲染的那个问题,这里来顺便完善补充一下,本文的重点在渲染 上面的图就是浏览器从服务器请求来页面后渲染的全过程 这里我们分开来看: ...
- Java中synchronized关键字你知道多少
1.什么是synchronized 我们将其理解为同步锁,可以实现共享资源的同步访问,解决线程并发的安全问题.synchronize翻译成中文:同步,使同步.synchronized:已同步. 1.1 ...
- [Mysql] GroupBy 分组,按天、周、月
简单说明: 最近在做报表功能的时候,需要将数据按天.周和月进行合并展示(数据记录都是按天20190701). 正文: 说明:数据表中date都是int类型:如 20190701 一.按天 SELECT ...
- C#开发学习人工智能的第一步
前言 作为一个软件开发者,我们除了要学会复制,黏贴,还要学会调用API和优秀的开源类库. 也许,有人说C#做不了人工智能,如果你相信了,那只能说明你的思想还是狭隘的. 做不了人工智能的不是C#这种语言 ...
- POJ-1062 昂贵的聘礼 (最短路)
POJ-1062 昂贵的聘礼:http://poj.org/problem?id=1062 题意: 有一个人要到1号点花费最少的钱,他可以花费一号点对应的价格,也可以先买下其他一些点,使得费用降低. ...
- Codeforces#398 &767C. Garland 树形求子节点的和
传送门 题意:在一个树上,问能否切两刀,使得三块的节点值的和相同. 思路: 由于这个总的节点和是不变的,每块的节点值和sum固定,dfs搜索,和等于sum/3,切.若不能分成三块(不能被3整除,-1) ...
- codeforces 765 D. Artsem and Saunder(数学题)
题目链接:http://codeforces.com/contest/765/problem/D 题意:题目中给出你两个公式,g(h(x))==x,h(g(x))==f(x).现给你f(x) 让你求符 ...