人话解释如何解决Spring循环依赖,三级缓存能解决所有循环依赖吗?

参考文章

三级缓存数据结构

三级缓存的数据结构都是 Map<k,v>类型:

1
2
3
4
5
6
7
8
//一级缓存,存储已经完全初始化的 Bean。
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);

//二级缓存,存储的 Bean 处于初始化过程中(例如,构造函数调用期间,但还未完全初始化的 Bean)。
private final Map<String, ObjectearlySingletonObjects = new HashMap<>(16);

//三级缓存,存储的是 ObjectFactory 对象,这些对象可以生成早期的 bean 引用。当一个 bean 正在创建过程中,如果它被其他 bean 依赖,那么这个正在创建的 bean 就会通过这个 ObjectFactory 来创建一个早期引用,从而解决循环依赖的问题。
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16)

如果只有二级缓存

如果 A 类加了@Transactional  等需要创建代理的注解,那么最终需要暴露给其他 Bean 的应该是 ServiceA 的代理对象,而非 ServiceA 的原始对象!,在上述二级缓存方案中,B 拿到的是 A 的原始对象。但最终 A 完成后,放入一级缓存的是 A 的代理对象。这就导致了:B 持有的是 A 的原始对象。而其他没有和产生 A 依赖循环的地方注入的是 A 的代理对象。

所以说二级缓存可以解决循环依赖问题,但无法正确处理需要 AOP 代理的 Bean。

三级缓存

为了解决代理问题,Spring 引入了第三级缓存。它的核心不是一个直接存放对象(Object)的缓存,而是一个存放  ObjectFactory(对象工厂)的缓存。

在循环依赖场景下,二级缓存 的作用就是临时缓存实现复用:只有第一次需要“提前引用 A”时,Spring 才会调用三级缓存里的 ObjectFactory.getObjectA() 生成了早期引用 A 并放到二级缓存,后续对于每个依赖 A 的地方都不用重新触发三级缓存的 A 的工厂方法,直接去二级缓存取即可,性能更优。

三级缓存能解决所有循环依赖吗?

不能,无法解决@Async 注解 + 依赖循环的问题。

我们知道 Bean 的声明流程是实例化、属性赋值、初始化、创建使用、销毁,@Async 标注的 Bean,是在对象完全初始化(成品 Bean)完才创建的,而依赖循环中只有半成品的 Bean

解决方法:

把 @Async 抽到一个独立的、不参与循环的新逻辑中

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
// 原来(有问题):
@Service
public class OrderService {
    @Autowired
    private UserService userService;

    @Async
    public void sendEmail(){
    }
}

@Service
public class UserService {
    @Autowired
    private OrderService orderService; // 循环依赖 + @Async异步 → 崩!
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// 1. 普通服务,无 @Async,可安全循环(或更好:消除循环)
@Service
public class OrderService {
    @Autowired
    private AsyncTaskService asyncTaskService; // 不循环

    public void placeOrder() {
        asyncTaskService.sendEmail(); // 转发给纯异步服务
    }
}

@Service
public class UserService {
    @Autowired
    private OrderService orderService;
}

// 2. 纯异步服务,不参与任何循环依赖
@Service
public class AsyncTaskService {

    @Async
    public void sendEmail() {
    }
}

使用 ApplicationContext 手动获取(应急用)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
@Service
public class OrderService implements ApplicationContextAware {

    private ApplicationContext context;

    public void doSomething() {
        // 不通过字段注入,而是运行时从容器拿
        UserService userService = context.getBean(UserService.class);
        userService.xxx();
    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) {
        this.context = applicationContext;
    }

    @Async
    public void asyncMethod(){
    }
}

Lazy 延迟调用

让依赖首次使用时才初始化,打破启动时的循环

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
@Service
public class OrderService {
    @Lazy
    @Autowired
    private UserService userService; // 启动时不立即创建,避免循环

    @Async
    public void asyncMethod(){
    }
}

@Service
public class UserService {
    @Autowired
    private OrderService orderService;
}
comments powered by Disqus
使用 Hugo 构建
主题 StackJimmy 设计