在公司发现项目竟然有循环依赖问题。。
哈哈哈,挺搞笑的。
所以晚上我特地整理了一下关于这方面的资料与大家分享
首先说spring的三级缓存解决了哪些循环依赖问题?
循环依赖问题在Spring中主要有三种情况:
- 通过构造方法进行依赖注入时产生的循环依赖问题。
- 通过 Setter注入或者Field注入 进行依赖注入且是在多例(原型)模式下产生的循环依赖问题。
- 通过 Setter注入或者Field注入 进行依赖注入且是在单例模式下产生的循环依赖问题。
注意:在Spring中,只有【第三种方式】的循环依赖问题被解决了,其他两种方式在遇到循环依赖问题时都会产生异常。
其实也很好解释:
- 第一种构造方法注入的情况下,在new对象的时候就会堵塞住了,其实也就是”先有鸡还是先有蛋“的历史难题。
- 第二种setter方法 && 多例的情况下,每一次getBean()时,都会产生一个新的Bean,如此反复下去就会有无穷无尽的Bean产生了,最终就会导致OOM问题的出现。
1. What Is a Circular Dependency? 什么是循环依赖?
当一个 bean A 依赖于另一个 bean B,而 bean B 也依赖于 bean A 时,就会发生循环依赖:
Bean A → Bean B → Bean A
当然,我们可以存在更多的 bean:
Bean A → Bean B → Bean C → Bean D → Bean E → Bean A
2. What Happens in Spring 2. 会发生什么?
当 Spring 上下文加载所有 bean 时,它尝试按照 bean 完全工作所需的顺序创建 bean。
假设我们没有循环依赖,我们有这样的东西:
Bean A → Bean B → Bean C
创建的顺序应该是:
Spring 将创建 bean C,然后创建 bean B (并将 bean C 注入其中) ,然后创建 bean A (并将 bean B 注入其中)。
但是使用循环依赖关系,Spring 无法决定首先创建哪个 bean,因为它们彼此依赖。在这些情况下,Spring 将在加载上下文时引发 BeanCurrentlyInCreationException
。
在 Spring 中使用构造函数注入时可能会发生这种情况。如果我们使用其他类型的注入,就不会出现这个问题,因为依赖项将在需要时注入,而不是在上下文加载时注入。
当发生依赖注入时,一些解决方案
1. 重新设计代码
当我们有一个循环依赖关系时,很可能我们有一个设计问题,并且责任没有很好地分离。我们应该尝试正确地重新设计组件,以便它们的层次结构设计良好,不需要循环依赖。
然而,有许多可能的原因,我们可能无法做一个重新设计,如遗留代码,代码已经测试,无法修改,没有足够的时间或资源进行完整的重新设计,等等。如果我们不能重新设计组件,我们可以尝试一些变通方法。
2. 使用 @Lazy 注解
这是一个简单的处理方法。
@Lazy
注解会告诉 Spring 懒惰地初始化其中一个 bean。因此,它不会完全初始化 bean,而是创建一个代理将其注入到另一个 bean 中。注入的 bean 只有在第一次需要时才会被完全创建。
比如下面这样:
@Component
public class CircularDependencyA {
private CircularDependencyB circB;
@Autowired
public CircularDependencyA(@Lazy CircularDependencyB circB) {
this.circB = circB;
}
}
3. 使用 Setter/Field 注入
说明:在 Spring 4.5 及更高的版本中,setXXX 上面的 @Autowired 注解是可以不写的。
最流行的变通方法之一,也是 Spring 文档所建议的,是使用 setter 注入。
简而言之,我们可以通过改变 bean 的连接方式来解决这个问题ーー使用 setter 注入(或字段注入)而不是构造函数注入。通过这种方式,Spring 创建 bean,但是直到需要时才注入依赖项。
比如这样:
@Component
public class CircularDependencyA {
private CircularDependencyB circB;
@Autowired
public void setCircB(CircularDependencyB circB) {
this.circB = circB;
}
public CircularDependencyB getCircB() {
return circB;
}
}
@Component
public class CircularDependencyB {
private CircularDependencyA circA;
private String message = "Hi!";
@Autowired
public void setCircA(CircularDependencyA circA) {
this.circA = circA;
}
public String getMessage() {
return message;
}
}
说说spring 中的依赖注入
Spring 中有这么3种依赖注入的方式:
- 基于 field 注入(属性注入)
- 基于 setter 注入
- 基于 constructor 注入(构造器注入)
2.1field注入
在 spring 中与依赖注入相关的注解,即 @Resource 、 @Inject 和 @Autowired
他们提供了一种声明式的的方式来解决依赖关系:
比如这样:
@Autowired
ArbitraryClass arbObject;
@Autowired 注解属于org.springframework.beans.factory.annotation
包。
剩下两个注解属于 Java 扩展包:javax.annotation.Resource
和javax.inject.Inject
。
@Resource
是JSR-250定义的注解。Spring 在 CommonAnnotationBeanPostProcessor
实现了对JSR-250
的注解的处理,其中就包括@Resource
。
@Resource有2个属性name和type。在spring中name属性定义为bean的名字,type这是bean的类型。如果属性上加@Resource注解那么他的注入流程是
- 如果同时指定了name和type,则从Spring上下文中找到唯一匹配的bean进行装配,找不到则抛出异常。
- 如果指定了name,则从上下文中查找名称匹配的bean进行装配,找不到则抛出异常。
- 如果指定了type,则从上下文中找到类型匹配的唯一bean进行装配,找不到或是找到多个,都会抛出异常。
- 如果既没有指定name,又没有指定type,则默认按照byName方式进行装配;如果没有匹配,按照byType进行装配。
@Autowired 只根据type进行注入,不会去匹配name。如果涉及到type无法辨别注入对象时,那需要依赖 @Qualifier 或 @Primary 注解一起来修饰。
像这样:
public class Controller {
@Autowired
@Qualifier(name="AServer")
private AServer aSver;
}
( @Inject 注解 和 @Autowired 注入流程相似)
如果要求所有的依赖项都由 Spring 框架处理,那么唯一的选择是 @Autowired 注释。
思考 用 @Resource 、 @Inject 还是 @Autowired
现在用的最多的是 @Resource 和 @Autowired
观点1:@Resource是J2EE的注解是Java自已的东西使用 @Resource可以减少代码和Spring之间的耦合。
原文:https://juejin.cn/post/7064823104584810532
@Resource是J2EE的注解是Java自已的东西使用 @Resource可以减少代码和Spring之间的耦合。
观点2:确认是基于Spring框架的项目,使用@Autowired就没有没有问题,使用@Autowired会显得更local,整体比较得体,也能提醒大家这是在用spring框架,使用@Resource可能会让人不确定这个容器是不是spring容器;
原文:https://juejin.cn/post/7023618746501562399#heading-12 评论区
@Autowired属性注入和spring框架绑定的较深,比如spring定义了@Autowired注解,
然后通过AutowiredAnnotationBeanPostProcessor实现了注解,
而@Resource是jsr约定的注解,所有的DI容器化的框架基本都需要去实现这个的注入,
比如spring对这个的实现是 CommonAnnotationBeanPostProcessor
所以如果你的项目确认是基于Spring框架的项目,使用@Autowired就没有没有问题,
使用@Autowired会显得更local,整体比较得体,也能提醒大家这是在用spring框架,
使用@Resource可能会让人不确定这个容器是不是spring容器;
还有一点大家都觉得jsr虽然是规范,但是也是约束,这种约束感可能使得他没在程序员群体流行起来
2.2setter注入
对于基于 setter 的注入,Spring是这么说的:
Setter injection should primarily only be used for optional dependencies that can be assigned reasonable default values within the class. Otherwise, not-null checks must be performed everywhere the code uses the dependency. One benefit of setter injection is that setter methods make objects of that class amenable to reconfiguration or re-injection later.
基于 setter 的注入,则只应该被用于注入非必需的依赖,同时在类中应该对这个依赖提供一个合理的默认值。如果使用 setter 注入必需的依赖,那么将会有过多的 null 检查充斥在代码中。使用 setter 注入的一个优点是,这个依赖可以很方便的被改变或者重新注入。
2.3构造器注入
@Controller
public class FooController {
private final FooService fooService;
@Autowired
public FooController(FooService fooService) {
this.fooService = fooService;
}
}
使用构造器注入的好处:
- 保证依赖不可变(final关键字)
- 保证依赖不为空(省去了我们对其检查)
- 保证返回客户端(调用)的代码的时候是完全初始化的状态
- 避免了循环依赖 (启动报错了)
- 提升了代码的可复用性
看看Spring在文档里怎么说:
The Spring team generally advocates constructor injection as it enables one to implement application components as immutable objects and to ensure that required dependencies are not
null
. Furthermore constructor-injected components are always returned to client (calling) code in a fully initialized state.
Spring 团队提倡使用基于构造方法的注入,因为这样一方面可以将依赖注入到一个不可变的变量中 (注:final
修饰的变量),另一方面也可以保证这些变量的值不会是 null。此外,经过构造方法完成依赖注入的组件 (注:比如各个 service
),在被调用时可以保证它们都完全准备好了。与此同时,从代码质量的角度来看,一个巨大的构造方法通常代表着出现了代码异味,这个类可能承担了过多的责任。
对于循环依赖问题:如果使用构造器注入,在spring项目启动的时候,就会抛出:BeanCurrentlyInCreationException:Requested bean is currently in creation: Is there an unresolvable circular reference?从而提醒你避免循环依赖,如果是field注入的话,启动的时候不会报错,在使用那个bean的时候才会报错。
强制依赖就用构造器方式
可选、可变的依赖就用setter注入
小声的说如果是开发,我就喜欢用 @Resource 属性注入