概述

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)) > 0){//获取带特性标记的构造函数参数foreach (var cotr in type.GetConstructors().Where(c => c.IsDefined(typeof(LInjectionConstructorAttribute), true))){var paraArray = cotr.GetParameters();//获取参数数组if (paraArray.Length == 0){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 == 0){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)) > 0){//获取带特性标记的构造函数参数foreach (var cotr in type.GetConstructors().Where(c => c.IsDefined(typeof(LInjectionConstructorAttribute), true))){var paraArray = cotr.GetParameters();//获取参数数组if (paraArray.Length == 0){//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 == 0){//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 == 0){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 == 0){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 == 0){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 = 0,Transient = 1,Scoped = 2}

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

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

转载于:https://www.cnblogs.com/lyps/p/10560256.html

手写实现简单版IOC相关推荐

  1. 手写一个简单的IOC容器

    手写一个简单的IOC容器 原文 http://localhost:4000/2020/02/25/SSM/spring/%E6%89%8B%E5%86%99%E4%B8%80%E4%B8%AA%E5% ...

  2. 纯手写SpringFramework-完结版(原创)

    个人简介 作者是一个来自河源的大三在校生,以下笔记都是作者自学之路的一些浅薄经验,如有错误请指正,将来会不断的完善笔记,帮助更多的Java爱好者入门. 文章目录 个人简介 纯手写SpringFrame ...

  3. 【c++】手写笔记扫描版

    c++手写笔记扫描版 c++基础 类与对象 数据的共享与保护 数组.指针与字符串 进阶与派生 多态 模板与群体数据 泛型函数设计与c++标准模板 流类库与输入输出 异常处理 3月份境外确诊病例数增多 ...

  4. 手写实现简单栈(练习题)

    一.手写实现简单栈 push(x) -- 将元素 x 推入栈中. pop() -- 删除栈顶的元素. top() -- 获取栈顶元素. getMin() -- 检索栈中的最小元素.示例:输入: [&q ...

  5. 手写 springIoc 注解版 ,实现@Service (beng),@Resource (依赖注入)

    手写springIoc 注解版 代码demo https://pan.baidu.com/s/1jyvLMDrg_bfpKmhtrTTZSQ 提取码:5ju1 代码目录结构 1.pom.xml < ...

  6. jquery手写轮播图_用jQuery如何手写一个简单的轮播图?(附代码)

    用jQuery如何手写一个简单的轮播图?下面本篇文章通过代码示例来给大家介绍一下.有一定的参考价值,有需要的朋友可以参考一下,希望对大家有所帮助. 用 jQuery 手写轮播图 先上个效果截图: 主要 ...

  7. 从0-1带你手写代码生成器(Java版)

    前言 在实际的项目开发中,根据数据库表创建实体.service.controller等结构是一件非常繁琐的事.所以我们经常需要使用到各种代码生成器,例如mybatis-plus,若依等框架都有自己的代 ...

  8. 怎么手写一个简单的List集合

    List集合 手写一个简单的List集合为自己调用并不是特别难,只需要定义一个集合接口去提供所有方法的定义如下代码 : package com.myself.util; /*** * @author ...

  9. 基于bio手写实现简单的rpc

    基于bio手写实现简单的rpc 1.bio基础知识 Java BIO:传统的网络通讯模型,就是BIO,同步阻塞IO, 其实就是服务端创建一个ServerSocket, 然后就是客户端用一个Socket ...

最新文章

  1. 工业机器人抓取时怎么定位的?用什么传感器来检测?
  2. FPGA/IC Technology Exchange
  3. telegraf输出MySQL_如何使用Telegraf拖尾远程日志文件
  4. java multivaluemap_java – 使用自定义值集合类型创建Commons Collections MultiValueMap
  5. 前端学习(3169):react-hello-react之对props进行限制
  6. 论文浅尝 | ICLR2020 - 基于组合的多关系图卷积网络
  7. expires为session_面试必问:session,cookie和token的区别
  8. 【Python】数据转换利器
  9. 通用窗口类 Inventory Pro 2.1.2 Demo1(中)
  10. rk399_android7.1的mipi驱动代码追踪(部分)
  11. 2020年勒索病毒事件盘点及未来发展趋势
  12. 学习语文必须掌握的知识点思维导图
  13. 3d数字孪生工厂可视化三维建模平台
  14. html5 css 插入视频,HTML+CSS入门 HTML网页中插入视频各种方法
  15. c语言万年历方案论证,C语言编写方案-万年历分析.doc
  16. 云智慧透视宝Java代码性能监控实现原理
  17. HTML 图片热点map area使用方法
  18. 少儿机器人编程课程学什么
  19. Jepg转DICOM
  20. GPU计算主板学习资料保存第735篇:基于3U VPX的AGX Xavier GPU计算主板

热门文章

  1. linux 创建分区 4t,centos对4T硬盘进行分区
  2. 钉钉邮箱登录入口_阿里企业邮箱发信失败 报错:554reject by content_「阿里云企业邮箱_阿里企业邮箱」指定经销商热线:400-855...
  3. [转贴]eclipse和netbeans的区别
  4. 【最全面教程】搞定配置MySQL的各种幺蛾子!!
  5. jzoj 4883. 【NOIP2016提高A组集训第12场11.10】灵知的太阳信仰
  6. 洛谷P2698 Flowerpot S
  7. Unity3D 安装
  8. Java面向对象程序设计实训教程_JAVA课程实验报告 实验二 JAVA面向对象程序设计...
  9. 获取物料批次特性取值BAPI_SAP刘梦_新浪博客
  10. 接口 索引签名 接口与类型别名的异同 接口的继承 函数接口