200行Java代码如何实现依赖注入框架详解

时间:2021-09-06 17:42:41

依赖注入介绍

先回顾下依赖注入的概念:

我们常提起的依赖注入(dependency injection)和控制反转(inversion of control)是同一个概念。具体含义是:当某个角色(可能是一个java实例,调用者)需要另一个角色(另一个java实例,被调用者)的协助时,在 传统的程序设计过程中,通常由调用者来创建被调用者的实例。但在spring里,创建被调用者的工作不再由调用者来完成,因此称为控制反转;创建被调用者 实例的工作通常由spring容器来完成,然后注入调用者,因此也称为依赖注入。

其实简单的说,依赖注入起到的作用就是讲对象之间的依赖关系从原先的代码中解耦出来,通过配置文件或注解等方式加上spring框架的处理让我们对依赖关系灵活集中的进行管理。

依赖注入框架

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

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

  1. 单例/非单例注入
  2. 构造器注入
  3. 字段注入
  4. 循环依赖注入
  5. qualifier注入

我们看一个稍微复杂一点的使用示例

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
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代码如何实现依赖注入框架详解

上面的代码输出如下

?
1
root(nodeawithb(leafwithroot0), nodebwitha(leafwithroot1))

从这个输出中,我们也可以大致想象出依赖结构。

iockids提供了丰富的注入错误异常报告,防止用户注入配置出错。

比如我们将上面的nodea和nodeb的名称都配置成一样的a,就会曝出下面的错误堆栈

?
1
2
3
4
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的构造器随意加一个参数

?
1
2
3
4
@inject
public nodeb(leaf leaf, int k) {
 this.leaf = leaf;
}

运行时就会抛出下面的错误

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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