如何熟记spring循环依赖 (一)

spring循环依赖问题真的是面试题里一道难题,

工作中业务类层级太多,太过复杂的时候,启动项目的时候也经常会遇到循环依赖,

所以不管是作为面试题和工作需要都应该理解sping的循环依赖。

 

但是spring循环依赖是一个难点,笔者经常是看了又忘,如何才能熟练掌握这个知识点呢?

回想自己每次都是大概懂,浅尝则止,我想问题大概就是没有给自己说明白,就以为明白了,不去深入研究留的毛病。之前报了一个私教班,老师对我的评价是思维跳跃性太高,一个问题还没说明白,又去说另外一个问题。那么这一次,我选择写出自己的理解。

 

首先我写了一个循环依赖的例子,先运行看看报啥错

package com.example.demo;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;

// 第一层:服务A依赖服务B
@Component
class ServiceA {
private final ServiceB serviceB;
private ServiceD serviceD;

@Autowired
public ServiceA(ServiceB serviceB) {
this.serviceB = serviceB;
System.out.println("ServiceA initialized");
}

@Autowired
public void setServiceD(ServiceD serviceD) {
this.serviceD = serviceD;
System.out.println("ServiceD injected into ServiceA");
}

public void operationA() {
System.out.println("ServiceA -> operationA()");
serviceB.operationB();
}
}

// 第二层:服务B依赖服务C
@Component
class ServiceB {
private final ServiceC serviceC;

@Autowired
public ServiceB(ServiceC serviceC) {
this.serviceC = serviceC;
System.out.println("ServiceB initialized");
}

public void operationB() {
System.out.println("ServiceB -> operationB()");
serviceC.operationC();
}
}

// 第三层:服务C依赖服务D
@Component
class ServiceC {
private final ServiceD serviceD;

@Autowired
public ServiceC(ServiceD serviceD) {
this.serviceD = serviceD;
System.out.println("ServiceC initialized");
}

public void operationC() {
System.out.println("ServiceC -> operationC()");
serviceD.operationD();
}
}

// 第四层:服务D通过工厂方法创建,依赖服务A
@Component
class ServiceD {
private final ServiceA serviceA;
private final PrototypeBean prototypeBean;

@Autowired
public ServiceD(ServiceA serviceA, PrototypeBean prototypeBean) {
this.serviceA = serviceA;
this.prototypeBean = prototypeBean;
System.out.println("ServiceD initialized");
}

public void operationD() {
System.out.println("ServiceD -> operationD()");
prototypeBean.doSomething();
serviceA.operationA(); // 循环依赖点
}
}

// 原型作用域Bean,增加复杂度
@Component
@Scope("prototype")
class PrototypeBean {
public void doSomething() {
System.out.println("PrototypeBean is doing something");
}
}

@Configuration
@ComponentScan
class AppConfig {
@Bean
public FactoryService factoryService() {
return new FactoryService();
}
}

// 工厂类,模拟更复杂的依赖关系
@Component
class FactoryService {
private final ServiceC serviceC;

@Autowired
public FactoryService(ServiceC serviceC) {
this.serviceC = serviceC;
}

public void createResource() {
System.out.println("Creating resource using ServiceC");
serviceC.operationC();
}
}

public class ComplexCircularDependencyDemo {
public static void main(String[] args) {
try (AnnotationConfigApplicationContext context =
new AnnotationConfigApplicationContext(AppConfig.class)) {
ServiceA serviceA = context.getBean(ServiceA.class);
serviceA.operationA();
} catch (Exception e) {
e.printStackTrace();
}
}
}

 

 

 

如何解决呢

解决方案

 

三种解决方案的说明:

 

  1. Setter 注入方案:
    • 将 ServiceA 和 ServiceD 的构造器注入改为 Setter 注入
    • 利用 Spring 的三级缓存机制解决循环依赖
    • 适用于非必需依赖的场景
  2. 应用上下文查找方案:
    • ServiceD 实现 ApplicationContextAware 接口
    • 在需要使用 ServiceA 时通过应用上下文获取
    • 延迟依赖解析,避免初始化时的循环
  3. 依赖倒置方案:
    • 定义公共接口 BusinessLogic
    • ServiceA 和 ServiceD 实现该接口
    • 通过接口列表注入依赖,消除直接依赖关系
    • 符合 SOLID 设计原则,推荐用于复杂业务场景

 

根据项目实际情况选择合适的解决方案,通常推荐优先使用依赖倒置重构业务逻辑,其次考虑 Setter 注入,最后才使用应用上下文查找这种侵入式方案。
posted @ 2025-05-15 23:36  余明星  阅读(5)  评论(0)    收藏  举报