200行Java代码实现依赖注入框架

依赖注入框架并不神秘,其实它是非常简单的东西。不要去看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

原文发布于微信公众号 - 码洞(codehole)

原文发表时间:2018-05-02

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏Kubernetes

kube-proxy源码分析

##kube-proxy介绍 请参考我的另一篇博文:kube-proxy工作原理 ##源码目录结构分析 cmd/kube-proxy //负责kub...

78250
来自专栏我是攻城师

ElasticSearch之Java Api聚合分组实战

48460
来自专栏linux驱动个人学习

android 休眠唤醒机制分析(一) — wake_lock【转】

Android的休眠唤醒主要基于wake_lock机制,只要系统中存在任一有效的wake_lock,系统就不能进入深度休眠,但可以进行设备的浅度休眠操作。wak...

21430
来自专栏开发与安全

浅谈原始套接字 SOCK_RAW 的内幕及其应用(port scan, packet sniffer, syn flood, icmp flood)

一、SOCK_RAW 内幕 首先在讲SOCK_RAW 之前,先来看创建socket 的函数: int socket(int domain, int type, ...

1.2K00
来自专栏ios 技术积累

iOS AFNetworking 源码阅读一

大名鼎鼎的AFNetWorking,做iOS开发的人都知道吧。 AFNetWorking一款轻量级网络请求开源框架,基于iOS和mac os 网络进行扩展的高...

18630
来自专栏乐沙弥的世界

替代变量与SQL*Plus环境设置

scott@ORCL> select * from emp where empno=7788;

8520
来自专栏GIS讲堂

Arcgis4js实现链家找房的效果

买房的各位亲们不知是否留意过链家的"地图找房",这样的功能对于使用者来说,是非常方便的,大家可通过连接(https://bj.lianjia.com/ditu/...

12920
来自专栏wannshan(javaer,RPC)

ConcurrentHashMap 锁分段 源码分析

看ConcurrentHashMap下几个属性: /** * The default concurrency level for this table...

42360
来自专栏王硕

原 透过pageinspect了解Post

389150
来自专栏aCloudDeveloper

python网络编程初级

网络编程的专利权应该属于Unix,各个平台(如windows、Linux等)、各门语言(C、C++、Python、Java等)所实现的符合自身特性的语法都大同小...

25350

扫码关注云+社区

领取腾讯云代金券