Spring循环依赖详解
一、什么是循环依赖
循环依赖是指两个或多个Bean相互依赖,形成了一个闭环。例如:A依赖B,B依赖A。
1.1 示例代码
@Service
public class ServiceA {
@Autowired
private ServiceB serviceB;
}
@Service
public class ServiceB {
@Autowired
private ServiceA serviceA;
}
二、Spring Bean的创建过程
Spring管理Bean的生命周期分为两个主要阶段:
- 实例化(Instantiation): 创建Bean的实例
- 初始化(Initialization): 设置属性、依赖注入等
2.1 不同注入方式的区别
- 构造器注入:
@Service
public class ServiceA {
private final ServiceB serviceB;
// 创建实例时就需要依赖对象
public ServiceA(ServiceB serviceB) {
this.serviceB = serviceB;
}
}
- Set方法注入:
@Service
public class ServiceA {
private ServiceB serviceB;
// 先创建实例
public ServiceA() {
}
// 后注入依赖
@Autowired
public void setServiceB(ServiceB serviceB) {
this.serviceB = serviceB;
}
}
三、Spring如何解决循环依赖
Spring通过三级缓存机制解决循环依赖问题:
3.1 三级缓存的定义
public class DefaultSingletonBeanRegistry {
// 一级缓存:存放完全初始化好的Bean
private Map<String, Object> singletonObjects = new HashMap<>();
// 二级缓存:存放早期的Bean引用
private Map<String, Object> earlySingletonObjects = new HashMap<>();
// 三级缓存:存放Bean的工厂对象
private Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>();
}
3.2 三级缓存的作用
-
一级缓存(singletonObjects):
- 存放完全初始化好的Bean
- Bean的生命周期已经完全完成
- 可以直接使用
-
二级缓存(earlySingletonObjects):
- 存放早期的Bean引用
- 可能是原始对象,也可能是代理对象
- 用于解决循环依赖
-
三级缓存(singletonFactories):
- 存放Bean的工厂对象
- 主要用于处理AOP代理
- 延迟代理对象的创建
3.3 关键问题解析
1. Spring为什么要分两个阶段创建Bean?
Spring管理Bean对象分为两个阶段:
- 创建实例:实例化对象,但属性还未赋值
- 初始化实例:给对象的属性赋值、执行初始化方法等
这种分阶段的设计是解决循环依赖的关键。
2. 构造器注入为什么无法解决循环依赖?
@Service
public class ServiceA {
private final ServiceB serviceB;
// 创建实例的同时就需要完整的ServiceB
public ServiceA(ServiceB serviceB) {
this.serviceB = serviceB;
}
}
@Service
public class ServiceB {
private final ServiceA serviceA;
// 创建实例的同时就需要完整的ServiceA
public ServiceB(ServiceA serviceA) {
this.serviceA = serviceA;
}
}
- 构造器注入意味着创建实例时就需要依赖对象
- 无法分离创建实例和属性赋值的过程
- 导致无法提前暴露早期引用
3. Set注入为什么可以解决循环依赖?
@Service
public class ServiceA {
private ServiceB serviceB;
// 1. 先创建实例(无需依赖)
public ServiceA() {
}
// 2. 后注入属性
@Autowired
public void setServiceB(ServiceB serviceB) {
this.serviceB = serviceB;
}
}
- 可以先创建实例,后注入属性
- 创建实例后可以暴露早期引用
- 允许其他Bean先引用未完全初始化的实例
4. 为什么需要三级缓存?二级缓存不够吗?
这是最容易混淆的问题。三级缓存的核心目的是处理AOP代理:
@Service
public class UserService {
@Autowired
private OrderService orderService;
@Transactional // 需要AOP代理
public void createUser() {
// 如果注入的是原始对象而不是代理对象
// 这里的@Transactional注解将失效
}
}
如果只有二级缓存:
- 要么提前创建代理对象(可能创建不必要的代理)
- 要么存储原始对象(AOP失效)
三级缓存的优势:
- 存储工厂而不是具体对象
- 可以延迟决定是否需要创建代理
- 保证所有依赖注入的都是同一个对象(代理或原始)
5. AOP代理与事务为什么需要在对象创建时处理?
// 简化的代理逻辑
public class TransactionProxy extends UserService {
@Override
public void createUser() {
try {
// 开启事务
txManager.begin();
// 调用原始方法
super.createUser();
// 提交事务
txManager.commit();
} catch (Exception e) {
txManager.rollback();
}
}
}
- 事务等功能需要通过代理对象来实现
- 代理对象需要在Bean创建过程中生成
- 所有引用该Bean的地方都必须注入同一个代理对象
- 三级缓存可以保证AOP创建的时机和对象的一致性
3.4 为什么需要三级缓存
主要是为了处理AOP代理的情况:
@Service
public class UserServiceImpl implements UserService {
@Autowired
private OrderService orderService;
@Override
@Transactional // 需要AOP代理
public void createUser(String username) {
// ...
}
}
- 如果只有二级缓存:
// 问题场景
UserServiceImpl userService = new UserServiceImpl();
// 直接放入二级缓存
earlySingletonObjects.put("userService", userService);
// 后续需要AOP代理时
Object proxy = createProxy(userService);
// 这时二级缓存中的是原始对象,而不是代理对象
// OrderService已经注入了原始对象,导致AOP失效
- 使用三级缓存:
// 1. 创建实例
UserServiceImpl userService = new UserServiceImpl();
// 2. 放入三级缓存(工厂)
singletonFactories.put("userService", () -> {
// 延迟创建代理对象
return wrapIfNecessary(userService);
});
// 3. 当OrderService需要注入UserService时
Object userServiceProxy = getEarlyReference("userService");
// 这时才创建代理对象,并放入二级缓存
earlySingletonObjects.put("userService", userServiceProxy);
四、最佳实践
4.1 如何避免循环依赖
- 重构代码结构
- 使用@Lazy注解
- 使用事件机制解耦
- 使用三方类进行解耦
4.2 示例:使用事件机制解决循环依赖
// 1. 定义事件
public class UserCreatedEvent extends ApplicationEvent {
private String userId;
public UserCreatedEvent(Object source, String userId) {
super(source);
this.userId = userId;
}
}
// 2. 发布事件
@Service
public class UserService {
@Autowired
private ApplicationEventPublisher eventPublisher;
public void createUser(String userId) {
// 创建用户
eventPublisher.publishEvent(new UserCreatedEvent(this, userId));
}
}
// 3. 监听事件
@Service
public class OrderService {
@EventListener
public void handleUserCreated(UserCreatedEvent event) {
// 处理订单逻辑
}
}
五、总结
- Spring循环依赖主要通过三级缓存解决
- 构造器注入的循环依赖无法解决
- 三级缓存的主要目的是处理AOP代理
- 最好的解决方案是避免循环依赖
参考资料
- Spring官方文档
- 《Spring源码深度解析》