JAVA全系列 教程
3762个小节阅读:7092.7k
目录
C语言快速入门
JAVA全系列 教程
面向对象的程序设计语言
Python全系列 教程
Python3.x版本,未来主流的版本
人工智能 教程
顺势而为,AI创新未来
大厂算法 教程
算法,程序员自我提升必经之路
C++ 教程
一门通用计算机编程语言
微服务 教程
目前业界流行的框架组合
web前端全系列 教程
通向WEB技术世界的钥匙
大数据全系列 教程
站在云端操控万千数据
AIGC全能工具班
A A
White Night
我们前面学习了DI的两种方式,分别是构造注入和设值注入。这两种方式,官方更推荐使用构造注入。
网址:https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#beans-setter-injection
但是有一种情况确实无法使用构造注入,就是循环依赖的问题。
循环依赖就是多个Bean相互依赖,形成一个闭环。
下面我们先看看Spring 官方中对构造注入时出现循环注入的解释。
当两个类都是用构造注入时,没有等当前类实例化完成就需要注入另一个类,而另一个类没有实例化完整还需要注入当前类,所以这种情况是无法解决循环注入问题的的。会出现BeanCurrentlyInCreationException异常。
其实Spring循环注入问题并不是我们开发者去解决的,而是Spring本身会根据我们的代码进行解决。其中有的情况能解决,有的会直接报异常。汇总如下:
通过这六种情况可以看出来,只要一个Bean使用设值注入,并且scope为singleton,就没有循环注入异常。
下面通过代码给小伙伴们演示一下构造注入时循环注入的效果。
在搭建好Spring环境的项目中新建两个类:
先新建com.bjsxt.circular.Teacher类代表老师
xxxxxxxxxx
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Teacher {
private Student student;
}
然后在新建个com.bjsxt.circular.Student类,代表学生
xxxxxxxxxx
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Student {
private Teacher teacher;
}
在Spring的配置文件applicationContext.xml中设置两个Bean的循环注入
xxxxxxxxxx
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="student" class="com.bjsxt.circular.Student">
<constructor-arg name="teacher" ref="teacher"></constructor-arg>
</bean>
<bean id="teacher" class="com.bjsxt.circular.Teacher">
<constructor-arg name="student" ref="student"></constructor-arg>
</bean>
</beans>
最后在测试类com.bjsxt.test.CircularTest中编写测试代码
xxxxxxxxxx
@SpringJUnitConfig
@ContextConfiguration("classpath:applicationContext-circular.xml")
public class CircularTest {
@Autowired
Teacher teacher;
@Test
void test(){
System.out.println(teacher);
}
}
运行测试类后会发现IDEA控制台出现异常。最后一个Cased by的异常类型是Caused by: org.springframework.beans.factory.BeanCreationException,代表着发生了循环注入问题。
xxxxxxxxxx
Caused by: org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'student': Requested bean is currently in creation: Is there an unresolvable circular reference?
这种方式可以理解,因为在Java代码中下面代码就编译错误了。
xxxxxxxxxx
Teacher teacher = new Teacher(student);// 编译错误
Student student = new Student(teacher);
第二种情况,两个Bean的scope都是prototype类型,依然使用构造注入。运行后依然出现BeanCurrentlyInCreationException
xxxxxxxxxx
<bean id="student" class="com.bjsxt.circular.Student" scope="prototype">
<constructor-arg name="teacher" ref="teacher"></constructor-arg>
</bean>
<bean id="teacher" class="com.bjsxt.circular.Teacher" scope="prototype">
<constructor-arg name="student" ref="student"></constructor-arg>
</bean>
第三种情况,使用设值注入。如果Bean的scope属性为prototype时,循环注入的效果。
我们先把配置修改一下,注入的方式修改为设置注入,并设置Bean的scope="prototype"
xxxxxxxxxx
<bean id="student" class="com.bjsxt.circular.Student" scope="prototype">
<property name="teacher" ref="teacher"></property>
</bean>
<bean id="teacher" class="com.bjsxt.circular.Teacher" scope="prototype">
<property name="student" ref="student"></property>
</bean>
运行测试类,发现依然会产生循环注入问题。控制台还是出现了BeanCurrentlyInCreationException异常。
第四种情况,使用设值注入。Bean的scope属性为singleton时,循环注入的效果。
只需要修改配置文件中,把<bean>
的scope属性设置为singleton就可以了。
xxxxxxxxxx
<bean id="student" class="com.bjsxt.circular.Student" scope="singleton">
<property name="teacher" ref="teacher"></property>
</bean>
<bean id="teacher" class="com.bjsxt.circular.Teacher" scope="singleton">
<property name="student" ref="student"></property>
</bean>
运行后没有循环注入异常了,但是出现StackOverflowError.是因为@Data生成的toString()中循环包含对方对象。
xxxxxxxxxx
java.lang.StackOverflowError
修改两个类的,在关联属性都添加上不被toString()输出。
xxxxxxxxxx
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Student {
@ToString.Exclude
private Teacher teacher;
}
xxxxxxxxxx
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Teacher {
@ToString.Exclude
private Student student;
}
再次运行,程序可以正常输出。
这种方式之所以可以成功运行是因为单例默认下有三级缓存(DefaultSingletonBeanRegistry),可以暂时缓存没有被实例化完成的Bean。这样就不用考虑Bean实例化时先后问题,也就不会出现循环注入问题了。
第四种情况,使用设值注入。一个类scope="singleton",另外一个类scope="prototype"。
xxxxxxxxxx
<bean id="student" class="com.bjsxt.circular.Student" scope="prototype">
<property name="teacher" ref="teacher"></property>
</bean>
<bean id="teacher" class="com.bjsxt.circular.Teacher" scope="singleton">
<property name="student" ref="student"></property>
</bean>
这时发现,两个类都可以被成功注入。
第五种情况,一个类使用构造注入,另一个类使用设置注入并且scope="singleton"
xxxxxxxxxx
<bean id="student" class="com.bjsxt.circular.Student" scope="singleton">
<property name="teacher" ref="teacher"></property>
</bean>
<bean id="teacher" class="com.bjsxt.circular.Teacher" >
<constructor-arg name="student" ref="student"></constructor-arg>
</bean>
通过这些演示后小伙伴们知道了只要Bean的scope="singleton"就不会出现循环注入问题。那么在平时我们进行代码编写时,尽量避开循环注入。如果实在无法避开,类中涉及到两个类的相互引用。例如:双向多对一、双向一对一的关系中就必须有双向引用。这时最好使用设值注入,并且scope设置为singleton。