spring 依赖注入和循环依赖问题

在公司发现项目竟然有循环依赖问题。。
哈哈哈,挺搞笑的。

所以晚上我特地整理了一下关于这方面的资料与大家分享

首先说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.Resourcejavax.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;
  }
 
}

使用构造器注入的好处:

  1. 保证依赖不可变(final关键字)
  2. 保证依赖不为空(省去了我们对其检查)
  3. 保证返回客户端(调用)的代码的时候是完全初始化的状态
  4. 避免了循环依赖 (启动报错了)
  5. 提升了代码的可复用性

看看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 属性注入

当你在Spring框架中通过`@Autowired`注解或其他依赖注入方式将Mapper接口注入到一个类中时,如果没有出现问题,通常意味着Spring能够成功地识别并创建对应的实现类。然而,如果其他类无法找到已经注入的Mapper,可能是以下几个原因: 1. **缺少装配**:确保你在需要使用Mapper的类上也启用了@Autowired或手动配置了bean。例如,在Spring XML配置文件中为Mapper接口声明了一个对应的具体实现。 ```xml <bean id="yourMapper" class="com.example.YourMapperImpl" /> ``` 2. **包扫描范围**:确认是否有正确的包扫描路径设置,使得Spring能自动发现和初始化Mapper实现类。如果不正确,你需要添加相应的`@ComponentScan`或者`<context:component-scan>`标签,并指定包含Mapper实现类的包名。 3. **接口与实现类不在同一包下**:Spring默认只会扫描当前包及其子包下的Bean,所以如果Mapper接口和实现类不在同一个包内,你可能需要调整扫描路径。 4. **循环依赖**:检查是否存在循环依赖导致的bean解析顺序问题。这可能导致某些bean尚未被初始化就去依赖其他bean。 5. **Spring Boot Starter问题**:如果你在Spring Boot项目中,确认是否正确引入了相关的starter(如spring-boot-starter-data-jpa),它会自动处理JPA的Mapper配置。 为了解决这个问题,你可以按照以下步骤排查: 1. 查看日志:Spring会在启动过程中打印关于Bean初始化的日志,看看是否有异常信息。 2. 使用`@RefreshScope`:如果是在Spring Cloud环境下,尝试刷新应用,看看是否因为环境变化导致注入未生效。 3. 添加debug模式:在代码中加入`log.info("YourMapper: {}", yourMapper)`这样的日志,查看Mapper实例是否真正注入到了。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

菜鸟猫喵喵

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值