循环依赖:一个Spring经典坑

发布于 更新于 429 字 3 分钟阅读

#循环依赖:一个Spring经典坑

当你有两个 service​ 时,他们的业务互相关联,比如:用户service需要查询订单service,同时订单service又需要通过用户service来查询用户

这样就会形成一个闭环,如下:

image

#实际代码demo

UserService

Java
@Service
public class UserService {

    @Autowired
    private OrderService orderService;

    public Order getUserOrder(Long userId) {
        return orderService.getOrderByUser(userId);
    }

    public User getUser(Long userId) {
        // 模拟查询后User
        User user = new User();
        user.setId(1L);
        return user;
    }

}

OrderService

Java
@Service
public class OrderService {

    @Autowired
    private UserService userService;

    public Order getOrderByUser(Long userId) {
        User user = userService.getUser(userId);
        Order order = new Order();
        order.setOrderId("xxx");
        order.setAmount(100L);
        order.setUserId(user.getId());
        return order;
    }

}

使用时,会报错如下:

log
Caused by: org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'orderService': Unsatisfied dependency expressed through field 'userService': Error creating bean with name 'userService': Unsatisfied dependency expressed through field 'orderService': Error creating bean with name 'orderService': Requested bean is currently in creation: Is there an unresolvable circular reference or an asynchronous initialization dependency?
	at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.resolveFieldValue(AutowiredAnnotationBeanPostProcessor.java:767)
.....

#临时解决方案

UserService​依赖添加 @lazy

Java
@Lazy
@Autowired
private UserService userService;

Tip

#什么是 @lazy

@Lazy​ 是 Spring 框架中的一个注解,用于实现 Bean 的延迟初始化


#核心概念

#默认行为 vs @Lazy

场景 初始化时机
默认(无@Lazy) Spring 容器启动时立即创建 Bean
使用 @Lazy 首次从容器获取 Bean 时才创建

#使用方式

#1️⃣ 类级别使用

Java
@Component
@Lazy  // 整个类延迟初始化
public class ExpensiveService {
    public ExpensiveService() {
        System.out.println("ExpensiveService 被创建了!");
        // 假设这里有耗时的初始化操作
    }
    
    public void doSomething() {
        System.out.println("执行操作...");
    }
}

#2️⃣ 配置类中使用

Java
@Configuration
public class AppConfig {
    
    @Bean
    @Lazy  // 该 Bean 延迟初始化
    public DataSource dataSource() {
        System.out.println("创建数据库连接池...");
        return new HikariDataSource();
    }
}

#3️⃣ 注入时使用(推荐)

Java
@Service
public class OrderService {
    
    private final ExpensiveService expensiveService;
    
    @Autowired
    @Lazy  // 注入时标记为延迟加载
    public OrderService(ExpensiveService expensiveService) {
        this.expensiveService = expensiveService;
    }
}

#工作原理图解

Text
┌─────────────────────────────────────────────────────┐
│                  Spring 容器启动                      │
├─────────────────────────────────────────────────────┤
│                                                      │
│   @Component          @Component @Lazy               │
│   ┌─────────┐         ┌─────────────┐               │
│   │ ServiceA │         │  ServiceB   │ ← 不创建!    │
│   │  ✓创建   │         │  (仅注册)    │               │
│   └─────────┘         └─────────────┘               │
│                                                      │
└─────────────────────────────────────────────────────┘
                         │
                         ▼ 当首次使用 ServiceB 时
┌─────────────────────────────────────────────────────┐
│              ServiceB 首次被调用                       │
├─────────────────────────────────────────────────────┤
│                                                      │
│   ┌─────────────┐                                    │
│   │  ServiceB   │ ← 此时才真正创建!                  │
│   │   ✓创建     │                                    │
│   └─────────────┘                                    │
│                                                      │
└─────────────────────────────────────────────────────┘

#实际应用场景

#场景1:解决循环依赖

Java
@Service
public class ServiceA {
    @Autowired
    @Lazy  // 打破循环依赖
    private ServiceB serviceB;
}

@Service
public class ServiceB {
    @Autowired
    private ServiceA serviceA;
}

#场景2:重量级资源延迟加载

Java
@Service
public class ReportService {
    
    @Lazy
    @Autowired
    private MLModelService mlModelService; // 加载大型机器学习模型
    
    public Report generateReport() {
        // 只有在生成报告时才加载模型
        return mlModelService.analyze();
    }
}

#场景3:可选功能

Java
@Component
public class PaymentService {
    
    @Lazy
    @Autowired(required = false)
    private Optional<wechatpayservice> wechatPay; // 可选的微信支付
    
    public void pay() {
        wechatPay.ifPresent(service -> service.pay());
    }
}

#全局延迟配置(Spring Boot)

application.yml 中可以全局开启延迟初始化:

YAML
spring:
  main:
    lazy-initialization: true  # 所有 Bean 都延迟初始化

#注意事项

注意点 说明
单例特性不变 @Lazy Bean 仍然是单例,首次创建后会缓存
@Lazy 失效情况 @PostConstruct​、SmartInitializingSingleton 等主动触发时可能失效
测试困难 延迟加载的 Bean 在测试中可能需要额外处理
启动快,首次慢 启动时间缩短,但首次调用会有延迟

#总结

Text
@Lazy = 推迟 Bean 的创建时机

何时使用?
├── 解决循环依赖
├── 减少启动时间(轻量级应用)
├── 延迟加载重量级资源
└── 可选/按需加载的功能

#Spring2.6前的情况

在Spring2.6前,Spring遇到这种情况会自动帮你兜底,除了你自己用构造器创建的bean。

但后续删了这个特性,因为这样子的情况本就是设计问题,使用 @lazy 只是缓兵之计。

#如何避免循环依赖

image

zxb的博客

评论

还没有评论,来说点什么吧。

评论经发布者审核后公开
46 篇文档

文档树

20 个章节

本文目录

搜索文档

输入关键词,立即搜索当前分享。