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行代码,它具备以下功能。

  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 (本地下载)

来源:https://mp.weixin.qq.com/s/R14Xaq2iSUbVphdVtRiyjg

标签:java,依赖注入,框架
0
投稿

猜你喜欢

  • Android继承现有控件拓展实现自定义控件textView

    2021-05-29 06:29:15
  • Java实现年兽大作战游戏详解

    2023-11-08 04:28:05
  • java实现简单的验证码功能

    2023-08-06 09:21:44
  • Spring boot2+jpa+thymeleaf实现增删改查

    2021-06-02 07:21:49
  • springboot项目部署到宝塔的详细图文教程

    2023-03-27 05:24:31
  • Java应用/JVM宕机排查步骤操作

    2021-08-10 14:52:10
  • 如何解决Spring in action @valid验证不生效的问题

    2023-08-29 07:59:56
  • Android中banner的使用步骤

    2023-07-04 17:47:23
  • C#用RabbitMQ实现消息订阅与发布

    2022-09-05 16:23:40
  • logcat命令使用方法和查看android系统日志缓冲区内容的方法

    2022-06-09 10:42:36
  • MyBatis使用动态表或列代码解析

    2023-06-13 07:57:40
  • Java经典面试题最全汇总208道(四)

    2023-11-08 23:59:26
  • spring mvc url匹配禁用后缀访问操作

    2021-11-19 23:57:34
  • Android RecyclerView使用GridLayoutManager间距设置的方法

    2023-03-20 07:32:44
  • C#简单爬虫案例分享

    2023-04-14 08:44:13
  • C# char类型字符转换大小写的实现代码

    2021-07-27 19:21:09
  • java组件commons-fileupload实现文件上传、下载、在线打开

    2022-02-24 22:16:59
  • 详解Java中使用泛型实现快速排序算法的方法

    2022-04-28 09:47:00
  • Android Q适配之IMEI替换为Android_id

    2022-08-08 00:46:01
  • JavaWeb ServletContext基础与应用详细讲解

    2021-12-20 22:13:10
  • asp之家 软件编程 m.aspxhome.com