Spring循环依赖详解

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的生命周期分为两个主要阶段:

  1. 实例化(Instantiation): 创建Bean的实例
  2. 初始化(Initialization): 设置属性、依赖注入等

2.1 不同注入方式的区别

  1. 构造器注入:
@Service
public class ServiceA {
    private final ServiceB serviceB;
    
    // 创建实例时就需要依赖对象
    public ServiceA(ServiceB serviceB) {
        this.serviceB = serviceB;
    }
}
  1. 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 三级缓存的作用

  1. 一级缓存(singletonObjects):

    • 存放完全初始化好的Bean
    • Bean的生命周期已经完全完成
    • 可以直接使用
  2. 二级缓存(earlySingletonObjects):

    • 存放早期的Bean引用
    • 可能是原始对象,也可能是代理对象
    • 用于解决循环依赖
  3. 三级缓存(singletonFactories):

    • 存放Bean的工厂对象
    • 主要用于处理AOP代理
    • 延迟代理对象的创建

3.3 关键问题解析

1. Spring为什么要分两个阶段创建Bean?

Spring管理Bean对象分为两个阶段:

  1. 创建实例:实例化对象,但属性还未赋值
  2. 初始化实例:给对象的属性赋值、执行初始化方法等

这种分阶段的设计是解决循环依赖的关键。

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) {
        // ...
    }
}
  1. 如果只有二级缓存:
// 问题场景
UserServiceImpl userService = new UserServiceImpl();
// 直接放入二级缓存
earlySingletonObjects.put("userService", userService);

// 后续需要AOP代理时
Object proxy = createProxy(userService);
// 这时二级缓存中的是原始对象,而不是代理对象
// OrderService已经注入了原始对象,导致AOP失效
  1. 使用三级缓存:
// 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 如何避免循环依赖

  1. 重构代码结构
  2. 使用@Lazy注解
  3. 使用事件机制解耦
  4. 使用三方类进行解耦

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) {
        // 处理订单逻辑
    }
}

五、总结

  1. Spring循环依赖主要通过三级缓存解决
  2. 构造器注入的循环依赖无法解决
  3. 三级缓存的主要目的是处理AOP代理
  4. 最好的解决方案是避免循环依赖

参考资料

  1. Spring官方文档
  2. 《Spring源码深度解析》
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

漫漫求

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

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

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

打赏作者

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

抵扣说明:

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

余额充值