IoC 反转控制原则也被叫做依赖注入 DI, 容器按照配置注入实例化的对象.

假设 A 的相互依赖关系如下图, 如何将 A 对象实例化并注入属性.

本文将实现一个轻量化的 IoC 容器, 完成对象的实例化和注入, 基于注解不依赖于任何库. (注解参考 JSR-330)

前提 JSR-330

注解 说明
@Inject 标识可注入的字段
@Named 基于字符串的限定符, 表示需要 IoC 接管的类

JSR-330 远比前提中提到的更多, 可以看下官方的解释说明, 这里只截取了本文目的需要开发的部分.

类定义

按照背景中的依赖关系图, 先定义出来对象.

@Named("a")
public class A {
@Inject
public B b;
@Inject
public C c;
// getter and setter
// constructor
} @Named("b")
public class B {
@Value("hello world!")
public String name;
// getter and setter
// constructor
} @Named("c")
public class C {
@Inject
public A a;
// getter and setter
// constructor
}

为了清晰, 这里省略了构造器和 setter 函数, 这些对于实现是必要的, 如果需要完整代码可以参照项目 xnuc-insni.

先考虑简单情况, AB 的相互依赖如何实现.

注解定义

注解定义参照 inject, 这里只截取了需要的部分.

@Target(FIELD)
@Retention(RUNTIME)
public @interface Inject {} @Target(TYPE)
@Retention(RUNTIME)
public @interface Named {
String value() default "";
}

对于简单类型, 可以提供一个设定的数值, 使用 Val 注解完成.

@Target(FIELD)
@Retention(RUNTIME)
public @interface Value {
String value() default "";
}

容器定义

容器定义很简单, 有一个实例的表和类定义的表.

public class Context {
public HashMap<String, Object> instances; // 实例
public HashMap<String, Class<?>> defineds; // 类定义
}

获取类定义

获取类定义用到反射和注解, 不了解相关知识的同学可以先补一下这部分. 如果要获取类定义, 最简单的方法就是找到全部类进行类加载. 首先获取主类加载器, 找到全部 .class 路径.

Enumeration<URL> resources = Main.class.getClassLoader().getResources(pkg.replace(".", "/"));
File file = new File(resources.nextElement().getFile());

获取全部包下的全部类, 存在子包的情况, 可以用递归或者队列, 最开始用的队列, 但是发现队列对于子包处理时非常复杂的, 需要根据队列信息维护当前包名. 递归的系统栈会帮我们记录下来自然就不需要我们自己维护了, 选择递归的方式处理子包.

private void subdir(String pkg, File file, List<Class<?>> clzes) throws Exception {
for (File f : file.listFiles()) { // 退出条件
if (f.isFile()) {
String clsName = String.format("%s.%s", pkg, f.getName().substring(0, f.getName().lastIndexOf(".")));
clzes.add(Class.forName(clsName));
}
if (f.isDirectory())
subdir(String.format("%s.%s", pkg, f.getName()), f, clzes);
}
}

这里选择了参数传返回值, 更好的方式还是直接内部将 list 构造出来, 返回出去.

拿到全部类后, 将有存在注解的类筛选出来. 放入 Contextdefineds.

for (Class<?> c : clzList) {
if (c.getAnnotation(Named.class) != null) {
defineds.put(c.getAnnotation(Named.class).value(), c);
}
}

此时初始化完毕, 类定义获取到. 另外, 其实这里已经可以开始注入了, 但是真实情况下, 如果类定义比较多, 那么初始化将非常耗时, 如果选择用到再说的原则, 初始化就会快很.

用到再说

Context#get 用来获取容器对象, 如果对象还没有实例化, 就实例化. 实例化 instance 实现比较简单, 找到定义的 filed 进行 set. 这也解释了, 为什么需要无参构造器和 setter. 对于基础值也可以通过 @Value 主动赋予自定义的数值. 对于 @Inject 直接去 Context#get即可.

private Object instance(Object rto, Class<?> clz) throws Exception {
for (Field field : clz.getFields()) {
if (field.getAnnotation(Value.class) != null) {
PropertyDescriptor descriptor = new PropertyDescriptor(field.getName(), clz);
descriptor.getWriteMethod().invoke(rto, field.getAnnotation(Value.class).value());
}
if (field.getAnnotation(Inject.class) != null) {
PropertyDescriptor descriptor = new PropertyDescriptor(field.getName(), clz);
descriptor.getWriteMethod().invoke(rto, get(field.getName()));
}
}
return rto;
}

写完 instance 的逻辑, get 的逻辑就比较简单了. 有返回, 没有实例化.

public Object get(String objName) throws Exception {
if (instances.get(objName) != null)
return instances.get(objName); if (defineds.get(objName) == null)
throw new Exception(String.format("objName %s undefined", objName));
Class<?> clz = defineds.get(objName);
instances.put(objName, instance(unreadyInstances.get(objName), clz));
return instances.get(objName);
}

这样只能解决 AB 的问题, 对于 AC 的问题这样就会导致注入 A 时发现需要注入 C, 而注入 C 时又要去注入 A, 最终导致循环.

依赖循环

循环依赖解决方法很简单, 只需要一个表记录下我现在正在注入 A, 所以 C 注入 A 的时候直接把正在注入的 A 丢给 C 即可.

所以新增 Context 成员 public HashMap<String, Object> unreadyInstances

public class Context {

	public HashMap<String, Object> instances;
++ public HashMap<String, Object> unreadyInstances;
public HashMap<String, Class<?>> defineds;
}

注入前先把这个对象扔进去, 注入时如果其他对象有循环依赖, Context#get 可以直接返回这个对象.

public Object get(String objName) throws Exception {
if (instances.get(objName) != null)
return instances.get(objName);
++ if (unreadyInstances.get(objName) != null)
++ return unreadyInstances.get(objName); if (defineds.get(objName) == null)
throw new Exception(String.format("objName %s undefined", objName));
Class<?> clz = defineds.get(objName); ++ unreadyInstances.put(objName, clz.getDeclaredConstructor().newInstance());
instances.put(objName, instance(unreadyInstances.get(objName), clz));
++ unreadyInstances.remove(objName); return instances.get(objName);
}

最终的代码就是这样了, 写个 Main 类, 运行下.

public class Main {
public static void main(String[] args) throws Exception {
Context ioc = Context.run(Main.class);
A a = (A) ioc.get("a");
System.out.println(a.getC().getA().getB().getName()); // >hello world!
}
}

全部代码可以参考 xnuc - insni 喜欢可以帮忙点个 Star.

引子 - 实现轻量的 ioc 容器的更多相关文章

  1. 曹工说Tomcat4:利用 Digester 手撸一个轻量的 Spring IOC容器

    一.前言 一共8个类,撸一个IOC容器.当然,我们是很轻量级的,但能够满足基本需求.想想典型的 Spring 项目,是不是就是各种Service/DAO/Controller,大家互相注入,就组装成了 ...

  2. 通过中看不中用的代码分析Ioc容器,依赖注入....

    /** * 通过生产拥有超能力的超人实例 来理解IOC容器 */ //超能力模组接口 interface SuperModuleInterface{ public function activate( ...

  3. IoC容器Autofac - Autofac + Asp.net MVC + EF Code First(转载)

    转载地址:http://www.cnblogs.com/JustRun1983/archive/2013/03/28/2981645.html  有修改 Autofac通过Controller默认构造 ...

  4. 比Spring简单的IoC容器

    比Spring简单的IoC容器 Spring 虽然比起EJB轻量了许多,但是因为它需要兼容许多不同的类库,导致现在Spring还是相当的庞大的,动不动就上40MB的jar包, 而且想要理解Spring ...

  5. laravel5.8 IoC 容器

    网上 对容器的解释有很多,这里只是记录,搬运! 1.简单理解: 2019-10-10 11:24:09 解析 lavarel 容器 IoC 容器 作用 就是 “解耦” .“依赖注入(DI) IoC 容 ...

  6. 【曹工杂谈】Maven IOC容器的下半场:Google Guice

    Maven容器的下半场:Guice 前言 在前面的文章里,Maven底层容器Plexus Container的前世今生,一代芳华终落幕,我们提到,在Plexus Container退任后,取而代之的底 ...

  7. 【最简单IOC容器实现】实现一个最简单的IOC容器

    前面DebugLZQ的两篇博文: 浅谈IOC--说清楚IOC是什么 IoC Container Benchmark - Performance comparison 在浅谈IOC--说清楚IOC是什么 ...

  8. 挖坟之Spring.NET IOC容器初始化

    因查找ht项目中一个久未解决spring内部异常,翻了一段时间源码.以此文总结springIOC,容器初始化过程. 语言背景是C#.网上有一些基于java的spring源码分析文档,大而乱,乱而不全, ...

  9. SqlSugar轻量ORM

      蓝灯软件数据股份有限公司项目,代码开源. SqlSugar是一款轻量级的MSSQL ORM ,除了具有媲美ADO的性能外还具有和EF相似简单易用的语法. 学习列表 0.功能更新 1.SqlSuga ...

  10. IOC容器MEF在MVC中的使用

    最近想把自己的网站框架用IOC改造下,经过对比,我初步选择autofac,虽然MEF不需要配置,但性能不行,autofac虽然需要自己写自动化注入,但性能非常好. 先分析下各大IOC框架的性能,分两类 ...

随机推荐

  1. 在FreeSQL中实现「触发器」和软删除功能

    前言 最近做新项目,技术栈 AspNetCore + FreeSQL 这个ORM真的好用,文档也很完善,这里记录一下两个有关「触发器」的功能实现 修改实体时记录更新时间 模型代码 我的模型都是基于这个 ...

  2. 云小课|MRS基础原理之MapReduce介绍

    阅识风云是华为云信息大咖,擅长将复杂信息多元化呈现,其出品的一张图(云图说).深入浅出的博文(云小课)或短视频(云视厅)总有一款能让您快速上手华为云.更多精彩内容请单击此处. 摘要:MapReduce ...

  3. C语言白盒测试讲义

    好久没有做过C语言的白盒测试了,估计以后也没这个机会.把自己之前参加过的培训素材做个分享. 素材下载链接:https://pan.baidu.com/s/1LPD9Az04zEj8RuCICaKYxQ ...

  4. 第一百零八篇:最常用的基本数据类型(Number,String类型)

    好家伙, 1.Number类型 从名称中我们可以得出,这是一个存放数值的类型, 不同的数值类型相应地也有不同的数值字面量格式 number类型可以储存不同进制的数(不常用就是了) 八进制:在数前加一个 ...

  5. 阿里技术专家详解 DDD 系列- Domain Primitive

    简介: 关于DDD的一系列文章,希望能继续在总结前人的基础上发扬光大DDD的思想,但是通过一套我认为合理的代码结构.框架和约束,来降低DDD的实践门槛,提升代码质量.可测试性.安全性.健壮性. 作者| ...

  6. 基于python的数学建模---logicstic回归

    樱花数据集的Logistic回归 绘制散点图 import matplotlib.pyplot as plt import numpy as np from sklearn.datasets impo ...

  7. 【深入浅出 Yarn 架构与实现】4-1 ResourceManager 功能概述

    前面几篇文章对 Yarn 基本架构.程序基础库.应用设计方法等进行了介绍.之后几篇将开始对 Yarn 核心组件进行剖析. ResourceManager(RM)是 Yarn 的核心管理服务,负责集群管 ...

  8. 在Windows模拟器中使用LVGL8.3

    引言 LVGL是一个跨平台.轻量级.易于移植的图形库.也因其支持大量特性和其易于裁剪,配置开关众多,且版本升级较快,不同版本之间存在一定的差异性,相关的使用教程有一定的滞后性,由于缺少最新版本的中文教 ...

  9. jquery 简单分页插件jQuerypage

    昨天项目手机端要用到table的分页,考虑到手机端界面小,系统数据不多,在没考虑大批量数据处理的前提前就下载了这个插件,简单. 展示数据datas为json格式. <!DOCTYPE html& ...

  10. jmeter ORA-00911: invalid character报错解决方法

    今天通过jmeter进行Oracle数据库操作时,遇到一个小坑. 解决办法:去掉sql最后的分号.