目录
- Spring Boot 启动失败:循环依赖排查到懒加载配置的坑
- 摘要
- 1. 循环依赖问题概述
- 1.1 什么是循环依赖
- 1.2 Spring的三级缓存机制
- 2. 循环依赖检测机制演进
- 2.1 Spring Boot 2.6版本的重大变化
- 2.2 配置项详解
- 3. 循环依赖类型分析
- 3.1 不同注入方式的循环依赖表现
- 3.2 构造器注入循环依赖示例
- 3.3 Setter注入解决方案
- 4. 懒加载配置的陷阱
- 4.1 懒加载与循环依赖的关系
- 4.2 懒加载配置示例
- 5. 实战排查方法
- 5.1 循环依赖检测工具
- 5.2 启动日志分析
- 6. 解决方案最佳实践
- 6.1 架构重构方案
- 6.2 事件驱动解耦
- 6.3 接口抽象解耦
- 7. 性能影响分析
- 7.1 不同解决方案的性能对比
- 7.2 内存使用分析
- 8. 监控与告警
- 8.1 循环依赖监控指标
- 8.2 告警配置
- 9. 故障案例复盘
- 9.1 生产环境故障分析
- 9.2 根因分析
- 9.3 解决方案实施
- 10. 最佳实践总结
- 10.1 设计原则
- 10.2 代码规范
- 10.3 配置管理
- 11. 工具与资源
- 11.1 开发工具推荐
- 11.2 检测脚本
- 总结
- 参考链接
Spring Boot 启动失败:循环依赖排查到懒加载配置的坑
摘要
作为一名在Spring Boot生态中摸爬滚打多年的开发者,我深知循环依赖问题是每个Java工程师都会遇到的经典难题。最近在一个微服务项目中,我遭遇了一个看似简单却极其隐蔽的启动失败问题:应用在本地开发环境运行正常,但在生产环境部署时却频繁出现循环依赖异常。经过深入排查,我发现这个问题的根源竟然隐藏在Spring Boot 2.6版本后的懒加载配置变更中。
这次排查过程让我重新审视了Spring容器的依赖php注入机制,从最基础的Bean生命周期到高级的循环依赖检测算法,从传统的setter注入到现代的构造器注入最佳实践。我发现许多开发者对循环依赖的理解还停留在表面,往往只知道使用@Lazy注解来"解决"问题,却不明白背后的原理和潜在风险。更令人担忧的是,随着Spring Boot版本的不断演进,一些默认配置的变化可能会让原本运行良好的代码突然出现问题。
在这篇文章中,我将从一个真实的生产环境故障案例出发,带你深入了解Spring Boot循环依赖的检测机制、排查方法和解决方案。我们将探讨从Spring 5.1到Spring Boot 2.6版本中循环依赖处理逻辑的重要变化,分析不同注入方式对循环依赖的影响,并提供一套完整的最佳实践指南。通过系统性的分析和实战演练,你将掌握如何在复杂的企业级应用中优雅地处理循环依赖问题,避免因为配置不当而导致的生产事故。
1. 循环依赖问题概述
1.1 什么是循环依赖
循环依赖是指两个或多个Bean之间存在相互依赖的关系,形成一个闭环。在Spring容器初始化过程中,如果检测到循环依赖且无法通过三级缓存机制解决,就会抛出BeanCurrentlyInCreationException
异常。
图1:循环依赖关系图 - 展示Bean间的相互依赖关系
1.2 Spring的三级缓存机制
Spring通过三级缓存来解决循环依赖问题:
// Spring容器中的三级缓存 public class DefaultSingletonBeanRegistry { // 一级缓存:完整的Bean实例 private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256); // 二级缓存:早期Bean实例(未完成属性注入) private final Map<String, Object> earlySingletonObjects = new HashMap<>(16); // 三级缓存:Bean工厂对象 private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16); /** * 获取单例Bean的核心方法 * 依次从三级缓存中查找Bean实例 */ protected Object getSingleton(String beanName, boolean allowEarlyReference) { // 从一级缓存获取完整Bean Object singletonObject = this.singletonObjects.get(beanName); if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) { synchronized (this.singletonObjects) { // 从二级缓存获取早期Bean singletonObject = this.earlySingletonObjects.get(beanName); if (singletonObject == null && allowEarlyReference) { // 从三级缓存获取Bean工厂 ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName); if (singletonFactory != null) { singletonObject = singletonFactory.getObject(); // 将早期Bean放入二级缓存 this.earlySingletonObjects.put(beanName, singletonObject); this.singletonFactories.remove(beanName); } } } } return singletonObject; } }
这段代码展示了Spring三级缓存的核心逻辑:首先尝试从一级缓存获取完整的Bean实例,如果获取不到且Bean正在创建中,则依次从二级和三级缓存中查找。
2. 循环依赖检测机制演进
2.1 Spring Boot 2.6版本的重大变化
Spring Boot 2.6版本引入了一个重要变化:默认禁用了循环依赖。这个变化通过spring.main.allow-circular-references
配置项控制。
图2:Spring循环依赖处理演进时间线 - 展示关键版本变化
2.2 配置项详解
# application.yml spring: main: # 是否允许循环依赖(Spring Boot 2.6+默认为false) allow-circular-references: true # 懒加载配置 lazy-initialization: false # 更细粒度的配置 management: endpoints: web: exposure: include: beans,health,info
这个配置变化的影响范围很广,许多原本运行正常的应用在升级到Spring Boot 2.6后可能会遇到启动失败的问题。
3. 循环依赖类型分析
3.1 不同注入方式的循环依赖表现
注入方式 | 循环依赖支持 | 检测时机 | 解决难度 | 推荐程度 |
构造器注入 | ❌ 不支持 | 实例化时 | 困难 | ⭐⭐⭐⭐⭐ |
Setter注入 | ✅ 支持 | 属性注入时 | 简单 | ⭐⭐⭐ |
字段注入 | ✅ 支持 | 属性注入时 | 简单 | ⭐⭐ |
方法注入 | ✅ 支持 | 方法调用时 | 中等 | ⭐⭐⭐⭐ |
3.2 构造器注入循环依赖示例
@Service public class UserService { private final OrderService orderService; // 构造器注入 - 会导致循环依赖异常 public UserService(OrderService orderService) { this.orderService = orderService; } public void processUser(Long userId) { // 业务逻辑 orderService.getOrdersByUser(userId); } } @Service public class OrderService { private final UserService userService; // 构造器注入 - 形成循环依赖 public OrderService(UserService userService) { this.userService = userService; } public List<Order> getOrdersByUser(Long userId) { // 业务逻辑 userService.processUser(userId); return Collections.emptyList(); } }
上述代码在Spring Boot 2.6+版本中会抛出循环依赖异常,因为构造器注入无法通过三级缓存机制解决。
3.3 Setter注入解决方案
@Service public class UserService { private OrderService orderService; // 使用Setter注入避免循环依赖 @Autowired public void setOrderService(OrderService orderService) { this.orderService = orderService; } public void processUser(Long userId) { if (orderService != null) { orderService.getOrdersByUser(userId); } } } @Service public class OrderService { private UserService userService; @Autowired public void setUserService(UserService userService) { this.userService = userService; } public List<Order> getOrdersByUser(Long userId) { if (userService != null) { // 注意:这里可能导致无限递归 // userService.processUser(userId); } return Collections.emptyList(); } }
这种方式可以解决循环依赖问题,但需要注意空指针检查和潜在的无限递归风险。
4. 懒加载配置的陷阱
4.1 懒加载与循环依赖的关系
图3:懒加载与循环依赖检测时序图 - 展示不同配置下的行为差异
4.2 懒加载配置示例
@Configuration @EnableConfigurationProperties public class LazyLoadingConfig { /** * 全局懒加载配置 * 注意:这可能会掩盖循环依赖问题 */ @Bean @Lazy public UserService userService() { return new UserService(); } /** * 选择性懒加载 * 推荐:只对特定Bean使用懒加载 */ @Bean @Lazy @ConditionalOnProperty(name = "app.lazy-loading.enabled", havingValue = "true") public ExpensiveService expensiveService() { return new ExpensiveService(); } } // 在Bean级别使用懒加载 @Service @Lazy // 这个注解会延迟Bean的创建 public class ReportService { @Autowired @Lazy // 延迟注入依赖 private DataService dataService; public void generateReport() { // 只有在实际调用时才会创建dataService dataService.fetchData(); } }
懒加载虽然可以避免启动时的循环依赖检测,但可能会将问题延迟到运行时,增加排查难度。
5. 实战排查方法
5.1 循环依赖检测工具
@Component public class CircularDependencyDetector implements BeanPostProcessor { private static final Logger logger = LoggerFactory.getLogger(CircularDependencyDetector.class); private final Set<String> beansInCreation = ConcurrentHashMap.newKeySet(); @Override public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { beansInCreation.add(beanName); logger.debug("开始创建Bean: {}", beanName); return bean; } @Override public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { beansInCreation.remove(beanName); logger.debug("完成创建Bean: {}", beanName); // 检测潜在的循环依赖 if (hasCircularDependency(bean)) { logger.warn("检测到潜在循环依赖: {}", beanName); } return bean; } /** * 检测Bean是否存在循环依赖 */ private boolean hasCircularDependency(Object bean) { Class<?> beanClass = bean.getClass(); Field[] fields = beanClass.getDeclaredFields(); for (Field field : fields) { if (field.isAnnotationPresent(Autowired.class)) { String fieldTypeName = field.getType().getSimpleName(); if (beansInCreation.contains(fieldTypeName.toLowerCase())) { return true; } } } return false; } }
这个检测器可以帮助我们在开发阶段及早发现潜在的循环依赖问题。
5.2 启动日志分析
@Configuration @Slf4j public class BeanCreationMonitor { @EventListener public void handleContextRefresh(ContextRefreshedEvent event) { ApplicationContext context = event.getApplicationContext(); String[] beanNames = context.getBeanDefinitionNames(); log.info("=== Bean创建统计 ==="); log.info("总Bean数量: {}", beanNames.length); // 分析Bean依赖关系 analyzeBeanDependencies(context, beanNames); } private void analyzeBeanDependencies(ApplicationContext context, String[] beanNames) { Map<String, Set<String>> dependencyGraph = new HashMap<>(); for (String beanName : beanNames) { try { BeanDefinition beanDefinition = ((ConfigurableApplicationContext) context) .getBeanFactory().getBeanDefinition(beanName); if (beanDefinition instanceof AbstractBeanDefinition) { String[] dependsOn = ((AbstractBeanDefinition) beanDefinition).getDependsOn(); if (dependsOn != null) { dependencyGraph.put(beanName, Set.of(dependsOn)); } } } catch (Exception e) { log.debug("无法获取Bean定义: {}", beanName); } } // 检测循环依赖 detectCircularDependencies(dependencyGraph); } private void detectCircularDependencies(Map<String, Set<String>> graph) { Set<String> visited = new HashSet<>(); Set<String> recursionStack = new HashSet<>(); for (String node : graph.keySet()) { if (hasCycle(graph, node, visited, recursionStack)) { log.warn("检测到循环依赖路径包含: {}", node); } } } private boolean hasCycle(Map<String, Set<String>> graph, String node, Set<String> visited, Set<String> recursionStack) { if (recursionStack.contains(node)) { return true; } if (visited.contains(node)) { return fawww.devze.comlse; } visited.add(node); recursionStack.add(node); Set<String> neighbors = graph.get(node); if (neighbors != null) { for (String neighbor : neighbors) { if (hasCycle(graph, neighbor, visited, recursionStack)) { return true; } } } recursionStack.remove(node); return false; } }
这个监控器可以在应用启动时分析Bean的依赖关系,帮助识别潜在的循环依赖问题。
6. 解决方案最佳实践
6.1 架构重构方案
图4:循环依赖解决方案占比图 - 展示不同方案的使用频率
6.2 事件驱动解耦
// 定义业务事件 @Data @AllArgsConstructor public class UserCreatedEvent { private Long userId; private String username; private LocalDateTime createdAt; } @Service public class UserService { @Autowired private ApplicationEventPublisher eventPublisher; public void createUser(CreateUserRequest request) { // 创建用户逻辑 User user = new User(); user.setUsername(request.getUsername()); user.setCreatedAt(LocalDateTime.now()); // 保存用户 // userRepository.save(user); // 发布事件而不是直接调用其他服务 eventPublisher.publishEvent(new UserCreatedEvent( user.getId(), user.getUsername(), user.getCreatedAt() )); } } @Service public class OrderService { // 通过事件监听处理业务逻辑,避免直接依赖javascriptUserService @EventListener @Async public void handleUserCreated(UserCreatedEvent event) { // 为新用户创建默认订单配置 createDefaultOrderSettings(event.getUserId()); } private void createDefaultOrderSettings(Long userId) { // 创建默认订单设置的逻辑 log.info("为用户 {} 创建默认订单设置", userId); } }
事件驱动模式可以有效解耦服务间的直接依赖关系,是解决循环依赖的优雅方案。
6.3 接口抽象解耦
// 定义接口抽象 public interface UserOperations { void processUser(Long userId); UserInfo getUserInfo(Long userId); } public interface OrderOperations { List<Order> getOrdersByUser(Long userId); void createOrder(CreateOrderRequest request); } // 实现类只依赖接口 @Service public class UserServiceImpl implements UserOperations { @Autowired private OrderOperations orderOperations; // 依赖接口而非具体实现 @Override public void processUser(Long userId) { // 处理用户逻辑 List<Order> orders = orderOperations.getOrdersByUser(userId); // 进一步处理 } @Override public UserInfo getUserInfo(Long userId) { return new UserInfo(userId, "用户" + userId); } } @Service public class OrderServiceImpl implements OrderOperations { @Autowired private UserOperations userOperations; // 依赖接口而非具体实现 @Override public List<Order> getOrdersByUser(Long userId) { // 获取用户信息 UserInfo userInfo = userOperations.getUserInfo(userId); // 返回订单列表 return Collections.emptyList(); } @Override public void createOrder(CreateOrderRequest request) { // 创建订单逻辑 } }
通过接口抽象,我们可以在保持业务逻辑完整性的同时,降低类之间的耦合度。
7. 性能影响分析
7.1 不同解决方案的性能对比
图5:循环依赖解决方案性能对比图 - 展示不同方案的性能表现
7.2 内存使用分析
@Component @Slf4j public class MemoryUsageAnalyzer { private final MemoryMXBean memoryBean = ManagementFactory.getMemoryMXBean(); @EventListener public void analyzeMemoryUsage(ContextRefreshedEvent event) { MemoryUsage heapUsage = memoryBean.getHeapMemoryUsage(); MemoryUsage nonHeapUsage = memoryBean.getNonHeapMemoryUsage(); log.info("=== 内存使用分析 ==="); log.info("堆内存使用: {} MB / {} MB", heapUsage.getUsed() / 1024 / 1024, heapUsage.getMax() / 1024 / 1024); log.info("非堆内存使用: {} MB / {} MB", nonHeapUsage.getUsed() / 1024 / 1024, nonHeapUsage.getMax() / 1024 / 1024); // 分析Bean实例占用的内存 analyzeBeanMemoryUsage(event.getApplicationContext()); } private void analyzeBeanMemoryUsage(ApplicationContext context) { String[] beanNames = context.getBeanDefinitionNames(); Map<String, Long> beanSizes = new HashMap<>(); for (String beanName : beanNames) { try { Object bean = context.getBean(beanName); long size = calculateObjectSize(bean); beanSizes.put(beanName, size); } catch (Exception e) { log.debug("无法计算Bean大小: {}", beanName); } } // 输出占用内存最多的前10个Bean beanSizes.entrySet().stream() .sorted(Map.Entry.<String, Long>comparingByValue().reversed()) .limit(10) .forEach(entry -> log.info("Bean: {} - 大小: {} bytes", entry.getKey(), entry.getValue())); } private long calculateObjectSize(Object obj) { // 简化的对象大小计算 // 实际项目中可以使用更精确的工具如JOL return obj.toString().length() * 2; // 粗略估算 } }
这个分析器可以帮助我们了解不同循环依赖解决方案对内存使用的影响。
8. 监控与告警
8.1 循环依赖监控指标
@Component public class CircularDependencyMetrics { private final MeterRegistry meterRegistry; private final Counter circularDependencyCounter; private final Timer beanCreationTimer; public CircularDependencyMetrics(MeterRegistry meterRegistry) { this.meterRegistry = meterRegistry; this.circularDependencyCounter = Counter.builder("circular.dependency.detected") .description("检测到的循环依赖数量") .register(meterRegistry); this.beanCreationTimer = Timer.builder("bean.creation.time") .description("Bean创建耗时") .register(meterRegistry); } public void recordCircularDependency(String beanName) { circularDependencyCounter.increment( Tags.of("bean.name", beanName) ); } public void recordBeanCreationTime(String beanName, Duration duration) { beanCreationTimer.record(duration, Tags.of("bean.name", beanName) ); } @EventListener public void handleApplicationReady(ApplicationReadyEvent event) { // 应用启动完成后,输出循环依赖统计 double totalCircularDependencies = circularDependencyCounter.count(); if (totalCircularDependencies > 0) { log.warn("应用启动过程中检测到 {} 个循环依赖", totalCircularDependencies); } } }
通过监控指标,我们可以及时发现和处理循环依赖问题。
8.2 告警配置
# Prometheus告警规则 groups: - name: spring-boot-circular-dependency rules: - alert: CircularDependencyDetected expr: increase(circular_dependency_detected_total[5m]) > 0 for: 0m labels: severity: warning annotations: summary: "检测到循环依赖" description: "应用 {{ $labels.application }} 在过去5分钟内检测到循环依赖" - alert: BeanCreationTimeHigh expr: histogram_quantile(0.95, rate(bean_creation_time_seconds_bucket[5m])) > 10 for: 2m labels: severity: critical annotations: summary: "Bean创建耗时过长" description: "Bean创建95%分位数耗时超过10秒"
最佳实践提醒
循环依赖不仅仅是技术问题,更是架构设计问题。优秀的架构设计应该避免循环依赖的产生,而不是依赖框架的机制来解决。在设计阶段就应该考虑模块间的依赖关系,遵循单一职责原则和依赖倒置原则。
9. 故障案例复盘
9.1 生产环境故障分析
在一次生产环境部署中,我们遇到了一个典型的循环依赖问题。应用在本地和测试环境运行正常,但在生产环境启动时却抛出了循环依赖异常。
图6:循环依赖问题影响矩阵 - 展示不同场景下的影响程度
9.2 根因分析
经过深入分析,我们发现问题的根源在于:
- 环境配置差异:生产环境使用了Spring Boot 2.编程6版本,而本地环境使用的是2.4版本
- 懒加载配置不一致:生产环境禁用了懒加载,导致启动时立即检测循环依赖
- Bean创建顺序变化:不同环境下Bean的创建顺序可能不同
// 问题代码示例 @Service public class PaymentService { @Autowired private OrderService orderService; // 构造器注入导致循环依赖 public void processPayment(Long orderId) { Order order = orderService.getOrder(orderId); // 支付处理逻辑 } } @Service public class OrderService { @Autowired private PaymentService paymentService; // 形成循环依赖 public Order getOrder(Long orderId) { // 获取订单逻辑 return new Order(); } }
9.3 解决方案实施
我们采用了多层次的解决方案:
// 解决方案1:重构为事件驱动架构 @Service public class PaymentService { @Autowired private ApplicationEventPublisher eventPublisher; public void processPayment(PaymentRequest request) { // 处理支付逻辑 Payment payment = createPayment(request); // 发布支付完成事件 eventPublisher.publishEvent(new PaymentCompletedEvent( payment.getId(), payment.getOrderId(), payment.getAmount() )); } private Payment createPayment(PaymentRequest request) { // 创建支付记录 return new Payment(); } } @Service public class OrderService { // 通过事件监听处理订单状态更新 @EventListener @Async public void handlePaymentCompleted(PaymentCompletedEvent event) { updateOrderStatus(event.getOrderId(), OrderStatus.PAID); } public Order getOrder(Long orderId) { // 获取订单逻辑 return new Order(); } private void updateOrderStatus(Long orderId, OrderStatus status) { // 更新订单状态 log.info("订单 {} 状态更新为: {}", orderId, status); } }
10. 最佳实践总结
10.1 设计原则
- 依赖倒置原则:依赖抽象而非具体实现
- 单一职责原则:每个类只负责一个职责
- 开闭原则:对扩展开放,对修改关闭
10.2 代码规范
// 推荐:使用构造器注入 + 接口依赖 @Service public class RecommendedUserService { private final UserRepository userRepository; private final NotificationService notificationService; // 构造器注入,依赖明确 public RecommendedUserService(UserRepository userRepository, NotificationService notificationService) { this.userRepository = userRepository; this.notificationService = notificationService; } public void createUser(CreateUserRequest request) { User user = new User(request.getUsername(), request.getEmail()); userRepository.save(user); // 异步发送通知,避免强依赖 notificationService.sendwelcomeNotification(user.getId()); } } // 不推荐:字段注入 + 循环依赖 @Service public class NotRecommendedUserService { @Autowired private OrderService orderService; // 可能导致循环依赖 @Autowired private PaymentService paymentService; // 可能导致循环依赖 // 业务逻辑与依赖管理混合 public void createUser(CreateUserRequest request) { // 复杂的业务逻辑 } }
10.3 配置管理
# 推荐的配置方式 spring: main: # 明确禁用循环依赖,强制良好的架构设计 allow-circular-references: false # 根据需要配置懒加载 lazy-initialization: false profiles: active: ${SPRING_PROFILES_ACTIVE:dev} # 环境特定配置 --- spring: config: activate: on-profile: prod main: # 生产环境严格模式 allow-circular-references: false lazy-initialization: false --- spring: config: activate: on-profile: dev main: # 开发环境可以适当放宽 allow-circular-references: true lazy-initialization: true
11. 工具与资源
11.1 开发工具推荐
工具名称 | 功能描述 | 使用场景 | 推荐指数 |
Spring Boot Actuator | 应用监控和管理 | 生产环境监控 | ⭐⭐⭐⭐⭐ |
JProfiler | Java性能分析 | 性能调优 | ⭐⭐⭐⭐ |
VisualVM | JVM监控工具 | 内存分析 | ⭐⭐⭐⭐ |
SonarQube | 代码质量检测 | 代码审查 | ⭐⭐⭐⭐⭐ |
Micrometer | 指标收集 | 监控告警 | ⭐⭐⭐⭐ |
11.2 检测脚本
#!/bin/bash # 循环依赖检测脚本 echo "=== Spring Boot 循环依赖检测 ===" # 检查Spring Boot版本 echo "检查Spring Boot版本..." grep -r "spring-boot-starter" pom.XML | head -5 # 检查循环依赖配置 echo "检查循环依赖配置..." find . -name "*.yml" -o -name "*.yaml" -o -name "*.properties" | \ xargs grep -l "allow-circular-references\|lazy-initialization" # 扫描可能的循环依赖 echo "扫描潜在循环依赖..." find . -name "*.java" -exec grep -l "@Autowired\|@Inject" {} \; | \ while read file; do echo "分析文件: $file" # 简单的循环依赖检测逻辑 done echo "检测完成!"
总结
通过这次深入的循环依赖问题排查和解决过程,我深刻体会到了架构设计的重要性。循环依赖不仅仅是一个技术问题,更是架构设计和代码质量的体现。在我多年的Spring Boot开发经验javascript中,我发现最好的解决方案往往不是依赖框架的机制来"修复"循环依赖,而是从根本上避免循环依赖的产生。
Spring Boot 2.6版本默认禁用循环依赖的决定是明智的,它迫使开发者重新审视自己的架构设计,采用更加清晰和可维护的依赖关系。虽然这可能会在短期内增加一些重构工作,但从长远来看,这将大大提高代码的质量和可维护性。
在实际项目中,我建议采用以下策略来处理循环依赖问题:首先,在设计阶段就要考虑模块间的依赖关系,遵循SOLID原则;其次,优先使用事件驱动架构来解耦服务间的直接依赖;最后,建立完善的监控和告警机制,及时发现和处理潜在的循环依赖问题。
记住,优秀的架构设计应该像一首和谐的交响乐,每个组件都有自己的职责,相互协作而不相互依赖。只有这样,我们才能构建出真正健壮、可扩展的企业级应用。在技术的道路上,让我们始终保持对代码质量的追求,在每一次重构中都能听到架构优化的美妙音符。
参考链接
- Spring Framework官方文档 - 循环依赖处理
- Spring Boot 2.6发布说明 - 循环依赖变更
- Martin Fowler - 依赖注入模式
- Baeldung - Spring循环依赖指南
- Spring Boot Actuator监控指南
到此这篇关于Spring Boot 启动失败:循环依赖排查到懒加载配置的过程解析的文章就介绍到这了,更多相关Spring Boot 循环依赖内容请搜索编程客栈(www.devze.com)以前的文章或继续浏览下面的相关文章希望大家以后多多支持编程客栈(www.devze.com)!
精彩评论