徐昊 · TDD 项目实战 70 讲
徐昊
Thoughtworks 中国区 CTO
18159 人已学习
新⼈⾸单¥98
登录后,你可以任选4讲全文学习
课程目录
已完结/共 88 讲
实战项目二|RESTful开发框架:依赖注入容器 (24讲)
实战项目三|RESTful Web Services (44讲)
徐昊 · TDD 项目实战 70 讲
15
15
1.0x
00:00/00:00
登录|注册

15|DI Container(3):如何重构已有的代码?

你好,我是徐昊。今天我们继续使用 TDD 的方式来实现注入依赖容器。

回顾代码与任务列表

到目前为止,我们的代码是这样的:
package geektime.tdd.di;
import jakarta.inject.Inject;
import jakarta.inject.Provider;
import java.lang.reflect.Constructor;
import java.util.HashMap;
import java.util.Map;
import static java.util.Arrays.stream;
public class Context {
private Map<Class<?>, Provider<?>> providers = new HashMap<>();
public <Type> void bind(Class<Type> type, Type instance) {
providers.put(type, (Provider<Type>) () -> instance);
}
public <Type, Implementation extends Type>
void bind(Class<Type> type, Class<Implementation> implementation) {
Constructor<?>[] injectConstructors = stream(implementation.getConstructors()).filter(c -> c.isAnnotationPresent(Inject.class))
.toArray(Constructor<?>[]::new);
if (injectConstructors.length > 1) throw new IllegalComponentException();
if (injectConstructors.length == 0 && stream(implementation.getConstructors())
.filter(c -> c.getParameters().length == 0).findFirst().map(c -> false).orElse(true))
throw new IllegalComponentException();
providers.put(type, (Provider<Type>) () -> {
try {
Constructor<Implementation> injectConstructor = getInjectConstructor(implementation);
Object[] dependencies = stream(injectConstructor.getParameters())
.map(p -> get(p.getType()))
.toArray(Object[]::new);
return injectConstructor.newInstance(dependencies);
} catch (Exception e) {
throw new RuntimeException(e);
}
});
}
private <Type> Constructor<Type> getInjectConstructor(Class<Type> implementation) {
return (Constructor<Type>) stream(implementation.getConstructors())
.filter(c -> c.isAnnotationPresent(Inject.class)).findFirst().orElseGet(() -> {
try {
return implementation.getConstructor();
} catch (NoSuchMethodException e) {
throw new RuntimeException(e);
}
});
}
public <Type> Type get(Class<Type> type) {
return (Type) providers.get(type).get();
}
}
任务列表状态为:
无需构造的组件——组件实例
如果注册的组件不可实例化,则抛出异常
抽象类
接口
构造函数注入
无依赖的组件应该通过默认构造函数生成组件实例
有依赖的组件,通过 Inject 标注的构造函数生成组件实例
如果所依赖的组件也存在依赖,那么需要对所依赖的组件也完成依赖注入
如果组件有多于一个 Inject 标注的构造函数,则抛出异常
如果组件没有 Inject 标注的构造函数,也没有默认构造函数(新增任务)
如果组件需要的依赖不存在,则抛出异常
如果组件间存在循环依赖,则抛出异常
字段注入
通过 Inject 标注将字段声明为依赖组件
如果组件需要的依赖不存在,则抛出异常
如果字段为 final 则抛出异常
如果组件间存在循环依赖,则抛出异常
方法注入
通过 Inject 标注的方法,其参数为依赖组件
通过 Inject 标注的无参数方法,会被调用
按照子类中的规则,覆盖父类中的 Inject 方法
如果组件需要的依赖不存在,则抛出异常
如果方法定义类型参数,则抛出异常
如果组件间存在循环依赖,则抛出异常
对 Provider 类型的依赖
注入构造函数中可以声明对于 Provider 的依赖
注入字段中可以声明对于 Provider 的依赖
注入方法中可声明对于 Provider 的依赖
自定义 Qualifier 的依赖
注册组件时,可额外指定 Qualifier
注册组件时,可从类对象上提取 Qualifier
寻找依赖时,需同时满足类型与自定义 Qualifier 标注
支持默认 Qualifier——Named
Singleton 生命周期
注册组件时,可额外指定是否为 Singleton
注册组件时,可从类对象上提取 Singleton 标注
对于包含 Singleton 标注的组件,在容器范围内提供唯一实例
容器组件默认不是 Single 生命周期
自定义 Scope 标注
可向容器注册自定义 Scope 标注的回调
确认放弃笔记?
放弃后所记笔记将不保留。
新功能上线,你的历史笔记已初始化为私密笔记,是否一键批量公开?
批量公开的笔记不会为你同步至部落
公开
同步至部落
取消
完成
0/2000
荧光笔
直线
曲线
笔记
复制
AI
  • 深入了解
  • 翻译
    • 英语
    • 中文简体
    • 中文繁体
    • 法语
    • 德语
    • 日语
    • 韩语
    • 俄语
    • 西班牙语
    • 阿拉伯语
  • 解释
  • 总结

本文介绍了如何使用TDD的方式来实现注入依赖容器的方法。通过对已有代码的重构,作者介绍了如何实现循环依赖的检测,并提出了一系列任务列表,包括抽象类、接口、构造函数注入、字段注入、方法注入、对Provider类型的依赖、自定义Qualifier的依赖、Singleton生命周期等。文章通过代码示例和任务列表的方式,帮助读者了解了DI Container的实现原理和相关技术特点。通过本文的学习,读者可以掌握如何使用TDD的方式来实现注入依赖容器,并了解如何重构已有的代码以满足不同的依赖注入需求。

仅可试看部分内容,如需阅读全部内容,请付费购买文章所属专栏
《徐昊 · TDD 项目实战 70 讲》
新⼈⾸单¥98
立即购买
登录 后留言

全部留言(7)

  • 最新
  • 精选
  • 小5
    置顶
    学习了这节课的内容,想到了之前章节的一段话: Kent Beck 作为极限编程(Exetreme Programming)的创始人,将勇气(Courage)作为极限编程的第一原则,提出编程的第一大敌是恐惧(Fear),实在是有非凡的洞见。同时,他也花了极大的篇幅,说明为什么 TDD 可以让我们免于恐惧:重构使得我们在实现功能时,不恐惧于烂代码;测试使得我们在重构时,不恐惧于功能破坏。 我们平时常说:代码看不懂、改不动、不敢改。 看不懂是指认知负载太高,导致代码很难改,改了一个地方影响很多地方,甚至不知道影响多少地方,也就自然不敢改了,改出问题了谁负责,改一个bug改出多了几个新bug,以前公司老人常说千万不要在现有的代码上改,要把方法copy一份,然后再改,调用新的方法😂,多么痛的领悟。 在做练习的重构过程中明白了因为对所有的代码都有测试,改了代码跑一下如果有错误就很容易发现,所以给了我们改代码的勇气。
    2022-09-25归属地:广东
    2
  • escray
    置顶
    进入到第二阶段的课程之后,给出的代码相对比较详细,虽然实现的功能比起前面要复杂一些,但是跟上节奏就不那么困难了。 这个专栏的门槛还是挺高的。 实现循环依赖检测,比较容易想到的就是在每个依赖添加一个标志位,但是这个似乎不那么容易实现;那么另一个方式,就是增加一个记录依赖的列表,每次都去判断是否存在循环依赖。 代码不太会写,只能先想一下。 本课的代码链接:https://github.com/escray/TDDCourse/tree/ch15
    2022-04-28
    3
  • 汗香
    获取一个依赖前,先从已有依赖获取, 若获取结果不为空则获取成功, 若获取结果为空,先将被获取依赖类型放入一个待创建集合中,并判断当前组件本身是否在集合中,若在,说明有循环依赖
    2022-04-25
    2
  • 努力努力再努力
    哭了,上一个留言发现有问题,自己实现的时候才发现单纯一个 set 集合记录正在创建的bean还不行,得利用 TLS(https://time.geekbang.org/column/article/93745) 模式做线程隔离,否则多线程的测试用例下,就会出现明明没有循环依赖而报错的问题。
    2022-09-23归属地:广东
  • 努力努力再努力
    循环依赖,可以加一个 bean 在创建中的标识,当 bean 被创建完之后,就把标识去掉,这样如果创建过程中发现这个 bean 正在创建中,就可以认为是发生循环依赖了
    2022-09-23归属地:广东
  • 蝴蝶
    @Inject注解的构造方法如果没有参数,那肯定是有问题的,这次看明白了。
    2022-08-13归属地:广东
  • davix
    get() 重構受啟發!雖然看過《重構》了解其中的方法,但平時如果get()設計變化我通常還是直接改。這回直觀感受引入get_()的好處。
    2022-05-25
收起评论
显示
设置
留言
7
收藏
沉浸
阅读
分享
手机端
快捷键
回顶部