200行Java代码如何实现依赖注入框架详解
作者:老钱 发布时间:2022-08-28 01:22:42
依赖注入介绍
先回顾下依赖注入的概念:
我们常提起的依赖注入(Dependency Injection)和控制反转(Inversion of Control)是同一个概念。具体含义是:当某个角色(可能是一个Java实例,调用者)需要另一个角色(另一个Java实例,被调用者)的协助时,在 传统的程序设计过程中,通常由调用者来创建被调用者的实例。但在Spring里,创建被调用者的工作不再由调用者来完成,因此称为控制反转;创建被调用者 实例的工作通常由Spring容器来完成,然后注入调用者,因此也称为依赖注入。
其实简单的说,依赖注入起到的作用就是讲对象之间的依赖关系从原先的代码中解耦出来,通过配置文件或注解等方式加上Spring框架的处理让我们对依赖关系灵活集中的进行管理。
依赖注入框架
依赖注入框架并不神秘,其实它是非常简单的东西。不要去看spring的依赖注入源码,因为你只要一去看就意味着你再也写不敢下手自己撸了,它的功能因为过于强大,所以设计也过于复杂,普通程序员一眼看去只能望洋兴叹。
我也并没有去细致阅读spring源码。即便如此也只用了半天的时间便自己撸了一个基本满足标准依赖注入规范「JSR-330」的小框架iockids。这个小框架只有一个主类Injector,大约200行代码,它具备以下功能。
单例/非单例注入
构造器注入
字段注入
循环依赖注入
Qualifier注入
我们看一个稍微复杂一点的使用示例
import javax.inject.Inject;
import javax.inject.Named;
import javax.inject.Singleton;
import iockids.Injector;
@Singleton
class Root {
@Inject
@Named("a")
Node a;
@Inject
@Named("b")
Node b;
@Override
public String toString() {
return String.format("root(%s, %s)", a.name(), b.name());
}
}
interface Node {
String name();
}
@Singleton
@Named("a")
class NodeA implements Node {
@Inject
Leaf leaf;
@Inject
@Named("b")
Node b;
@Override
public String name() {
if (b == null)
return String.format("nodeA(%s)", leaf);
else
return String.format("nodeAWithB(%s)", leaf);
}
}
@Singleton
@Named("b")
class NodeB implements Node {
Leaf leaf;
@Inject
@Named("a")
Node a;
@Inject
public NodeB(Leaf leaf) {
this.leaf = leaf;
}
@Override
public String name() {
if (a == null)
return String.format("nodeB(%s)", leaf);
else
return String.format("nodeBWithA(%s)", leaf);
}
}
class Leaf {
@Inject
Root root;
int index;
static int sequence;
public Leaf() {
index = sequence++;
}
public String toString() {
if (root == null)
return "leaf" + index;
else
return "leafwithroot" + index;
}
}
public class Demo {
public static void main(String[] args) {
var injector = new Injector();
injector.registerQualifiedClass(Node.class, NodeA.class);
injector.registerQualifiedClass(Node.class, NodeB.class);
var root = injector.getInstance(Root.class);
System.out.println(root);
}
}
上面这份代码用到了iockids提供的所有功能。
Root/NodeA/NodeB类是单例类
Leaf类是非单例类
它们都使用了字段注入
NodeB使用了构造器注入
NodeA和NodeB还使用了Qualifier名称注入
Leaf类中有Root类型的字段,这便是循环依赖
NodeA中有NodeB字段,NodeB中有NodeA字段,这也是循环依赖
为了便于理解上述代码,我画了依赖图
上面的代码输出如下
root(nodeAWithB(leafwithroot0), nodeBWithA(leafwithroot1))
从这个输出中,我们也可以大致想象出依赖结构。
iockids提供了丰富的注入错误异常报告,防止用户注入配置出错。
比如我们将上面的NodeA和NodeB的名称都配置成一样的a,就会曝出下面的错误堆栈
iockids.InjectException: duplicated qualifier javax.inject.Named with the same class iockids.demo.Node
at iockids.Injector.registerQualifiedClass(Injector.java:87)
at iockids.Injector.registerQualifiedClass(Injector.java:70)
at iockids.demo.Demo.main(Demo.java:106)
如果我们将NodeB的构造器随意加一个参数
@Inject
public NodeB(Leaf leaf, int k) {
this.leaf = leaf;
}
运行时就会抛出下面的错误
iockids.InjectException: no accessible constructor for injection class int
at iockids.Injector.createNew(Injector.java:120)
at iockids.Injector.createNew(Injector.java:94)
at iockids.Injector.createFromParameter(Injector.java:167)
at iockids.Injector.createFromConstructor(Injector.java:145)
at iockids.Injector.createNew(Injector.java:123)
at iockids.Injector.createFromQualified(Injector.java:216)
at iockids.Injector.createFromField(Injector.java:173)
at iockids.Injector.injectMembers(Injector.java:233)
at iockids.Injector.createNew(Injector.java:136)
at iockids.Injector.createFromQualified(Injector.java:216)
at iockids.Injector.createFromField(Injector.java:173)
at iockids.Injector.injectMembers(Injector.java:233)
at iockids.Injector.createNew(Injector.java:136)
at iockids.Injector.createNew(Injector.java:94)
at iockids.Injector.getInstance(Injector.java:245)
at iockids.demo.Demo.main(Demo.java:107)
项目开源地址:https://github.com/pyloque/iockids (本地下载)
来源:https://mp.weixin.qq.com/s/R14Xaq2iSUbVphdVtRiyjg


猜你喜欢
- 一、枚举类类的对象只有有限个, 确定的. 我们称此类为枚举类.说明:1.类的对象只有有限个,确定的。如:星期:Monday(星期一)、…、S
- 前言好几天没写了,工作有点忙,最近工作刚好做一个定时任务统计的,所以就将springboot 如何创建定时任务整理了一下。总的来说,spri
- 在之前,已经学习到了线程的创建和状态控制,但是每个线程之间几乎都没有什么太大的联系。可是有的时候,可能存在多个线程多同一个数据进行操作,这样
- 这是我们用得比较多的一种设计模式,也是23种标准设计模式之一,使用前面讲的简单工厂设计模式,遇到具体产品经常变换时就不太适合了,违反了开闭设
- C#支持的位逻辑运算符如表2.9所示。运算符号意义运算对象类型运算结果类型对象数实例~位逻辑非运算整型,字符型整型1~a&位逻辑与运
- 1. this 引用1.1 为什么要有this引用先来写一个日期类的例子:public class classCode { &
- String:字符串类型1、构造函数。String() :构造一个空字符串对象。String(byte[] bytes) :通过byte数组
- 本文实例讲述了Android开发实现查询远程服务器的工具类QueryUtils。分享给大家供大家参考,具体如下:/** * 查询远程服务器的
- 前言在一般能搜到的所有实现圆角窗体的示例中,都是通过绘制圆角的路径,并创建对应的窗体Region区域实现。目前所知,重新创建Region的所
- Java中有哪些队列ArrayBlockingQueue 使用ReentrantLockLinkedBlockingQueue 使用Reen
- 本文实例讲述了Android实现向Launcher添加快捷方式的方法。分享给大家供大家参考。具体如下:当我们在应用程序Launcher的桌面
- 表达式目录树表达式目录树:语法树,或者说是一种数据结构1.表达式目录树Expression:System.Linq.Expressions;
- 使用System.Threading.Thread类可以创建和控制线程。常用的构造函数有: // 摘要: // 初
- 本文实例讲述了C#内置队列类Queue用法。分享给大家供大家参考。具体分析如下:这里详细演示了C#内置的队列如何进行添加,移除等功能。usi
- 总结一下java使用http发送post的方法:1、post请求用于发送json 格式的参数:/** * post请求(用于请
- 今天新建项目的时候突然发现编译后运行按钮为灰色。解决方案:第一步:点击图中的Add Configuration,出来如下界面第二步:点+号,
- 百度百科说法:Servlet(Server Applet)是Java Servlet的简称,称为小服务程序或服务连接器,用Java编写的服务
- 在Servlet2.5中,我们要实现文件上传功能时,一般都需要借助第三方开源组件,例如Apache的commons-fileupload组件
- 【1】首先我们定义一段假数据,这里以一个string为例字static void Main(string[] args){string da
- 测试参数设置:1、循环调用new A()实现堆溢出,java.lang.OutOfMemoryError: Java heap space,