200行Java程式碼實現依賴注入框架

老錢發表於2018-05-01

依賴注入框架並不神祕,其實它是非常簡單的東西。不要去看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欄位,這也是迴圈依賴

為了便於理解上述程式碼,我畫了依賴圖

200行Java程式碼實現依賴注入框架

上面的程式碼輸出如下

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

200行Java程式碼實現依賴注入框架

關注公眾號「碼洞」,和大佬們一起來討論iockids的設計與實現

相關文章