概述

IOC (Inversion of Control) 控制反转,大家应该都比较熟悉了、应该也都有用过,这里就不具体介绍了。自己平时也有用到过IOC,但是对它的具体实现原理只有一个模糊的概念,所以决定自己手动实现一个简单IOC。

开始

首先呢我们得知道IOC的主要作用是什么,才能开始动手写。IOC主要不就是负责创建对象以及管理生命周期嘛,那我们就开始动手啦。

比如现在有一个IAnimal接口Animal继承接口,然后就是个Call的方法。一般我们使用的时候都是IAnimal animal=new Animal(); 如果是使用第三方IOC容器实现的话,我们需要先注册一下类型才能获取到实例。

所以我们先来个最简单的仿照这个过程:

新建一个Container,然后里面有一个类型注册的方法ResgisterType和一个返回实例的方法Rerolve,还有一个存储类型的字典,具体代码如下

        private static Dictionary<string, object> ContainerTypeDictionary = new Dictionary<string, object>();/// <summary>
/// 注册类型
/// </summary>
/// <typeparam name="IT"></typeparam>
/// <typeparam name="T"></typeparam>
public void ResgisterType<IT,T>()
{
if (!ContainerTypeDictionary.ContainsKey(typeof(IT).FullName))
ContainerTypeDictionary.Add(typeof(IT).FullName, typeof(T));
} /// <summary>
/// 根据注册信息生成实例
/// </summary>
/// <typeparam name="IT"></typeparam>
/// <returns></returns>
public IT Rerolve<IT>()
{
string key = typeof(IT).FullName;
Type type = (Type)ContainerTypeDictionary[key];
       return (IT)Activator.CreateInstance(type);
     }

然后我们新建一个控制台测试一下

Container container = new Container();
container.ResgisterType<IAnimal, Animal>();
IAnimal animal= container.Rerolve<IAnimal>();

然后可以在不依赖具体对象Animal的情况下成功的创建一个animal实例。

之后我们就可以考虑复杂一点的情况了,现在我们的Animal类里没有做任何事,假如它的构造函数里依赖于另一个对象呢,这样我们的程序肯定是会报错的。比如下面这样:

public class Animal: IAnimal
{
public Animal(Dog dog)
{ }
}

我们容器目前能创建的对象实例,只有通过ResgisterType方法注册过类型的,而像Animal里依赖的不能实现创建,所以这个时候就需要用到依赖注入了。

关于依赖注入与控制反转的关系,我个人的理解是:控制反转是一种设计思想,而依赖注入则是实现控制反转思想的方法。

IOC容器一般依赖注入有三种:构造函数注入、方法注入、属性注入。

那么我们就来照瓢画葫芦,实现一下构造函数注入。一般IOC容器构造函数注入是通过一个特性来识别注入的,如果没有标记特性则去找构造函数参数个数最多的,我们就按照这个思路来。

首先我们新建一个LInjectionConstructorAttribute类,只需继承Attribute就行了。

public class LInjectionConstructorAttribute :Attribute
{
}

然后在刚才那个Animal构造函数上标记上特性,接下来就开始写代码。

/// <summary>
/// 根据注册信息生成实例
/// </summary>
/// <typeparam name="IT"></typeparam>
/// <returns></returns>
public IT Rerolve<IT>()
{
string key = typeof(IT).FullName;
Type type = (Type)ContainerTypeDictionary[key]; return (IT)CreateType(type);
}
/// <summary>
/// 根据提供的类型创建类型实例并返回
/// </summary>
/// <param name="type"></param>
/// <returns></returns>
private object CreateType(Type type)
{
var ctorArray = type.GetConstructors();
if (ctorArray.Count(c => c.IsDefined(typeof(LInjectionConstructorAttribute), true)) > )
{
//获取带特性标记的构造函数参数
foreach (var cotr in type.GetConstructors().Where(c => c.IsDefined(typeof(LInjectionConstructorAttribute), true)))
{
var paraArray = cotr.GetParameters();//获取参数数组
if (paraArray.Length == )
{
return Activator.CreateInstance(type);
} List<object> listPara = new List<object>();
foreach (var para in paraArray)
{
string paraKey = para.ParameterType.FullName;//参数类型名称
//从字典中取出缓存的目标对象并创建对象
Type paraTargetType = (Type)ContainerTypeDictionary[paraKey];
object oPara = CreateType(paraTargetType);//递归
listPara.Add(oPara);
}
return Activator.CreateInstance(type,listPara.ToArray());
} return Activator.CreateInstance(type);
}
else
{
//没有标记特性则使用参数最多的构造函数
var ctor = ctorArray.OrderByDescending(c => c.GetParameters().Length).FirstOrDefault();
var paraArray = ctor.GetParameters();//获取参数数组
if (paraArray.Length == )
{
return Activator.CreateInstance(type);
} List<object> listPara = new List<object>();
foreach (var para in paraArray)
{
string paraKey = para.ParameterType.FullName;//参数类型名称
//从字典中取出缓存的目标对象并创建对象
Type paraTargetType = (Type)ContainerTypeDictionary[paraKey];
object oPara = CreateType(paraTargetType);//递归
listPara.Add(oPara);
}
return Activator.CreateInstance(type, listPara.ToArray());
}
}

这里说下为什么用到递归,在我们项目中使用会有层层依赖的关系。比如我这里Animal依赖于Dog只有一层依赖,如果Gog又依赖于猫、猫依赖于鱼。。。(当然这里只是打个比方)

因为我们不知道具体有几层依赖,所以使用了递归的方法,直到将所有依赖的对象得到后再创建实例。

然后我们再来测试

Container container = new Container();
container.ResgisterType<IAnimal, Animal>();
container.ResgisterType<IDog, Dog>();
IAnimal animal= container.Rerolve<IAnimal>();

注意,如果测试标记特性的一定不要忘了在构造函数上标记特性,然后我们会发现最终也可以得到animal对象。

然后,创建对象这一块我们先告一段落。接下来进行生命周期管理。

一般的IOC容器都支持三种类型:Transient每次都得到一个新的对象、Scoped同一个域(或者请求、线程)中使用同一个对象、Singleton整个程序生命周期都使用同一实例对象。

那按照我们以上的代码怎么才能实现生命周期管理呢?我是这么想的:既然创建对象的工作都是由我容器来做了,那么我们在创建完对象之后能不能像注册类型一样将对象保存起来呢?

所以我这里使用了简单的字典来存储对象实例,然后通过判断使用的哪一种生命周期来返回新的对象或是直接返回字典里的对象。直接改造上面的代码了:

/// <summary>
/// 根据提供的类型创建类型实例并返回
/// </summary>
/// <param name="type"></param>
/// <returns></returns>
private object CreateType(Type type)
{
var ctorArray = type.GetConstructors();
if (ctorArray.Count(c => c.IsDefined(typeof(LInjectionConstructorAttribute), true)) > )
{
//获取带特性标记的构造函数参数
foreach (var cotr in type.GetConstructors().Where(c => c.IsDefined(typeof(LInjectionConstructorAttribute), true)))
{
var paraArray = cotr.GetParameters();//获取参数数组
if (paraArray.Length == )
{
//return Activator.CreateInstance(type);
return GetSocpe(type);
} List<object> listPara = new List<object>();
foreach (var para in paraArray)
{
string paraKey = para.ParameterType.FullName;//参数类型名称
//从字典中取出缓存的目标对象并创建对象
Type paraTargetType = (Type)ContainerTypeDictionary[paraKey];
object oPara = CreateType(paraTargetType);//递归
listPara.Add(oPara);
}
//return Activator.CreateInstance(type,listPara.ToArray());
return GetSocpe(type, listPara.ToArray());
} return GetSocpe(type);
//return Activator.CreateInstance(type);
}
else
{
//没有标记特性则使用参数最多的构造函数
var ctor = ctorArray.OrderByDescending(c => c.GetParameters().Length).FirstOrDefault();
var paraArray = ctor.GetParameters();//获取参数数组
if (paraArray.Length == )
{
//return Activator.CreateInstance(type);
return GetSocpe(type);
} List<object> listPara = new List<object>();
foreach (var para in paraArray)
{
string paraKey = para.ParameterType.FullName;//参数类型名称
//从字典中取出缓存的目标对象并创建对象
Type paraTargetType = (Type)ContainerTypeDictionary[paraKey];
object oPara = CreateType(paraTargetType);//递归
listPara.Add(oPara);
}
return GetSocpe(type, listPara.ToArray());
//return Activator.CreateInstance(type, listPara.ToArray());
}
}
private object GetSocpe(Type type, params object[] listPara)
{
if (_scopeType == (int)Scope.Singleton)
{
return GetTypeSingleton(type, listPara);
}
else if (_scopeType == (int)Scope.Transient)
{
return GetTypeTransient(type, listPara);
}
else
{
return GetTypeScoped(type, listPara);
}
} #region 生命周期
/// <summary>
/// 设置获取实例对象生命周期为Singleton
/// </summary>
/// <param name="type"></param>
/// <param name="listPara"></param>
/// <returns></returns>
private object GetTypeSingleton(Type type, params object[] listPara)
{
if (ContainerExampleDictionary.ContainsKey(type.FullName))
{
lock (locker)
{
if (ContainerExampleDictionary.ContainsKey(type.FullName))
{
return ContainerExampleDictionary[type.FullName];
}
}
} if (listPara.Length == )
{
var Example = Activator.CreateInstance(type);
ContainerExampleDictionary.Add(type.FullName, Example);
return Example;
}
else
{
var Example = Activator.CreateInstance(type, listPara.ToArray());
ContainerExampleDictionary.Add(type.FullName, Example);
return Example;
}
} /// <summary>
/// 设置获取实例对象生命周期为Transient
/// </summary>
/// <param name="type"></param>
/// <param name="listPara"></param>
/// <returns></returns>
private object GetTypeTransient(Type type, params object[] listPara)
{
if (listPara.Length == )
{
var Example = Activator.CreateInstance(type);
//ContainerExampleDictionary.Add(type.FullName, Example);
return Example;
}
else
{
var Example = Activator.CreateInstance(type, listPara.ToArray());
//ContainerExampleDictionary.Add(type.FullName, Example);
return Example;
}
} /// <summary>
/// 设置获取实例对象生命周期为Scoped
/// </summary>
/// <param name="type"></param>
/// <param name="listPara"></param>
/// <returns></returns>
private object GetTypeScoped(Type type, params object[] listPara)
{
var pid = System.Threading.Thread.CurrentThread.ManagedThreadId;
if (ContainerExampleDictionary.ContainsKey(type.FullName + pid))
{
lock (locker)
{
if (ContainerExampleDictionary.ContainsKey(type.FullName + pid))
{
return ContainerExampleDictionary[type.FullName + pid];
}
}
} if (listPara.Length == )
{
var Example = Activator.CreateInstance(type);
ContainerExampleDictionary.Add(type.FullName + pid, Example);
return Example;
}
else
{
var Example = Activator.CreateInstance(type, listPara.ToArray());
ContainerExampleDictionary.Add(type.FullName + pid, Example);
return Example;
}
}
#endregion
private static Dictionary<string, object> ContainerExampleDictionary = new Dictionary<string, object>();
private static int _scopeType;
private static readonly object locker = new object();
public int scopeType
{
get
{
return _scopeType;
}
set
{
_scopeType = value;
}
}
public enum Scope
{
Singleton = ,
Transient = ,
Scoped =
}

然后调用的时候先声明下要使用的声明周期类型就行啦

Container container = new Container();
container.scopeType = (int)Container.Scope.Singleton;
container.ResgisterType<IAnimal, Animal>();
container.ResgisterType<IDog, Dog>();
IAnimal animal= container.Rerolve<IAnimal>();

说下三种生命周期管理的实现:

Transient:则可以直接创建一个实例

Scoped:使用的是同一个线程内使用同一个对象实例,使用var pid = System.Threading.Thread.CurrentThread.ManagedThreadId;获取线程id来判断的

Singleton:这种则只需一个单例模式获取就好了

到这里就先告一段落了,以上只是一个简单实现,代码还有需改进的地方以及可以扩展的功能,欢迎提意见指出错误。同时代码已上传GigHub,还有不懂的可以参考下代码。

源码地址:https://github.com/liangchengxuyuan/IocContainer

手写实现简单版IOC的更多相关文章

  1. 手写一个简单版的SpringMVC

    一 写在前面 这是自己实现一个简单的具有SpringMVC功能的小Demo,主要实现效果是; 自己定义的实现效果是通过浏览器地址传一个name参数,打印“my name is”+name参数.不使用S ...

  2. 动手写一个简单版的谷歌TPU-矩阵乘法和卷积

    谷歌TPU是一个设计良好的矩阵计算加速单元,可以很好的加速神经网络的计算.本系列文章将利用公开的TPU V1相关资料,对其进行一定的简化.推测和修改,来实际编写一个简单版本的谷歌TPU.计划实现到行为 ...

  3. laravel学习:php写一个简单的ioc服务管理容器

    php写一个简单的ioc服务管理容器 原创: 陈晨 CoderStory 2018-01-14 最近学习laravel框架,了解到laravel核心是一个大容器,这个容器负责几乎所有服务组件的实例化以 ...

  4. 动手写一个简单版的谷歌TPU-指令集

    系列目录 谷歌TPU概述和简化 基本单元-矩阵乘法阵列 基本单元-归一化和池化(待发布) TPU中的指令集 SimpleTPU实例: (计划中) 拓展 TPU的边界(规划中) 重新审视深度神经网络中的 ...

  5. 利用SpringBoot+Logback手写一个简单的链路追踪

    目录 一.实现原理 二.代码实战 三.测试 最近线上排查问题时候,发现请求太多导致日志错综复杂,没办法把用户在一次或多次请求的日志关联在一起,所以就利用SpringBoot+Logback手写了一个简 ...

  6. 手写一个简单的starter组件

    spring-boot中有很多第三方包,都封装成starter组件,在maven中引用后,启动springBoot项目时会自动装配到spring ioc容器中. 思考: 为什么我们springBoot ...

  7. 手写一个简单的ElasticSearch SQL转换器(一)

    一.前言 之前有个需求,是使ElasticSearch支持使用SQL进行简单查询,较新版本的ES已经支持该特性(不过貌似还是实验性质的?) ,而且git上也有elasticsearch-sql 插件, ...

  8. 识别手写数字增强版100% - pytorch从入门到入道(一)

    手写数字识别,神经网络领域的“hello world”例子,通过pytorch一步步构建,通过训练与调整,达到“100%”准确率 1.快速开始 1.1 定义神经网络类,继承torch.nn.Modul ...

  9. 手写一个简单到SpirngMVC框架

    spring对于java程序员来说,无疑就是吃饭到筷子.在每次编程工作到时候,我们几乎都离不开它,相信无论过去,还是现在或是未来到一段时间,它仍会扮演着重要到角色.自己对spring有一定的自我见解, ...

随机推荐

  1. 调整iframe滚动条失效

    1:<iframe scrolling="auto" frameborder="0" src="' + add + '" style= ...

  2. springboot2系列目录

    参考:https://blog.csdn.net/cowbin2012/article/details/85254990 带源码

  3. jquery 中后代遍历之children、find区别

    jquery 中children.find区别 首先看一段HTML代码,如下: <table id="tb"> <tr> <td>0</t ...

  4. cdnbest日志分析显示404的原因

    日志分析报404原因 报这个提示一般是日志没有开启或还没有日志 检查下面几点: 开启和关闭站点日志在节点3311中显示如下图nolog是关闭  日志翻转目前默认是1个小时以后会默认改成20分钟,翻转时 ...

  5. "Web Scraping with Python"笔记(一)

    1.  合法性:抓取的数据用于个人使用,不存在问题:数据用于转载,需注意抓取的数据类型. 一般情况,抓取的真实数据(营业地址,电话清单等)允许转载.而原创数据(比如意见和评论)受版权限制不能转载. 2 ...

  6. Linux Ipv6地址配置

    Step1:启用IPV6网络配置 [root@node-1 ~]# vi /etc/sysconfig/network NETWORKING_IPV6=yes   //全局启用ipv6初始化IPV6_ ...

  7. Rifidi

    简介 Rifidi是RFID软件公司Pramari推出了一款开源中间件平台,其主页是:http://www.rifidi.org/ 其分为Edge Server, Workbench, Prototy ...

  8. Curator场景应用

    分布式锁功能: 在分布式场景中,我们为了保证数据的一致性,经常在程序运行的某一个点,需要进行同步操作,(java提供synchronized或者Reentrantlock实现), 使用curator基 ...

  9. MAC book 无法删除普通用户的解决办法

    1来自苹果官网 macOS Sierra: 删除用户或群组 如果您是管理员,当您不想再让某些用户访问 Mac 时,可以删除他们.您也可以删除不想要的群组. 删除用户时,您可以存储该用户的个人文件夹(包 ...

  10. fortitoken

    1.token状态为error,且不能分配给用户使用 解决: 关联有User的token状态是error的原因是:用户一直并未使用.