信不信, 200 行 Java 代码就能实现依赖注入框架

2018-05-02 08:19:06 +08:00
 shellquery

依赖注入框架并不神秘,其实它是非常简单的东西。不要去看 spring 的依赖注入源码,因为你只要一去看就意味着你再也写不敢下手自己撸了,它的功能因为过于强大,所以设计也过于复杂,普通程序员一眼看去只能望洋兴叹。

我也并没有去细致阅读 spring 源码。即便如此也只用了半天的时间便自己撸了一个基本满足标准依赖注入规范「 JSR-330 」的小框架 iockids。这个小框架只有一个主类 Injector,大约 200 行代码,它具备以下功能。

  1. 单例 /非单例注入
  2. 构造器注入
  3. 字段注入
  4. 循环依赖注入
  5. 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 提供的所有功能。

  1. Root/NodeA/NodeB 类是单例类
  2. Leaf 类是非单例类
  3. 它们都使用了字段注入
  4. NodeB 使用了构造器注入
  5. NodeA 和 NodeB 还使用了 Qualifier 名称注入
  6. Leaf 类中有 Root 类型的字段,这便是循环依赖
  7. 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

4013 次点击
所在节点    程序员
11 条回复
mjStudio
2018-05-02 09:26:07 +08:00
Android 开发路过
omengye
2018-05-02 09:26:41 +08:00
大佬的 trySetAccessible 方法是只能用在 Java9 以上版本?
shellquery
2018-05-02 09:43:33 +08:00
准确的讲,整个项目只能运行在 java10,注意 var 关键字
shellquery
2018-05-02 10:19:30 +08:00
https://github.com/pyloque/httpkids 自己撸 web 框架
https://github.com/pyloque/ormkids 自己撸 orm 框架
https://github.com/pyloque/rpckids 自己撸 rpc 框架
还在犹豫什么呢,关注公众号「码洞」吧
des
2018-05-02 10:22:19 +08:00
@kai 移动到推广节点
exalex
2018-05-02 11:31:37 +08:00
@shellquery 关注了
arthas2234
2018-05-02 11:49:24 +08:00
。。。Java10,我们公司 Java8 都不愿意上
shellquery
2018-05-02 12:59:51 +08:00
@arthas2234 公司跟不上时代,但是我们自己可以跟得上
THP301
2018-05-03 12:07:53 +08:00
收藏了
AllOfMe
2018-05-04 09:43:04 +08:00
你这个照片看的我有点慌
shellquery
2018-05-04 12:16:36 +08:00
@AllOfMe 要的就是这效果

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/451321

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX