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

19|DI Container(7):如何实现Field Injection部分的功能?

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

回顾代码与任务列表

到目前为止,我们的代码是这样的:
ContextConfig.java:
package geektime.tdd.di;
import java.util.*;
import static java.util.List.of;
public class ContextConfig {
private Map<Class<?>, ComponentProvider<?>> providers = new HashMap<>();
public <Type> void bind(Class<Type> type, Type instance) {
providers.put(type, new ComponentProvider<Type>() {
@Override
public Type get(Context context) {
return instance;
}
@Override
public List<Class<?>> getDependencies() {
return of();
}
});
}
public <Type, Implementation extends Type>
void bind(Class<Type> type, Class<Implementation> implementation) {
providers.put(type, new ConstructorInjectionProvider<>(implementation));
}
public Context getContext() {
providers.keySet().forEach(component -> checkDependencies(component, new Stack<>()));
return new Context() {
@Override
public <Type> Optional<Type> get(Class<Type> type) {
return Optional.ofNullable(providers.get(type)).map(provider -> (Type) provider.get(this));
}
};
}
private void checkDependencies(Class<?> component, Stack<Class<?>> visiting) {
for (Class<?> dependency: providers.get(component).getDependencies()) {
if (!providers.containsKey(dependency)) throw new DependencyNotFoundException(component, dependency);
if (visiting.contains(dependency)) throw new CyclicDependenciesFoundException(visiting);
visiting.push(dependency);
checkDependencies(dependency, visiting);
visiting.pop();
}
}
interface ComponentProvider<T> {
T get(Context context);
List<Class<?>> getDependencies();
}
}
ConstructorInjectionProvider.java:
package geektime.tdd.di;
import jakarta.inject.Inject;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Parameter;
import java.util.List;
import java.util.stream.Collectors;
import static java.util.Arrays.stream;
class ConstructorInjectionProvider<T> implements ContextConfig.ComponentProvider<T> {
private Constructor<T> injectConstructor;
public ConstructorInjectionProvider(Class<T> component) {
this.injectConstructor = getInjectConstructor(component);
}
private static <Type> Constructor<Type> getInjectConstructor(Class<Type> implementation) {
List<Constructor<?>> injectConstructors = stream(implementation.getConstructors())
.filter(c -> c.isAnnotationPresent(Inject.class)).collect(Collectors.toList());
if (injectConstructors.size() > 1) throw new IllegalComponentException();
return (Constructor<Type>) injectConstructors.stream().findFirst().orElseGet(() -> {
try {
return implementation.getConstructor();
} catch (NoSuchMethodException e) {
throw new IllegalComponentException();
}
});
}
@Override
public T get(Context context) {
try {
Object[] dependencies = stream(injectConstructor.getParameters())
.map(p -> context.get(p.getType()).get())
.toArray(Object[]::new);
return injectConstructor.newInstance(dependencies);
} catch (InvocationTargetException | InstantiationException | IllegalAccessException e) {
throw new RuntimeException(e);
}
}
@Override
public List<Class<?>> getDependencies() {
return stream(injectConstructor.getParameters()).map(Parameter::getType).collect(Collectors.toList());
}
}
Context.java:
package geektime.tdd.di;
import java.util.Optional;
public interface Context {
<Type> Optional<Type> get(Class<Type> type);
}
任务列表状态为:
无需构造的组件——组件实例
如果注册的组件不可实例化,则抛出异常
抽象类
接口
构造函数注入
无依赖的组件应该通过默认构造函数生成组件实例
有依赖的组件,通过 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
  • 深入了解
  • 翻译
    • 英语
    • 中文简体
    • 中文繁体
    • 法语
    • 德语
    • 日语
    • 韩语
    • 俄语
    • 西班牙语
    • 阿拉伯语
  • 解释
  • 总结

本文介绍了徐八叉关于DI容器的第七部分内容,重点讨论了如何实现Field Injection部分的功能。作者通过TDD的方式实现了注入依赖容器的功能,并展示了相关的代码和任务列表。文章中提到了任务列表中的各项功能实现状态,并提出了两个思考题,分别是如何实现Method Injection部分的功能以及读者对本节课的难易程度和遇到的问题。通过本文,读者可以了解到DI容器的实现原理和相关技术特点,对于想要深入了解DI容器的开发和实现的技术人员具有一定的参考价值。

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

全部留言(7)

  • 最新
  • 精选
  • 枫中的刀剑
    置顶
    这节个人感觉比较重要的就是对于同样的功能,在不同上下文环境下对测试风格的选择方式问题。 在某些情况下,不同的风格传递的信息或者说知识是不太一样的。而伴随你不同风格的选择可能直接影响后续功能实现的难易程度。TDD主要的难点还是在于设计,在于你对知识的理解,究竟是以一种怎样的方式呈现出来。

    作者回复: 所以才说是架构技术而不是编码技术

    2022-04-27
    3
  • 张铁林
    config.bind(ComponentWithFieldInjection.class, ComponentWithFieldInjection.class); 干嘛要把自己绑定到自己身上? 老师说:不像写个接口[抠鼻],懒。 我说我看得云里雾里,我在这给大家提个醒。
    2022-04-26
    1
  • 常文龙
    有点恍然大悟,TDD恰恰不是自上而下的分而治之,而是自下而上的不断调整
    2023-07-23归属地:广东
  • 奇小易
    思考: 在不同功能上下文内完成任务项时, 有时候在更大范围的功能上下文的测试更好,有时在更小范围的功能上下文的测试更好。 故选择在哪个功能上下文进行编写测试时,需要思考不同功能上下文编写的测试会有什么差别。 然后再决定在哪个功能上下文进行编写。
    2022-05-25
  • 新的一页
    难的还是思维的转变,比如对于测试的整理,多少才算够。
    2022-05-10
  • aoe
    在你看来,这节课的难易程度如何? 很早就对逻辑实现一知半解了,坚持跟着视频敲代码,运行测试。每当测试通过时都会感觉:哇!好神奇!对于这个目标,难度可以接受。 有遇到什么卡壳的地方吗? 和之前一样,细小的差别,测试没通过,略微检查一下就过去了
    2022-04-27
  • 张铁林
    对比时,写了4、5个例子,没太理解有什么差别。
    2022-04-26
收起评论
显示
设置
留言
7
收藏
沉浸
阅读
分享
手机端
快捷键
回顶部