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

27|DI Container(15):如何封装类型判断逻辑?

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

回顾代码与任务列表

到目前为止,我们的代码是这样的:
InjectProvider.java:
package geektime.tdd.di;
import jakarta.inject.Inject;
import java.lang.reflect.*;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.function.BiFunction;
import java.util.stream.Stream;
import static java.util.Arrays.stream;
import static java.util.stream.Stream.concat;
class InjectionProvider<T> implements ContextConfig.ComponentProvider<T> {
private Constructor<T> injectConstructor;
private List<Field> injectFields;
private List<Method> injectMethods;
public InjectionProvider(Class<T> component) {
if (Modifier.isAbstract(component.getModifiers())) throw new IllegalComponentException();
this.injectConstructor = getInjectConstructor(component);
this.injectFields = getInjectFields(component);
this.injectMethods = getInjectMethods(component);
if (injectFields.stream().anyMatch(f -> Modifier.isFinal(f.getModifiers())))
throw new IllegalComponentException();
if (injectMethods.stream().anyMatch(m -> m.getTypeParameters().length != 0))
throw new IllegalComponentException();
}
@Override
public T get(Context context) {
try {
T instance = injectConstructor.newInstance(toDependencies(context, injectConstructor));
for (Field field : injectFields) field.set(instance, toDependency(context, field));
for (Method method : injectMethods) method.invoke(instance, toDependencies(context, method));
return instance;
} catch (InvocationTargetException | InstantiationException | IllegalAccessException e) {
throw new RuntimeException(e);
}
}
@Override
public List<Type> getDependencies() {
return concat(concat(stream(injectConstructor.getParameters()).map(Parameter::getParameterizedType),
injectFields.stream().map(Field::getGenericType)),
injectMethods.stream().flatMap(m -> stream(m.getParameters()).map(Parameter::getParameterizedType))).toList();
}
private static <T> List<Method> getInjectMethods(Class<T> component) {
List<Method> injectMethods = traverse(component, (methods, current) -> injectable(current.getDeclaredMethods())
.filter(m -> isOverrideByInjectMethod(methods, m))
.filter(m -> isOverrideByNoInjectMethod(component, m)).toList());
Collections.reverse(injectMethods);
return injectMethods;
}
private static <T> List<Field> getInjectFields(Class<T> component) {
return traverse(component, (fields, current) -> injectable(current.getDeclaredFields()).toList());
}
private static <Type> Constructor<Type> getInjectConstructor(Class<Type> implementation) {
List<Constructor<?>> injectConstructors = injectable(implementation.getConstructors()).toList();
if (injectConstructors.size() > 1) throw new IllegalComponentException();
return (Constructor<Type>) injectConstructors.stream().findFirst().orElseGet(() -> defaultConstructor(implementation));
}
private static <Type> Constructor<Type> defaultConstructor(Class<Type> implementation) {
try {
return implementation.getDeclaredConstructor();
} catch (NoSuchMethodException e) {
throw new IllegalComponentException();
}
}
private static <T> List<T> traverse(Class<?> component, BiFunction<List<T>, Class<?>, List<T>> finder) {
List<T> members = new ArrayList<>();
Class<?> current = component;
while (current != Object.class) {
members.addAll(finder.apply(members, current));
current = current.getSuperclass();
}
return members;
}
private static <T extends AnnotatedElement> Stream<T> injectable(T[] declaredFields) {
return stream(declaredFields).filter(f -> f.isAnnotationPresent(Inject.class));
}
private static boolean isOverride(Method m, Method o) {
return o.getName().equals(m.getName()) && Arrays.equals(o.getParameterTypes(), m.getParameterTypes());
}
private static <T> boolean isOverrideByNoInjectMethod(Class<T> component, Method m) {
return stream(component.getDeclaredMethods()).filter(m1 -> !m1.isAnnotationPresent(Inject.class)).noneMatch(o -> isOverride(m, o));
}
private static boolean isOverrideByInjectMethod(List<Method> injectMethods, Method m) {
return injectMethods.stream().noneMatch(o -> isOverride(m, o));
}
private static Object[] toDependencies(Context context, Executable executable) {
return stream(executable.getParameters()).map(p -> toDependency(context, p.getParameterizedType())).toArray(Object[]::new);
}
private static Object toDependency(Context context, Field field) {
return toDependency(context, field.getGenericType());
}
private static Object toDependency(Context context, Type type) {
return context.get(type).get();
}
}
Context.java:
package geektime.tdd.di;
import java.lang.reflect.Type;
import java.util.Optional;
public interface Context {
Optional get(Type type);
}
ContextConfig.java:
package geektime.tdd.di;
import jakarta.inject.Provider;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
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, (ComponentProvider<Type>) context -> instance);
}
public <Type, Implementation extends Type>
void bind(Class<Type> type, Class<Implementation> implementation) {
providers.put(type, new InjectionProvider<>(implementation));
}
public Context getContext() {
providers.keySet().forEach(component -> checkDependencies(component, new Stack<>()));
return new Context() {
@Override
public Optional get(Type type) {
if (isContainerType(type)) return getContainer((ParameterizedType) type);
return getComponent((Class<?>) type);
}
private <Type> Optional<Type> getComponent(Class<Type> type) {
return Optional.ofNullable(providers.get(type)).map(provider -> (Type) provider.get(this));
}
private Optional getContainer(ParameterizedType type) {
if (type.getRawType() != Provider.class) return Optional.empty();
return Optional.ofNullable(providers.get(getComponentType(type)))
.map(provider -> (Provider<Object>) () -> provider.get(this));
}
};
}
private Class<?> getComponentType(Type type) {
return (Class<?>) ((ParameterizedType) type).getActualTypeArguments()[0];
}
private boolean isContainerType(Type type) {
return type instanceof ParameterizedType;
}
private void checkDependencies(Class<?> component, Stack<Class<?>> visiting) {
for (Type dependency : providers.get(component).getDependencies()) {
if (isContainerType(dependency)) checkContainerTypeDependency(component, dependency);
else checkComponentDependency(component, visiting, (Class<?>) dependency);
}
}
private void checkContainerTypeDependency(Class<?> component, Type dependency) {
if (!providers.containsKey(getComponentType(dependency)))
throw new DependencyNotFoundException(component, getComponentType(dependency));
}
private void checkComponentDependency(Class<?> component, Stack<Class<?>> visiting, Class<?> dependency) {
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);
default List<Type> getDependencies() {
return of();
}
}
}
任务列表的状态为:
无需构造的组件——组件实例
如果注册的组件不可实例化,则抛出异常
抽象类
接口
构造函数注入
无依赖的组件应该通过默认构造函数生成组件实例
有依赖的组件,通过 Inject 标注的构造函数生成组件实例
如果所依赖的组件也存在依赖,那么需要对所依赖的组件也完成依赖注入
如果组件有多于一个 Inject 标注的构造函数,则抛出异常
如果组件没有 Inject 标注的构造函数,也没有默认构造函数(新增任务)
如果组件需要的依赖不存在,则抛出异常
如果组件间存在循环依赖,则抛出异常
字段注入
通过 Inject 标注将字段声明为依赖组件
如果字段为 final 则抛出异常
依赖中应包含 Inject Field 声明的依赖
方法注入
通过 Inject 标注的方法,其参数为依赖组件
通过 Inject 标注的无参数方法,会被调用
按照子类中的规则,覆盖父类中的 Inject 方法
如果方法定义类型参数,则抛出异常
依赖中应包含 Inject Method 声明的依赖
对 Provider 类型的依赖
从容器中取得组件的 Provider(新增任务)
注入构造函数中可以声明对于 Provider 的依赖
注入字段中可以声明对于 Provider 的依赖
注入方法中可声明对于 Provider 的依赖
将构造函数中的 Provider 加入依赖(新增任务)
将字段中的 Provider 加入依赖(新增任务)
将方法中的 Provider 加入依赖(新增任务)
自定义 Qualifier 的依赖
注册组件时,可额外指定 Qualifier
注册组件时,可从类对象上提取 Qualifier
寻找依赖时,需同时满足类型与自定义 Qualifier 标注
支持默认 Qualifier——Named
Singleton 生命周期
注册组件时,可额外指定是否为 Singleton
注册组件时,可从类对象上提取 Singleton 标注
对于包含 Singleton 标注的组件,在容器范围内提供唯一实例
容器组件默认不是 Single 生命周期
自定义 Scope 标注
可向容器注册自定义 Scope 标注的回调
确认放弃笔记?
放弃后所记笔记将不保留。
新功能上线,你的历史笔记已初始化为私密笔记,是否一键批量公开?
批量公开的笔记不会为你同步至部落
公开
同步至部落
取消
完成
0/2000
荧光笔
直线
曲线
笔记
复制
AI
  • 深入了解
  • 翻译
    • 英语
    • 中文简体
    • 中文繁体
    • 法语
    • 德语
    • 日语
    • 韩语
    • 俄语
    • 西班牙语
    • 阿拉伯语
  • 解释
  • 总结

本文介绍了徐八叉关于DI容器的第15篇文章,主要讲述了如何封装类型判断逻辑。文章以代码和任务列表的形式展示了DI容器的实现过程,通过TDD的方式逐步完善注入依赖容器的功能。文章中提到了代码实现的细节,包括构造函数注入、字段注入、方法注入等,以及对Provider类型的依赖、自定义Qualifier的依赖、Singleton生命周期等功能的实现。此外,文章还提出了两个思考题,引导读者思考行为封装和个人在编程方面的进步或变化。整体而言,本文通过具体的代码实现和任务列表,深入浅出地介绍了DI容器的实现过程,适合对DI容器感兴趣的读者阅读学习。

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

全部留言(9)

  • 最新
  • 精选
  • aoe
    1. 经常使用 inLine、extra修改代码 2. 感觉 Idea 的重构快捷键功能比 Copilot 插件更强大,写代码更开心了 3. 在工作中使用 TDD 整体开发效率提高了很多(再也不用在一堆日志中找 Bug 了) 4. 当需求变更或者有更好的想法时可以放心的重构代码,实现了《代码整洁之道》中提到的“最好的重构时机就是:即时重构” 5. 发现了自己对如何写出易维护、易使用的代码几乎没有经验,正在通过 2 种方式努力:一、跟着老师敲代码,在大师的熏陶下成长;二、通过阅读书籍,了解一下基础知识 已读:《代码整洁之道》、《测试驱动开发的艺术》、《Java 测试驱动开发》 在读:《修改代码的艺术》、《领域特定语言》 待读:《重构与模式》、《重构:改善既有代码的设计》、《Google软件测试之道》、《分析模式》、《领域驱动设计精粹》、《实现领域驱动设计》 我收集的书单 https://wyyl1.com/post/3/1
    2022-05-20
    5
  • 人间四月天
    我都是看了2遍,然后再敲一遍,感受一下TDD威力,其实,还能学学重构,很多时候,不敢对烂代码下手。 重构除掉坏味道,重构到设计,重构到模式。
    2022-05-11
    3
  • 枫中的刀剑
    这节老师最后提到的一个细节就是对于API的优化,应该要让API的使用尽可能的友好。这点好像在平时中很少注意,但往往就在这些细微之处才是专业的体现。
    2022-05-28
    1
  • 蝴蝶
    这操作好Sao啊.jpg
    2022-09-01归属地:广东
  • 大碗
    assertArrayEquals dependencies 为啥抽成一个方法呢,重复修改好多次了
    2022-08-22归属地:广东
  • davix
    這個項目從頭TDD,有全面的測試覆蓋,否則敢這麼多重構(再設計)嗎
    2022-08-20归属地:北京
  • tdd学徒
    照着写的时候,错把Ref构造函数里面的 this.container = container.getRawType(); 写成了 this.container = container; 一运行出错了,顿时有点慌,但是因为有测试,立马回退,然后加一行,跑一下测试,很快就发现问题了
    2022-05-20
  • Numbpad1
    1. 学会了很多重构的小技巧,IDE的快捷键,理解了为啥重构和测试是紧密相连的。 2. 读了很多关于测试的书,里面讲到测试的抗重构性,行为验证不如状态验证,抗重构性高,学到27讲算是体会到了。
    2022-05-11
  • 张铁林
    以前看的很多tdd都是kata一类的,就是逻辑比较短,还体会不到太多的TDD的好处,以及它的威力。通过老师这个项目的学习,学到了很多。本节代码已经跟着敲完了,有需要参考https://github.com/vfbiby/tdd-di-container/tree/master
    2022-05-11
收起评论
显示
设置
留言
9
收藏
沉浸
阅读
分享
手机端
快捷键
回顶部