概述

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. java 内部类注意点

    1.只有内部类可以是私有类,而常规类只可以具有包可见性,或公有可见性. 内部类访问权限修饰符可以为:public.protected.默认.private:(可以用哪些修饰符视具体内部类类型而定) 常 ...

  2. leetcode981

    考虑线性的搜索会超时,所以用二叉搜索来解决,代码如下: class TimeMap: def __init__(self): self.ST = dict() def set(self, key: ' ...

  3. XAMPP与本地Mysql冲突解决方法

    1.更改regeit目录 https://blog.csdn.net/sinat_37633633/article/details/77645463 2.更改配置文件my.ini (1)https:/ ...

  4. ABAP-IDOC配置

    转载路径: http://www.cnblogs.com/jiangzhengjun/p/4292135.html#_Toc411677431 https://wenku.baidu.com/view ...

  5. 【原】 The Linux Command Line- pwd,cd,ls,file,less

    pwd - print name of current working directory. cd - change directory ls - list directory contents fi ...

  6. django mysql 数据库配置

    在settings.py中保存了数据库的连接配置信息,Django默认初始配置使用sqlite数据库. DATABASES = { 'default': { 'ENGINE': 'django.db. ...

  7. C/S和B/S架构

    1.C/S架构(Client/Server结构,熟知的客户机和服务器结构),它是软件系统体系结构,通过它可以充分利用两端硬件环境的优势,将任务合理分配到Client端和Server端来实现,降低了系统 ...

  8. SpringBoot集成Jasypt安全框架,配置文件内容加密

    我们在SpringBoot项目中的yml或者properties配置文件中都是明文的,相对而言安全性就低了很多.都知道配置文件中的都是一些数据库连接用户名密码啊.一些第三方密钥等信息.所以我们谨慎点, ...

  9. [JAVA]JAVA章2 IOC与AOP是啥

    使用Spring框架的过程中,其实就是为了使用IOC(依赖注入),和AOP(面向切面编程),这两个是Spring的灵魂. 主要用到的设计模式有工厂模式和代理模式. IOC就是典型的工厂模式,通过ses ...

  10. playframework 一步一步来 之 日志(一)

    日志模块是一个系统中必不可少的一部分,它可以帮助我们写程序的时候查看错误信息,利于调试和维护,在业务面,它也可以记录系统的一些关键性的操作,便于系统信息的监控和追踪. play的日志是基于logbac ...