微信公众号: 线程
在一些Java容器技术中,比如IOC容器和DI容器,通常需要处理一种叫作循环依赖的情况。
循环依赖指的是两个或更多的bean彼此相互持有,最终形成一个环。
比如在下面这个例子中ComponentWithInjectConstructor是依赖于Dependency的, 同时DependencyDependedOnComponent依赖于component
public class ComponentWithInjectConstructor implements Component {
private Dependency dependency;
//表示注入
@Inject
public ComponentWithInjectConstructor(Dependency dependency) {
this.dependency = dependency;
}
}
public class DependencyDependedOnComponent implements Dependency {
private Component component;
@Inject
public DependencyDependedOnComponent(Component component) {
this.component = component;
}
}
DIcontainer的对外接口是这样的:
context.bind(Component.class, ComponentWithInjectConstructor.class);
context.bind(Dependency.class, DependencyDependedOnComponent.class);
context.get(Component.class);
在实现绑定bind时,我们使用了一个映射(map),以注入的接口作为键。接口的实例被封装在一个类中,这个类实现了Provider接口。
要获取已注入的实例,首先需要创建Provider类的实例,然后调用其get方法。
Map<Class<?>, Provider<?>> providers = new HashMap<>();
//bind注入
providers.put(type, new ConstructorInjectionProvider(type, injectConstructor));
//get提取
//根据注入的接口获得provider接口
Optional.ofNullable(providers.get(type)).map(provider -> provider.get());
//providers接口的get方法
//获取provider的实例, 然后调用get方法, 创建实例, 返回注入的实例
Object[] dependencies = stream(injectConstructor.getParameters())
.map(p -> get(p.getType()))
.toArray(Object[]::new);
在处理循环构造时,我们会遇到以下情况:
首先,根据Component查找类,找到了ComponentWithInjectConstructor。然后遍历其构造函数的参数,发现参数类型为Dependency。
接着,根据Dependency查找类,找到了DependencyDependedOnComponent。再次遍历其构造函数的参数,发现参数类型为Component,形成了一个循环依赖的环。最终程序会一种处在一种死循环中。
如前所述,循环依赖就是在对象关系图中形成了一个环。我们需要采取措施来打破这个循环。
解决方法相对简单,在构造对象时,我们首先将当前正在构造的对象压入栈中。在下一次判断时,检查对象是否已经在构造中,如果是,则说明存在循环依赖,应该抛出异常。
若构造完成,则将对象从栈中弹出。这一机制能有效防止循环依赖的问题。
对应的代码逻辑如下, 这里我们直接使用一个全局条件变量, 也可以达到上面的效果。
if (constructing) throw new CyclicDependenciesFoundException(componentType);
try {
//表示当前的provider正在构造
constrcuting = true;
Object[] dependencies = stream(injectConstructor.getParameters())
.map(p -> get(p.getType()))
.toArray(Object[]::new);
return injectConstructor.newInstance(dependencies);
} finally {
constructing = false;
}
因为不断的查找try中出现了死循环,finally中的东西永久不会执行, 并且当在一个依赖中查找到component的时候, 会发现constructing为true, 所以就会抛出异常。