关注公众号【1024个为什么】,及时接收最新推送文章!
本文观点可能颠覆你现有的认知,请坐稳扶好。
|| 依赖注入
| 什么是依赖注入?
依赖注入(Dependency Injection,简称DI),先看看百度百科的结果,大家自己领会,我持保留意见。
我的理解:一种通过外部传值给类成员变量赋值的编码风格。
| 为什么会有依赖注入?
个人猜测应该和面向对象设计原则有关,CRP(Composite Reuse Principle),CRP讲究的是对象间的相互配合,优先使用组合而不是继承。
那么问题来了,组合的这些对象如何赋值呢?最直接的做法是new一个对象给属性赋值。
public class Emailer {
private SpellChecker spellChecker;
public Emailer() {
this.spellChecker = new SpellChecker();
}
public void send(String text) { .. }
}
这种赋值方式存在几个问题:
-
每实例化一个Emailer对象,都会实例化一个SpellChecker对象,如果某个执行逻辑中没用到spellChecker对象,那岂不是即浪费时间又浪费空间。
-
如果用到spellChecker对象时发现最开始赋的值不满足怎么办?
-
如果引用的是一个ISpellChecker接口呢?在实例化Emailer的时候还不知道业务逻辑执行的时候用到ISpellChecker的哪个实现类。
于是就有了另外一个设计原则,DIP(Dependency Injection Principle),这个原则讲的是面向接口编程而不是面向实现编程,使用注入的方式完成依赖对象的赋值。说白了就是不管依赖类还是接口,都支持,谁用谁传值就行了。
外部传值,可以有多种形式,总结起来就两大类:构造方法传参,非构造方法传参(setter方法,方法名不重要,只是大多数习惯了叫setter,很多起名叫 init(Xxx xxx))。
| 依赖注入达到了什么目的?
解耦:我是一台豆浆机,要打出豆浆就需要水、豆子(依赖的2个对象), 谁使用谁就要负责加水、放豆子,我提供打豆浆的功能,但需要的东西(对象的值)要由使用方在使用的时候提供(注入)。就算不提供水、豆子,也不影响我正常的旋转。
易于扩展:主要是指依赖接口的情况,不管你放黄豆、绿豆还是黑豆,只要属于豆子(接口)都支持。
|| 循环依赖
| 什么情况下会循环依赖?
两个及以上的类在实例化的过程中,相互需要各自实例化出的对象给自己的属性赋值,这种情况下就会产生循环依赖。
表现在代码上就是下面的两种情况:
public class A {
B b = new B();
}
public class B {
A a = new A();
}
public class Test {
public static void main(String[] args) {
new A(); // 或者 new B();
}
}
public class A {
B b;
public A(B b){
this.b = b;
}
}
public class B {
A a;
public B(A a) {
this.a = a;
}
}
这种通过构造参数赋值,如果用无参的new(),在编译时就会报错,如果使用反射绕过编译器,真正执行的时候也会报错(找不到无参的构造)。
Spring里提到的唯一一种能产生循环依赖的场景,就是XML下注入方式为Constructor-based 的场景。后面会详细说明。
| 为什么会产生循环依赖?
要解释这个问题,就要了解对象初始化的过程,这里说的初始化是指类加载过程的最后一步 -- 赋值环节。这个环节主要会对下面的属性赋值:
-
static 修饰的属性、代码块,JVM会自动生成<clinit>方法,优先执行,而且只执行一次。final static 修饰的常量值(基础数据类型),在类加载时已经放在了class的常量池中了。
-
采用 new 关键字赋值的属性,因为new、.newInstanse()等关键字会触发JVM的初始化动作,所以为了保证这个属性对应的类型存在且正确,要对此类型初始化后赋值给该属性。
问题的关键来了,赋值没结束,初始化的过程就算没结束,除非正常赋值结束,或者赋值过程中出现了异常。像常见的堆溢出、栈溢出都会导致初始化失败。循环依赖就属于赋值过程中产生了异常,导致初始化失败。
拓展一下:发生了循环依赖,是栈先溢出,还是堆先溢出? 答案是不确定,要看类的结构。 如果只有单纯的几个引用类型的属性,那就是先报 java.lang.StackOverflowError,因为默认的栈帧深度是1024,而几千个引用对象占用堆的空间几乎可以忽略不计; 如果有占用大量内存空间的 static 属性(比如一个10M的byte[]),按照上面初始化赋值的过程,那就有可能先报 java.lang.OutOfMemoryError。 |
|| Spring 是如何处理循环依赖问题的?
| 直接抛异常
Spring在这一小节中提到了循环依赖:
https://docs.spring.io/spring-framework/docs/current/spring-framework-reference/core.html#beans-dependency-resolution
文档中指出,使用构造方法注入,可能会产生循环依赖,bean解析依赖的过程中如果发现循环依赖,处理手段很干脆,直接抛异常 BeanCurrentlyInCreationException ,并且建议你使用setter注入。
The dependencies of some of the beans in the application context form a cycle:
┌─────┐
| a defined in file [D:\yunsc\project\demo\target\classes\com\临时\A.class]
↑ ↓
| b defined in file [D:\yunsc\project\demo\target\classes\com\临时\B.class]
└─────┘
org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'a': Requested bean is currently in creation: Is there an unresolvable circular reference?
at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.beforeSingletonCreation(DefaultSingletonBeanRegistry.java:339)
at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:215)
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:318)
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:199)
at org.springframework.beans.factory.config.DependencyDescriptor.resolveCandidate(DependencyDescriptor.java:277)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:1251)
......
| 那我们熟知的@Autowired又作何解释?
这个很简单,回忆一下 @Autowired 的使用场景,是不是只声明了属性,没有赋值。所以JVM在初始化类的过程中就没有为这些属性赋值的环节,也就是说循环依赖的必要条件被破坏了,所以就不可能再发生循环依赖了。
至于我们使用时,这个属性为什么不是null,那是因为在JVM实例化结束后,Spring 又从自己的 bean 工厂中拿到待赋值的属性对应的对象(就算重新new一个也不会产生循环依赖),通过反射给赋上值的。
所以 @Autowired 本质上还是 setter 注入。
|| 总结
1、依赖注入和循环依赖没啥关系,依赖注入更侧重于由外部触发的赋值动作,而循环依赖是编码问题的一种表象。
2、如果真的发生循环依赖了,就只能等着堆栈溢出报错结束了。
3、Spring 只是用自己的注入方式避免了循环依赖,而不是解决了循环依赖。
4、就像死锁,一旦发生,就是无解的,只能说是编码过程中如何避免死锁。
参考资料:
《Dependency injection》 -- Dhanji R. Prasanna
《The Java Virtual Machine Specification(Java SE 8 Edition)》 -- Tim Lindholm、Frank Yellin、Gilad Bracha、Alex Buckley [5.5]
《深入理解Java虚拟机》 -- 周志明 [7.3]
https://docs.spring.io/spring-framework/docs/current/spring-framework-reference/core.html#beans-dependencies