35 DI Container(23):项目回顾与总结
你好,我是徐昊。今天我们来回顾一下第二个实战项目的整个过程。
最终的完成形态
首先,让我们来看一下最终的完成形态:
项目回顾与总结
首先是任务分解。在项目刚开始的时候,我们没有构想任何的架构愿景,而是直接采用经典模式,从功能点出发进行TDD开发。一开始进展是很顺利的,从任务列表上看,除了偶有遗落的功能点,基本上是按照任务列表顺畅进行的。
第一个转折点出现在我们第一次重构之后,我们将InjectionProvider从ContextConfig中分离了出来。并将依赖的检查从运行期检查,变成了在ContextConfig.getContext时预先检查。
也就是说,我们的架构愿景在此时发生了改变:只要Context能够被构建出来,其中就不存在无效组件和无效的组件关系(循环依赖、依赖缺失)。
这个架构愿景改变的直接影响,就是让我们分解任务的方式发生了变化。体现在任务列表上为:
-
方法注入(改变前)
-
通过Inject标注的方法,其参数为依赖组件
- 通过Inject标注的无参数方法,会被调用
- 按照子类中的规则,覆盖父类中的Inject方法
- 如果组件需要的依赖不存在,则抛出异常
- 如果方法定义类型参数,则抛出异常
- 如果组件间存在循环依赖,则抛出异常
-
方法注入(改变后)
-
通过Inject标注的方法,其参数为依赖组件
- 通过Inject标注的无参数方法,会被调用
- 按照子类中的规则,覆盖父类中的Inject方法
- 如果方法定义类型参数,则抛出异常
- 依赖中应包含Inject Method声明的依赖
也就是说,在这一刻,除了功能点之外,我们出现了两个功能上下文:ComponentProvider和ContextConfig。很自然地,连带着分解任务的方式也发生了改变。如下图所示:
同时,我们的项目还发生过两次重大的模型调整:
- 第一次是在引入Provider接口的时候。也就是除了能够直接对组件依赖外,还可以通过Provider获取组件的工厂。
- 第二次则是在引入Qualifier的时候。
第一次模型调整的时候,我们发现之前都以Class<?>作为获取组件的标识。而当引入Provider之后,我们就需要通过Type作为获取组件的标识。我们修改了ComponentProvider的接口体现了这种变化:
引入Provider之前:
public interface ComponentProvider<T> {
T get(Context context);
default List<Class<?>> getDependencies() {
return List.of();
}
}
引入Provider之后:
public interface ComponentProvider<T> {
T get(Context context);
default List<Type> getDependencies() {
return List.of();
}
}
第二次模型调整的时候,我们需要将Type和Qualifier标注组合在一起。于是我们引入了ComponentRef(最开始叫Ref)对象,作为获取组件的标识。我们再次修改了ComponentProvider的接口:
public interface ComponentProvider<T> {
T get(Context context);
default List<ComponentRef<?>> getDependencies() {
return List.of();
}
}
模型调整之后,Context中的组件就从实例组件(Instance)和注入组件(Injectable Component),变成实例组件、注入组件、带Qualifier的实例组件、带Qualifier的注入组件以及它们对应的Provider形式等八种。
这也进一步帮助我们理解了软件的需求,帮助我们进一步有效率地分解了任务。如下图所示:
带来的结果是,任务列表发生了更为剧烈的改变:
-
自定义Qualifier的依赖(最开始的任务)
-
注册组件时,可额外指定Qualifier
- 注册组件时,可从类对象上提取Qualifier
- 寻找依赖时,需同时满足类型与自定义Qualifier标注
- 支持默认Qualifier——Named
-
自定义Qualifier的依赖(实际完成的任务)
-
注册组件时,可额外指定Qualifier
- 针对instance指定一个Qualifieri(新增任务)
- 针对组件指定一个Qualiifer(新增任务)
- 针对instance指定多个Qualifieri(新增任务)
- 针对组件指定多个Qualiifer(新增任务)
- 注册组件时,如果不是合法的Qualifier,则不接受组件注册(新增任务)
-
寻找依赖时,需同时满足类型与自定义Qualifier标注
-
在检查依赖时使用Qualifier(新增任务)
- 在检查循环依赖时使用Qualifier(新增任务)
-
构造函数注入可以使用Qualifier声明依赖(新增任务)
-
依赖中包含Qualifier(新增任务)
- 如果不是合法的Qualifier,则组件非法
-
字段注入可以使用Qualifier声明依赖(新增任务)
-
依赖中包含Qualifier(新增任务)
- 如果不是合法的Qualifier,则组件非法
-
函数注入可以使用Qualifier声明依赖(新增任务)
-
依赖中包含Qualifier(新增任务)
- 如果不是合法的Qualifier,则组件非法
- 支持默认Qualifier——Named(不需要)
- 注册组件时,可从类对象上提取Qualifier(不需要)
我们通过重组测试,从结构上梳理并记录了这些改变。如下图所示:
这种对于核心模型的调整是痛苦的,在很多情况下,意味着软件需要重写。我们展示了如何通过重构来完成这种改写,所以请重点回看与重构有关的章节,反思我们是如何做到的。
最后,我的代码是这样的:
ComponentProvider.java
package geektime.tdd.di;
import java.util.List;
public interface ComponentProvider<T> {
T get(Context context);
default List<ComponentRef<?>> getDependencies() {
return List.of();
}
}
ComponentRef.java
package geektime.tdd.di;
import java.lang.annotation.Annotation;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.Objects;
public class ComponentRef<ComponentType> {
public static <ComponentType> ComponentRef<ComponentType> of(Class<ComponentType> component) {
return new ComponentRef<>(component, null);
}
public static <ComponentType> ComponentRef<ComponentType> of(Class<ComponentType> component, Annotation qualifier) {
return new ComponentRef<>(component, qualifier);
}
public static ComponentRef<?> of(Type type, Annotation qualifier) {
return new ComponentRef<>(type, qualifier);
}
private Type container;
private ContextConfig.Component component;
ComponentRef(Type type, Annotation qualifier) {
init(type, qualifier);
}
protected ComponentRef() {
this(null);
}
protected ComponentRef(Annotation qualifier) {
Type type = ((ParameterizedType) getClass().getGenericSuperclass()).getActualTypeArguments()[0];
init(type, qualifier);
}
private void init(Type type, Annotation qualifier) {
if (type instanceof ParameterizedType container) {
this.container = container.getRawType();
this.component = new ContextConfig.Component((Class<ComponentType>) container.getActualTypeArguments()[0], qualifier);
} else
this.component = new ContextConfig.Component((Class<ComponentType>) type, qualifier);
}
public Type getContainer() {
return container;
}
public boolean isContainer() {
return container != null;
}
ContextConfig.Component component() {
return component;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
ComponentRef<?> that = (ComponentRef<?>) o;
return Objects.equals(container, that.container) && component.equals(that.component);
}
@Override
public int hashCode() {
return Objects.hash(container, component);
}
}
Config.java
package geektime.tdd.di;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
public interface Config {
@Documented
@Retention(RUNTIME)
@Target({ElementType.FIELD})
@interface Export {
Class<?> value();
}
}
Context.java
package geektime.tdd.di;
import java.util.Optional;
public interface Context {
<ComponentType> Optional<ComponentType> get(ComponentRef<ComponentType> ref);
}
ContextConfig.java
package geektime.tdd.di;
import geektime.tdd.di.ContextConfig.Component;
import jakarta.inject.Provider;
import jakarta.inject.Qualifier;
import jakarta.inject.Scope;
import jakarta.inject.Singleton;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.text.MessageFormat;
import java.util.*;
import java.util.function.Function;
import java.util.stream.Stream;
import static geektime.tdd.di.ContextConfigError.circularDependencies;
import static geektime.tdd.di.ContextConfigError.unsatisfiedResolution;
import static geektime.tdd.di.ContextConfigException.illegalAnnotation;
import static java.util.Arrays.spliterator;
import static java.util.Arrays.stream;
import static java.util.stream.Collectors.*;
public class ContextConfig {
private final Map<Component, ComponentProvider<?>> components = new HashMap<>();
private final Map<Class<?>, ScopeProvider> scopes = new HashMap<>();
public ContextConfig() {
scope(Singleton.class, SingletonProvider::new);
}
public <Type> void instance(Class<Type> type, Type instance) {
bind(new Component(type, null), context -> instance);
}
public <Type> void instance(Class<Type> type, Type instance, Annotation... annotations) {
bindInstance(type, instance, annotations);
}
public <Type, Implementation extends Type>
void component(Class<Type> type, Class<Implementation> implementation, Annotation... annotations) {
bindComponent(type, implementation, annotations);
}
public <ScopeType extends Annotation> void scope(Class<ScopeType> scope, ScopeProvider provider) {
scopes.put(scope, provider);
}
public void from(Config config) {
new DSL(config).bind();
}
public Context getContext() {
components.keySet().forEach(component -> checkDependencies(component, new Stack<>()));
HashMap<Component, ComponentProvider<?>> context = new HashMap<>(components);
return new Context() {
@Override
public <ComponentType> Optional<ComponentType> get(ComponentRef<ComponentType> ref) {
if (ref.isContainer()) {
if (ref.getContainer() != Provider.class) return Optional.empty();
return (Optional<ComponentType>) Optional.ofNullable(getProvider(ref))
.map(provider -> (Provider<Object>) () -> provider.get(this));
}
return Optional.ofNullable(getProvider(ref)).map(provider -> (ComponentType) provider.get(this));
}
private <ComponentType> ComponentProvider<?> getProvider(ComponentRef<ComponentType> ref) {
return context.get(ref.component());
}
};
}
private void bindComponent(Class<?> type, Class<?> implementation, Annotation... annotations) {
Bindings bindings = Bindings.component(implementation, annotations);
bind(type, bindings.qualifiers(), provider(implementation, bindings.scope()));
}
private void bindInstance(Class<?> type, Object instance, Annotation[] annotations) {
bind(type, Bindings.instance(type, annotations).qualifiers(), context -> instance);
}
private <Type> void bind(Class<Type> type, List<Annotation> qualifiers, ComponentProvider<?> provider) {
if (qualifiers.isEmpty()) bind(new Component(type, null), provider);
for (Annotation qualifier : qualifiers)
bind(new Component(type, qualifier), provider);
}
private void bind(Component component, ComponentProvider<?> provider) {
if (components.containsKey(component)) throw ContextConfigException.duplicated(component);
components.put(component, provider);
}
private <Type> ComponentProvider<?> provider(Class<Type> implementation, Optional<Annotation> scope) {
ComponentProvider<?> injectionProvider = new InjectionProvider<>(implementation);
return scope.<ComponentProvider<?>>map(s -> scoped(s, injectionProvider)).orElse(injectionProvider);
}
private ComponentProvider<?> scoped(Annotation scope, ComponentProvider<?> provider) {
if (!scopes.containsKey(scope.annotationType()))
throw ContextConfigException.unknownScope(scope.annotationType());
return scopes.get(scope.annotationType()).create(provider);
}
private void checkDependencies(Component component, Stack<Component> visiting) {
for (ComponentRef<?> dependency : components.get(component).getDependencies()) {
if (!components.containsKey(dependency.component()))
throw unsatisfiedResolution(component, dependency.component());
if (!dependency.isContainer()) {
if (visiting.contains(dependency.component()))
throw circularDependencies(visiting, dependency.component());
visiting.push(dependency.component());
checkDependencies(dependency.component(), visiting);
visiting.pop();
}
}
}
record Component(Class<?> type, Annotation qualifier) {
}
static class Bindings {
public static Bindings component(Class<?> component, Annotation... annotations) {
return new Bindings(component, annotations, Qualifier.class, Scope.class);
}
public static Bindings instance(Class<?> instance, Annotation... annotations) {
return new Bindings(instance, annotations, Qualifier.class);
}
Class<?> type;
Map<Class<?>, List<Annotation>> group;
public Bindings(Class<?> type, Annotation[] annotations, Class<? extends Annotation>... allowed) {
this.type = type;
this.group = parse(type, annotations, allowed);
}
private static Map<Class<?>, List<Annotation>> parse(Class<?> type, Annotation[] annotations, Class<? extends Annotation>... allowed) {
Map<Class<?>, List<Annotation>> annotationGroups = stream(annotations).collect(groupingBy(allow(allowed), toList()));
if (annotationGroups.containsKey(Illegal.class))
throw illegalAnnotation(type, annotationGroups.get(Illegal.class));
return annotationGroups;
}
private static Function<Annotation, Class<?>> allow(Class<? extends Annotation>... annotations) {
return annotation -> Stream.of(annotations).filter(annotation.annotationType()::isAnnotationPresent)
.findFirst().orElse(Illegal.class);
}
private @interface Illegal {
}
Optional<Annotation> scope() {
List<Annotation> scopes = group.getOrDefault(Scope.class, from(type, Scope.class));
if (scopes.size() > 1) throw illegalAnnotation(type, scopes);
return scopes.stream().findFirst();
}
List<Annotation> qualifiers() {
return group.getOrDefault(Qualifier.class, List.of());
}
private static List<Annotation> from(Class<?> implementation, Class<? extends Annotation> annotation) {
return stream(implementation.getAnnotations()).filter(a -> a.annotationType().isAnnotationPresent(annotation)).toList();
}
}
class DSL {
private Config config;
public DSL(Config config) {
this.config = config;
}
void bind() {
for (Declaration declaration : declarations())
declaration.value().ifPresentOrElse(declaration::bindInstance, declaration::bindComponent);
}
private List<Declaration> declarations() {
return stream(config.getClass().getDeclaredFields()).filter(f -> !f.isSynthetic()).map(Declaration::new).toList();
}
class Declaration {
private Field field;
Declaration(Field field) {
this.field = field;
}
void bindInstance(Object instance) {
ContextConfig.this.bindInstance(type(), instance, annotations());
}
void bindComponent() {
ContextConfig.this.bindComponent(type(), field.getType(), annotations());
}
private Optional<Object> value() {
try {
return Optional.ofNullable(field.get(config));
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
}
}
private Class<?> type() {
Config.Export export = field.getAnnotation(Config.Export.class);
return export != null ? export.value() : field.getType();
}
private Annotation[] annotations() {
return stream(field.getAnnotations()).filter(a -> a.annotationType() != Config.Export.class).toArray(Annotation[]::new);
}
}
}
}
class ContextConfigError extends Error {
public static ContextConfigError unsatisfiedResolution(Component component, Component dependency) {
return new ContextConfigError(MessageFormat.format("Unsatisfied resolution: {1} for {0} ", component, dependency));
}
public static ContextConfigError circularDependencies(Collection<Component> path, Component circular) {
return new ContextConfigError(MessageFormat.format("Circular dependencies: {0} -> [{1}]",
path.stream().map(Objects::toString).collect(joining(" -> ")), circular));
}
ContextConfigError(String message) {
super(message);
}
}
class ContextConfigException extends RuntimeException {
static ContextConfigException illegalAnnotation(Class<?> type, List<Annotation> annotations) {
return new ContextConfigException(MessageFormat.format("Unqualified annotations: {0} of {1}",
String.join(" , ", annotations.stream().map(Object::toString).toList()), type));
}
static ContextConfigException unknownScope(Class<? extends Annotation> annotationType) {
return new ContextConfigException(MessageFormat.format("Unknown scope: {0}", annotationType));
}
static ContextConfigException duplicated(Component component) {
return new ContextConfigException(MessageFormat.format("Duplicated: {0}", component));
}
ContextConfigException(String message) {
super(message);
}
}
InjectionProvider.java
package geektime.tdd.di;
import jakarta.inject.Inject;
import jakarta.inject.Qualifier;
import java.lang.annotation.Annotation;
import java.lang.reflect.*;
import java.text.MessageFormat;
import java.util.*;
import java.util.function.BiFunction;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import static geektime.tdd.di.ComponentError.*;
import static java.util.Arrays.stream;
import static java.util.stream.Stream.concat;
class InjectionProvider<T> implements ComponentProvider<T> {
private final Injectable<Constructor<T>> injectConstructor;
private final Map<Class<?>, List<Injectable<Method>>> injectMethods;
private final Map<Class<?>, List<Injectable<Field>>> injectFields;
private final Collection<Class<?>> superClasses;
private final List<ComponentRef<?>> dependencies;
public InjectionProvider(Class<T> component) {
this.injectConstructor = getInjectConstructor(component);
this.superClasses = allSuperClass(component);
var injectFields = getInjectFields(component);
var injectMethods = getInjectMethods(component);
this.dependencies = concat(concat(Stream.of(injectConstructor), injectFields.stream()), injectMethods.stream())
.map(Injectable::required).flatMap(Arrays::stream).toList();
this.injectFields = groupByClass(injectFields);
this.injectMethods = groupByClass(injectMethods);
}
@Override
public T get(Context context) {
try {
T instance = injectConstructor.element().newInstance(injectConstructor.toDependencies(context));
for (Class<?> c : superClasses) {
for (Injectable<Field> field : injectFields.getOrDefault(c, List.of()))
field.element().set(instance, field.toDependencies(context)[0]);
for (Injectable<Method> method : injectMethods.getOrDefault(c, List.of()))
method.element().invoke(instance, method.toDependencies(context));
}
return instance;
} catch (InvocationTargetException | InstantiationException | IllegalAccessException e) {
throw new RuntimeException(e);
}
}
@Override
public List<ComponentRef<?>> getDependencies() {
return dependencies;
}
record Injectable<Element extends AccessibleObject>(Element element, ComponentRef<?>[] required) {
Object[] toDependencies(Context context) {
return stream(required).map(context::get).map(Optional::get).toArray();
}
static <Element extends Executable> Injectable<Element> of(Element element) {
element.setAccessible(true);
return new Injectable<>(element, stream(element.getParameters()).map(Injectable::toComponentRef).toArray(ComponentRef<?>[]::new));
}
static Injectable<Field> of(Field field) {
field.setAccessible(true);
return new Injectable<>(field, new ComponentRef<?>[]{toComponentRef(field)});
}
private static ComponentRef<?> toComponentRef(Field field) {
return ComponentRef.of(field.getGenericType(), getQualifier(field));
}
private static ComponentRef<?> toComponentRef(Parameter parameter) {
return ComponentRef.of(parameter.getParameterizedType(), getQualifier(parameter));
}
private static Annotation getQualifier(AnnotatedElement element) {
List<Annotation> qualifiers = stream(element.getAnnotations())
.filter(a -> a.annotationType().isAnnotationPresent(Qualifier.class)).toList();
if (qualifiers.size() > 1) throw ComponentError.ambiguousQualifiers(element, qualifiers);
return qualifiers.stream().findFirst().orElse(null);
}
}
private static <T> List<Injectable<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());
List<Injectable<Method>> injectableMethods = injectMethods.stream().map(Injectable::of).toList();
return check(component, injectableMethods, InjectionProvider::noTypeParameter, ComponentError::injectMethodsWithTypeParameter);
}
private static <T> List<Injectable<Field>> getInjectFields(Class<T> component) {
List<Injectable<Field>> injectableFields = InjectionProvider.<Field>traverse(component, (fields, current) -> injectable(current.getDeclaredFields()).toList())
.stream().map(Injectable::of).toList();
return check(component, injectableFields, InjectionProvider::notFinal, ComponentError::finalInjectFields);
}
private static <Type> Injectable<Constructor<Type>> getInjectConstructor(Class<Type> implementation) {
if (Modifier.isAbstract(implementation.getModifiers())) throw abstractComponent(implementation);
List<Constructor<?>> injectConstructors = injectable(implementation.getDeclaredConstructors()).toList();
if (injectConstructors.size() > 1) throw ambiguousInjectableConstructors(implementation);
return Injectable.of((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 noDefaultConstructor(implementation);
}
}
private static <E extends AccessibleObject> Map<Class<?>, List<Injectable<E>>> groupByClass(List<Injectable<E>> injectFields) {
return injectFields.stream().collect(Collectors.groupingBy(i -> ((Member)i.element()).getDeclaringClass(), Collectors.toList()));
}
private static Collection<Class<?>> allSuperClass(Class<?> component) {
List<Class<?>> result = new ArrayList<>();
for (Class superClass = component;
superClass != Object.class;
superClass = superClass.getSuperclass())
result.add(0, superClass);
return result;
}
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) {
boolean visible;
if (m.getDeclaringClass().getPackageName().equals(o.getDeclaringClass().getPackageName()))
visible = !Modifier.isPrivate(o.getModifiers()) && !Modifier.isPrivate(m.getModifiers());
else visible = (Modifier.isPublic(o.getModifiers()) || Modifier.isProtected(o.getModifiers()))
&& (Modifier.isPublic(m.getModifiers()) || Modifier.isProtected(m.getModifiers()));
return visible && 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 <Element extends AccessibleObject> List<Injectable<Element>> check(Class<?> component, List<Injectable<Element>> target, Predicate<Element> predicate,
BiFunction<Class<?>, List<Element>, ComponentError> error) {
List<Element> found = target.stream().map(Injectable::element).filter(predicate).toList();
if (found.size() > 0) throw error.apply(component, found.stream().toList());
return target;
}
private static boolean notFinal(Field field) {
return Modifier.isFinal(field.getModifiers());
}
private static boolean noTypeParameter(Method method) {
return method.getTypeParameters().length != 0;
}
}
class ComponentError extends Error {
public static ComponentError abstractComponent(Class<?> component) {
return new ComponentError(MessageFormat.format("Can not be abstract: {0}", component));
}
public static ComponentError finalInjectFields(Class<?> component, Collection<Field> fields) {
return new ComponentError(MessageFormat.format("Injectable field can not be final: {0} in {1}",
String.join(" , ", fields.stream().map(Field::getName).toList()), component));
}
public static ComponentError injectMethodsWithTypeParameter(Class<?> component, Collection<Method> fields) {
return new ComponentError(MessageFormat.format("Injectable method can not have type parameter: {0} in {1}",
String.join(" , ", fields.stream().map(Method::getName).toList()), component));
}
public static ComponentError ambiguousInjectableConstructors(Class<?> component) {
return new ComponentError(MessageFormat.format("Ambiguous injectable constructors: {0}", component));
}
public static ComponentError noDefaultConstructor(Class<?> component) {
return new ComponentError(MessageFormat.format("No default constructors: {0}", component));
}
public static ComponentError ambiguousQualifiers(AnnotatedElement element, List<Annotation> qualifiers) {
Class<?> component;
if (element instanceof Parameter p) component = p.getDeclaringExecutable().getDeclaringClass();
else component = ((Field) element).getDeclaringClass();
return new ComponentError(MessageFormat.format("Ambiguous qualifiers: {0} on {1} of {2}",
String.join(" , ", qualifiers.stream().map(Object::toString).toList()), element, component));
}
ComponentError(String message) {
super(message);
}
}
ScopeProvider.java
package geektime.tdd.di;
public interface ScopeProvider {
ComponentProvider<?> create(ComponentProvider<?> provider);
}
SingletonProvider.java
package geektime.tdd.di;
import java.util.List;
class SingletonProvider<T> implements ComponentProvider<T> {
private T singleton;
private ComponentProvider<T> provider;
public SingletonProvider(ComponentProvider<T> provider) {
this.provider = provider;
}
@Override
public T get(Context context) {
if (singleton == null) singleton = provider.get(context);
return singleton;
}
@Override
public List<ComponentRef<?>> getDependencies() {
return provider.getDependencies();
}
}
思考题
在进入下节课之前,希望你能认真思考如下两个问题。
- 请增加static注入,并通过Jakarta Inject TCK。
- 请回顾整个项目实践,看看跟最开始学习TDD相比,都有哪些进步和改变。
与我们实际工作中的项目相比,DI Container这个框架级实战的复杂度要略微高一些。经过这三个月的学习,除了提升你的技术能力外,相信对你的学习能力也有不小的磨练。所以,坚持到这一站的同学们,请为自己点个赞吧!
- 张铁林 👍(2) 💬(1)
主要还是开阔了眼界,用TDD来做一个相对复杂的功能,不像很多kata那样,短短几十行代码量,体会不到不断的重构和深进。
2022-06-04 - aoe 👍(1) 💬(1)
这次要求中最容易做到的就是为自己点个赞,我已经点了! 最开始学习 TDD 相比:学会使用 @Nested 标签;学会了泛型中 ?、T 之类的含义;有测试做保障想重构就重构
2022-06-02 - 枫中的刀剑 👍(2) 💬(0)
第一次静距离观察一个完整项目的TDD过程,感受还是挺深的。特别是第一次接触到测试重组、测试文档化的时候有种眼前一亮的感觉。而其中ComponentRef 模型和 Qualifier 模型调整的重构过程更加让人赏心悦目。新的模型引入拓展了知识,带来个更加明确的功能上下文划分。而原有整体的结构却没有发生太多的变化。这种一点点进步是能够很清楚的感受到的。 最后在附上项目链接:https://github.com/maplestoryJin/DiContainer 包括 static 注入实现。
2022-06-10 - 临风 👍(0) 💬(0)
有个关于bind的实现问题,一般情况都是bind(Component.class, Instance.class)或者bind(Component.class, Provider<Instance.class>)。如果直接bind(Instance.class, Instance.class),那么get(Component.class)还能找到吗? 另外,无论bind的是Intance还是Provider,当需要注入Provider类型的时候,是都需要支持吗? 这些规范的东西我可以去哪里找到,希望老师能解答一下。
2022-06-06