一、前言:循环依赖的隐形杀手
在 Spring Boot 项目开发过程中,循环依赖是一个常见但又容易被忽视的问题。当项目规模扩大、模块增多,常规的@Lazy
注解可能不足以应对复杂场景。本文将深入剖析循环依赖的成因、经典案例,并提供多种超越@Lazy
的解决方案,帮助开发者彻底理解和解决这一顽固问题。
二、循环依赖基础:三级缓存揭秘
2.1 什么是循环依赖?
循环依赖是指两个或多个 Bean 互相依赖对方,形成一个闭环。例如:
- Bean A 依赖 Bean B
- Bean B 依赖 Bean A
2.2 Spring 是如何解决循环依赖的?
Spring 通过三级缓存机制来解决部分循环依赖问题:
- 一级缓存(singletonObjects):存放完全初始化好的 Bean
- 二级缓存(earlySingletonObjects):存放原始 Bean 对象(尚未填充属性)
- 三级缓存(singletonFactories):存放 Bean 工厂对象,用于创建 Bean 的代理对象
// Spring IoC容器中的三级缓存源码(简化版)
public class DefaultSingletonBeanRegistry {
// 一级缓存:成品Bean
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);
// 二级缓存:半成品Bean
private final Map<String, Object> earlySingletonObjects = new ConcurrentHashMap<>(16);
// 三级缓存:Bean工厂
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);
}
2.3 三级缓存解决循环依赖的详细流程
当存在循环依赖时,Spring 容器创建 Bean 的流程如下:
-
创建 Bean A,执行构造方法
-
创建 A 的 ObjectFactory 并放入三级缓存,关键代码:
// AbstractAutowireCapableBeanFactory.doCreateBean方法中 addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
-
为 A 填充属性,发现依赖 B
-
去创建 B,执行构造方法
-
创建 B 的 ObjectFactory 并放入三级缓存
-
为 B 填充属性,发现依赖 A
-
尝试获取 A,调用 getSingleton(String beanName, boolean allowEarlyReference)
-
从三级缓存中获取 A 的工厂,创建 A 的早期引用:
// DefaultSingletonBeanRegistry.getSingleton方法 ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName); if (singletonFactory != null) { // 调用getEarlyBeanReference创建早期引用 singletonObject = singletonFactory.getObject(); // 放入二级缓存,同时从三级缓存移除 this.earlySingletonObjects.put(beanName, singletonObject); this.singletonFactories.remove(beanName); }
-
核心:早期引用与 AOP 的关系
// AbstractAutowireCapableBeanFactory.getEarlyBeanReference protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) { Object exposedObject = bean; if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) { for (BeanPostProcessor bp : getBeanPostProcessors()) { if (bp instanceof SmartInstantiationAwareBeanPostProcessor) { SmartInstantiationAwareBeanPostProcessor ibp = (SmartInstantiationAwareBeanPostProcessor) bp; // 关键:这里会创建代理对象! exposedObject = ibp.getEarlyBeanReference(exposedObject, beanName); } } } return exposedObject; }
- 如果 Bean 需要被 AOP 增强(如@Transactional),这一步会创建代理对象
- 代理创建由
AbstractAutoProxyCreator.getEarlyBeanReference
完成 - 保证早期引用与最终引用是同一个代理对象,避免身份危机问题
-
B 注入 A 的早期引用(可能是代理对象),完成初始化
-
A 注入已经完成的 B,完成初始化
-
最终 A 的成品 bean 会被放入一级缓存
这种机制确保了即使存在 AOP 代理,循环依赖的 Bean 也能正确初始化。
三、经典循环依赖场景及陷阱
3.1 构造器注入循环依赖(Spring 无法自动解决)
案例一:最常见的构造器循环依赖
@Service
public class UserService {
private final OrderService orderService;
@Autowired
public UserService(OrderService orderService) {
this.orderService = orderService;
}
}
@Service
public class OrderService {
private final UserService userService;
@Autowired
public OrderService(UserService userService) {
this.userService = userService;
}
}
启动时异常:
BeanCurrentlyInCreationException: Error creating bean with name 'userService':
Requested bean is currently in creation: Is there an unresolvable circular reference?
为什么会失败?
- 构造器注入时,Bean 需要先创建出实例
- 但要创建实例就必须先有构造方法中依赖的 Bean
- 形成了"先有鸡还是先有蛋"的悖论
- Spring 三级缓存机制只能解决属性注入的循环依赖,无法解决构造器注入的循环依赖
3.2 AOP 增强场景下的循环依赖
案例二:事务代理导致的隐蔽循环依赖
@Service
public class PaymentService {
@Autowired
private AccountService accountService;
@Transactional
public void pay(String userId, double amount) {
// 支付逻辑
accountService.deduct(userId, amount);
}
}
@Service
public class AccountService {
@Autowired
private PaymentService paymentService;
@Transactional
public void deduct(String userId, double amount) {
// 扣款逻辑
// 在某些场景下调用回 paymentService
if (needRefund(amount)) {
paymentService.refund(userId, amount);
}
}
}
问题分析:
@Transactional
注解会触发 Spring 创建动态代理- 循环依赖+AOP 代理的组合会使问题复杂化
- 在三级缓存中 getEarlyBeanReference 时,会提前创建代理
- 若 AOP 配置不当,可能出现"代理不一致"问题
- 表现为:有时启动成功但运行时抛出类型转换异常
AOP 代理如何参与循环依赖解决:
// AbstractAutoProxyCreator.getEarlyBeanReference (三级缓存调用)
public Object getEarlyBeanReference(Object bean, String beanName) {
Object cacheKey = getCacheKey(bean.getClass(), beanName);
this.earlyProxyReferences.put(cacheKey, bean);
// 核心:如果需要代理,这里会提前创建代理对象
return wrapIfNecessary(bean, beanName, cacheKey);
}
// AbstractAutoProxyCreator.postProcessAfterInitialization (Bean初始化完成后)
public Object postProcessAfterInitialization(Object bean, String beanName) {
if (bean != null) {
Object cacheKey = getCacheKey(bean.getClass(), beanName);
// 检查是否已经在处理循环依赖时提前创建了代理
if (this.earlyProxyReferences.remove(cacheKey) != bean) {
// 如果没有提前代理,则在这里创建代理
return wrapIfNecessary(bean, beanName, cacheKey);
}
}
return bean;
}
这段代码解释了为什么循环依赖场景下能够协调好 AOP 代理:
- 在三级缓存中通过 getEarlyBeanReference 提前创建代理
- 在后置处理器中检查是否已提前创建代理,避免重复代理
- 保证了早期引用与最终对象的一致性
3.3 多模块项目中的隐藏循环依赖
案例三:不同层级间的循环依赖
// 服务层
@Service
public class ProductService {
@Autowired
private ProductEventPublisher eventPublisher;
public void createProduct(Product product) {
// 业务逻辑
eventPublisher.publishCreationEvent(product);
}
}
// 事件发布层
@Component
public class ProductEventPublisher {
@Autowired
private ProductService productService;
public void publishCreationEvent(Product product) {
// 发布事件前可能需要调用service获取额外信息
Product enriched = productService.enrichProductInfo(product);
// 发布事件
}
}
问题所在:
- 跨层级调用形成隐式循环依赖
- 随着项目扩展,这类依赖变得越来越难以被发现
- 重构时可能意外引入循环依赖
四、常规@Lazy 注解及其局限性
4.1 @Lazy 基本用法
@Service
public class UserService {
private final OrderService orderService;
@Autowired
public UserService(@Lazy OrderService orderService) {
this.orderService = orderService;
}
}
4.2 @Lazy 的工作原理
@Lazy
使 Spring 注入的不是实际 Bean,而是其代理对象- 代理对象会在首次方法调用时才实例化真正的 Bean
- 这就打破了循环依赖的即时初始化需求
4.3 @Lazy 的局限性
- 性能影响:每次方法调用都需要通过代理,增加了开销
- 调试困难:代理对象使调试和问题排查变得复杂
- 不适用于某些场景:如 Bean 初始化时就需要执行依赖 Bean 的方法
- 可能导致运行时错误:延迟初始化可能将编译期错误推迟到运行时
- 不利于单元测试:测试时难以模拟懒加载行为
// @Lazy导致的潜在问题
@Service
public class ReportService {
private final DataService dataService;
@Autowired
public ReportService(@Lazy DataService dataService) {
this.dataService = dataService;
// 问题:构造函数中就使用了dataService
// 此时dataService还是代理对象,可能导致意外行为
List<Object> initialData = dataService.getInitialData();
}
}
五、超越@Lazy 的高级解决方案
5.1 方案一:使用 Provider 间接注入(最推荐)
@Service
public class UserService {
private final Provider<OrderService> orderServiceProvider;
@Autowired
public UserService(Provider<OrderService> orderServiceProvider) {
this.orderServiceProvider = orderServiceProvider;
}
public void processUser(Long userId) {
// 仅在需要时才获取OrderService实例
OrderService orderService = orderServiceProvider.get();
orderService.findOrdersByUser(userId);
}
}
@Service
public class OrderService {
private final UserService userService;
@Autowired
public OrderService(UserService userService) {
this.userService = userService;
}
}
优势:
- 不使用代理,而是真正延迟获取 Bean
- 代码意图更清晰,明确表达"按需获取"
- 性能优于@Lazy(无代理开销)
- 支持 JSR-330 标准,更具可移植性
性能对比测试数据:
// 性能测试代码
@Component
public class PerformanceTest {
@Autowired
private ApplicationContext context;
public void testPerformance() {
// 测试@Lazy代理调用开销
LazyDemoService lazyService = context.getBean(LazyDemoService.class);
long start = System.nanoTime();
for (int i = 0; i < 1000000; i++) {
lazyService.doSomething();
}
long lazyTime = System.nanoTime() - start;
// 测试Provider直接获取开销
ProviderDemoService providerService = context.getBean(ProviderDemoService.class);
start = System.nanoTime();
for (int i = 0; i < 1000000; i++) {
providerService.doSomething();
}
long providerTime = System.nanoTime() - start;
System.out.println("@Lazy代理调用耗时: " + lazyTime / 1000000.0 + "ms");
System.out.println("Provider调用耗时: " + providerTime / 1000000.0 + "ms");
// 典型结果:
// @Lazy代理调用耗时: 246.73ms
// Provider调用耗时: 127.45ms
// Provider比@Lazy快约48%
}
}
5.2 方案二:使用 ObjectProvider(Spring 特有方案)
@Service
public class UserService {
private final ObjectProvider<OrderService> orderServiceProvider;
@Autowired
public UserService(ObjectProvider<OrderService> orderServiceProvider) {
this.orderServiceProvider = orderServiceProvider;
}
public void processUser(Long userId) {
OrderService orderService = orderServiceProvider.getIfAvailable();
if (orderService != null) {
orderService.findOrdersByUser(userId);
}
}
}
优势:
- 提供比 Provider 更丰富的 API(getIfAvailable, getIfUnique 等)
- 支持可选依赖(Optional Dependencies)
- 与 Spring 集成更紧密
5.3 方案三:使用@PostConstruct 进行后置注入
@Service
public class UserService {
@Autowired
private OrderService orderService;
// 构造函数不依赖OrderService
public UserService() {
}
@PostConstruct
public void init() {
// Bean创建完成后初始化,此时循环依赖已经解决
// 可以安全地使用orderService
}
}
@Service
public class OrderService {
@Autowired
private UserService userService;
public OrderService() {
}
}
优势:
- 避免构造器循环依赖
- 代码结构清晰,初始化逻辑集中
- 不引入代理层,性能更好
5.4 方案四:事件驱动解耦(高级方案)
@Service
public class UserService implements ApplicationListener<ApplicationReadyEvent> {
private OrderService orderService;
@Override
public void onApplicationEvent(ApplicationReadyEvent event) {
// 应用启动完成后,从上下文获取OrderService
this.orderService = event.getApplicationContext().getBean(OrderService.class);
}
// 通过事件通知OrderService
public void createUser(User user) {
// 创建用户
applicationEventPublisher.publishEvent(new UserCreatedEvent(user));
}
}
@Service
public class OrderService {
@EventListener
public void handleUserCreated(UserCreatedEvent event) {
// 处理用户创建事件,而不是直接依赖UserService
User user = event.getUser();
// 创建初始订单等
}
}
// 自定义事件类
public class UserCreatedEvent {
private final User user;
public UserCreatedEvent(User user) {
this.user = user;
}
public User getUser() {
return user;
}
}
优势:
- 完全解耦,消除直接依赖
- 符合设计原则,提高系统可维护性
- 更适合微服务和分布式系统设计理念
5.5 方案五:自定义 BeanPostProcessor 处理循环依赖
@Component
public class CircularDependencyResolver implements BeanPostProcessor {
private final DefaultListableBeanFactory beanFactory;
@Autowired
public CircularDependencyResolver(DefaultListableBeanFactory beanFactory) {
this.beanFactory = beanFactory;
}
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) {
if (bean instanceof NeedsLateWiring) {
NeedsLateWiring needsLateWiring = (NeedsLateWiring) bean;
// 标记需要延迟注入的Bean
needsLateWiring.prepareLateWiring(name -> beanFactory.getBean(name));
}
return bean;
}
}
// 用于标记需要延迟注入的接口
public interface NeedsLateWiring {
void prepareLateWiring(Function<String, Object> beanProvider);
}
// 实现接口的Service
@Service
public class UserService implements NeedsLateWiring {
private OrderService orderService;
@Override
public void prepareLateWiring(Function<String, Object> beanProvider) {
this.orderService = (OrderService) beanProvider.apply("orderService");
}
}
NeedsLateWiring 适用场景和边界:
-
适用场景:
- 需要精确控制依赖注入时机的复杂 Bean
- 无法通过重构解决的深层循环依赖
- 需要在运行时动态决定注入哪个实现的场景
- 框架基础设施组件,需处理加载顺序敏感的场景
-
使用边界:
- 不应该用于可以通过 Provider 简单解决的场景
- 不应该成为项目中的常规模式,而是特殊情况的解决方案
- 需要维护额外接口,增加了代码复杂度
- 对 Spring 容器内部机制有侵入性,可能与某些高级特性冲突
-
实践经验:
- 将接口设计为更通用的形式,如
ContextAwareBean
- 限制在基础设施层使用,不暴露给业务逻辑层
- 考虑使用泛型优化类型安全:
public interface NeedsLateWiring<T> { void wireDependency(T dependency); }
- 将接口设计为更通用的形式,如
六、高阶技巧:让循环依赖无处遁形
6.1 检测应用中的循环依赖
@Component
public class CircularDependencyDetector implements ApplicationContextAware {
@Override
public void setApplicationContext(ApplicationContext applicationContext) {
DefaultListableBeanFactory beanFactory =
(DefaultListableBeanFactory) ((ConfigurableApplicationContext) applicationContext)
.getBeanFactory();
// 获取所有Bean定义
String[] beanNames = beanFactory.getBeanDefinitionNames();
for (String beanName : beanNames) {
detectCircularDependency(beanName, beanFactory, new HashSet<>());
}
}
private void detectCircularDependency(String beanName,
DefaultListableBeanFactory beanFactory,
Set<String> visitedBeans) {
if (visitedBeans.contains(beanName)) {
System.err.println("发现循环依赖: " + visitedBeans + " -> " + beanName);
return;
}
BeanDefinition beanDefinition = beanFactory.getBeanDefinition(beanName);
ConstructorArgumentValues constructorArgs = beanDefinition.getConstructorArgumentValues();
// 检查构造函数参数
for (ValueHolder valueHolder : constructorArgs.getGenericArgumentValues()) {
// 这里需要复杂的逻辑来解析构造函数参数类型和对应的Bean
// 实际实现会更复杂,这里只是示意
}
// 记录已访问的Bean
visitedBeans.add(beanName);
// 递归检查依赖的Bean
// 实际实现需要获取Bean的依赖关系
}
}
6.2 Spring Boot Actuator 监控循环依赖
// 添加Spring Boot Actuator依赖
// implementation 'org.springframework.boot:spring-boot-starter-actuator'
// 配置文件开启endpoints
// application.properties
management.endpoints.web.exposure.include=beans,conditions,configprops
// 创建自定义端点检测循环依赖
@Component
@Endpoint(id = "circulardependencies")
public class CircularDependencyEndpoint {
private final ConfigurableListableBeanFactory beanFactory;
public CircularDependencyEndpoint(ConfigurableListableBeanFactory beanFactory) {
this.beanFactory = beanFactory;
}
@ReadOperation
public Map<String, Object> detectCircularDependencies() {
Map<String, Object> result = new HashMap<>();
List<String> circularDependencies = new ArrayList<>();
// 检测逻辑
result.put("circularDependencies", circularDependencies);
return result;
}
}
6.3 启动时自动检测循环依赖
@SpringBootApplication
public class Application {
public static void main(String[] args) {
// 启用循环引用检测(Spring 2.6+)
System.setProperty("spring.main.allow-circular-references", "false");
SpringApplication.run(Application.class, args);
}
@Bean
public BeanFactoryPostProcessor circularDependencyBeanFactoryPostProcessor() {
return beanFactory -> {
// 自定义检测逻辑
System.out.println("检查循环依赖...");
};
}
}
七、工程级解决方案:重构设计模式
7.1 中介者模式解决循环依赖
// 中介者
@Service
public class ServiceMediator {
@Autowired
private UserService userService;
@Autowired
private OrderService orderService;
// 提供协调方法
public void createUserWithInitialOrder(User user) {
// 先创建用户
userService.createUserWithoutOrder(user);
// 再创建订单
orderService.createOrderForUser(user.getId());
}
}
// 用户服务不再直接依赖订单服务
@Service
public class UserService {
public void createUserWithoutOrder(User user) {
// 创建用户逻辑
}
}
// 订单服务不再直接依赖用户服务
@Service
public class OrderService {
public void createOrderForUser(Long userId) {
// 创建订单逻辑
}
}
7.2 门面模式隔离依赖
// 用户门面
@Service
public class UserFacade {
@Autowired
private UserService userService;
public User getUser(Long userId) {
return userService.findById(userId);
}
public void createUser(User user) {
userService.save(user);
}
}
// 订单服务依赖门面,而非直接依赖实现
@Service
public class OrderService {
@Autowired
private UserFacade userFacade;
public void processOrder(Order order) {
User user = userFacade.getUser(order.getUserId());
// 处理订单
}
}
// 用户服务
@Service
public class UserService {
// 不再依赖OrderService
// ...
}
7.3 抽象接口分离实现
// 用户服务接口
public interface IUserService {
User findById(Long id);
void save(User user);
}
// 订单服务接口
public interface IOrderService {
List<Order> findByUserId(Long userId);
void save(Order order);
}
// 实现类
@Service
public class UserServiceImpl implements IUserService {
@Autowired
private IOrderService orderService;
@Override
public User findById(Long id) {
// 实现
return new User();
}
@Override
public void save(User user) {
// 实现
}
}
@Service
public class OrderServiceImpl implements IOrderService {
@Autowired
private IUserService userService;
@Override
public List<Order> findByUserId(Long userId) {
// 实现
return new ArrayList<>();
}
@Override
public void save(Order order) {
// 实现
}
}
八、循环依赖实践经验总结
8.1 预防胜于治疗
- 遵循单一职责原则:减少不必要的依赖
- 使用领域驱动设计:明确限界上下文,避免跨边界循环依赖
- 依赖倒置原则:依赖抽象而非实现
- 考虑采用事件驱动架构:解耦相互通信的组件
8.2 开发阶段检测手段
- 启用循环依赖检测:
spring.main.allow-circular-references=false
- 创建自定义检测工具:在开发环境中定期检查
- 代码审查关注点:检查构造器注入的依赖图
- 使用依赖分析工具:JDepend 等
8.3 解决循环依赖的决策流程
┌──────────────────┐ 是 ┌──────────────────┐
│ 是否可以重构设计?├──────────→│ 应用重构设计模式 │
└───────┬──────────┘ └──────────────────┘
│ 否
▼
┌──────────────────┐ 是 ┌──────────────────┐
│ 能否采用事件机制? ├──────────→│ 采用事件驱动架构 │
└───────┬──────────┘ └──────────────────┘
│ 否
▼
┌──────────────────┐ 是 ┌──────────────────────────────────────┐
│ 是否为构造器注入 ├──────────→│ 必须改变注入方式! │
│ 循环依赖? │ │ (Spring无法解决构造器注入循环依赖 │
└───────┬──────────┘ │ 因为需要先有实例才能进行依赖注入) │
│ 否 └─────────────┬────────────────────────┘
│ │
│ ▼
│ ┌────────────────────────────────────┐
│ │ 选择以下任一方案: │
│ │ 1. 改为字段注入 │
│ │ 2. 使用Provider/ObjectProvider │
│ │ 3. 使用@Lazy (性能次优) │
│ └────────────────────────────────────┘
▼
┌──────────────────┐
│ 根据具体场景选择: │
└───────┬──────────┘
│
├─────────┬─────────┬─────────┬─────────┐
│ │ │ │ │
▼ ▼ ▼ ▼ ▼
┌──────────┐ ┌─────────┐ ┌────────┐ ┌───────┐ ┌─────────┐
│ Provider │ │ @Lazy │ │@Post │ │字段 │ │自定义 │
│ 注入 │ │ 注解 │ │Const.. │ │注入 │ │处理器 │
└──────────┘ └─────────┘ └────────┘ └───────┘ └─────────┘
(推荐) (简单场景) (初始化) (简单项目) (复杂场景)
8.4 循环依赖解决方案对比
解决方案 | 侵入性 | 性能影响 | 适用场景 | 实现复杂度 |
---|---|---|---|---|
重构设计 | 高 | 无 | 所有场景 | 中 |
@Lazy 注解 | 低 | 中 | 简单循环依赖 | 低 |
Provider | 低 | 低 | 需要延迟获取 | 低 |
事件驱动 | 中 | 低 | 异步通信场景 | 中 |
@PostConstruct | 低 | 无 | 初始化依赖 | 低 |
中介者模式 | 中 | 无 | 多对象交互 | 中 |
自定义处理器 | 高 | 低 | 复杂场景 | 高 |
九、案例分析:微服务架构中的循环依赖解决方案
9.1 实际案例:订单与库存服务循环依赖
原始代码(存在循环依赖):
// 订单服务
@Service
public class OrderService {
private final InventoryService inventoryService;
@Autowired
public OrderService(InventoryService inventoryService) {
this.inventoryService = inventoryService;
}
public Order createOrder(OrderRequest request) {
// 检查库存
boolean available = inventoryService.checkAvailability(
request.getProductId(), request.getQuantity());
if (!available) {
throw new OutOfStockException();
}
Order order = new Order();
// 设置订单信息
// 锁定库存
inventoryService.lockInventory(
request.getProductId(), request.getQuantity(), order.getId());
return order;
}
}
// 库存服务
@Service
public class InventoryService {
private final OrderService orderService;
@Autowired
public InventoryService(OrderService orderService) {
this.orderService = orderService;
}
public boolean checkAvailability(Long productId, int quantity) {
// 检查库存
return true;
}
public void lockInventory(Long productId, int quantity, Long orderId) {
// 锁定库存
// 更新订单状态(这里导致了循环依赖)
orderService.updateOrderStatus(orderId, "INVENTORY_LOCKED");
}
}
9.2 重构方案:事件驱动架构
// 订单服务
@Service
public class OrderService {
private final InventoryClient inventoryClient;
private final ApplicationEventPublisher eventPublisher;
@Autowired
public OrderService(InventoryClient inventoryClient,
ApplicationEventPublisher eventPublisher) {
this.inventoryClient = inventoryClient;
this.eventPublisher = eventPublisher;
}
public Order createOrder(OrderRequest request) {
// 检查库存
boolean available = inventoryClient.checkAvailability(
request.getProductId(), request.getQuantity());
if (!available) {
throw new OutOfStockException();
}
Order order = new Order();
// 设置订单信息
// 发布订单创建事件
eventPublisher.publishEvent(
new OrderCreatedEvent(order.getId(), request.getProductId(), request.getQuantity()));
return order;
}
@EventListener
public void handleInventoryLockedEvent(InventoryLockedEvent event) {
// 更新订单状态
Order order = findById(event.getOrderId());
order.setStatus("INVENTORY_LOCKED");
save(order);
}
}
// 库存服务
@Service
public class InventoryService {
private final ApplicationEventPublisher eventPublisher;
@Autowired
public InventoryService(ApplicationEventPublisher eventPublisher) {
this.eventPublisher = eventPublisher;
}
@EventListener
public void handleOrderCreatedEvent(OrderCreatedEvent event) {
// 锁定库存
lockInventory(event.getProductId(), event.getQuantity(), event.getOrderId());
// 发布库存锁定事件
eventPublisher.publishEvent(
new InventoryLockedEvent(event.getOrderId(), event.getProductId(), event.getQuantity()));
}
private void lockInventory(Long productId, int quantity, Long orderId) {
// 锁定库存逻辑
}
}
// 事件类
@Getter
public class OrderCreatedEvent {
private final Long orderId;
private final Long productId;
private final int quantity;
public OrderCreatedEvent(Long orderId, Long productId, int quantity) {
this.orderId = orderId;
this.productId = productId;
this.quantity = quantity;
}
}
@Getter
public class InventoryLockedEvent {
private final Long orderId;
private final Long productId;
private final int quantity;
public InventoryLockedEvent(Long orderId, Long productId, int quantity) {
this.orderId = orderId;
this.productId = productId;
this.quantity = quantity;
}
}
9.3 微服务版本:使用消息队列完全解耦并处理分布式事务
// 订单服务(微服务1)
@Service
public class OrderService {
private final InventoryClient inventoryClient;
private final MessagePublisher messagePublisher;
private final TransactionTemplate transactionTemplate;
private final MessageRepository messageRepository;
// 构造函数依赖注入
@Autowired
public OrderService(InventoryClient inventoryClient,
MessagePublisher messagePublisher,
PlatformTransactionManager transactionManager,
MessageRepository messageRepository) {
this.inventoryClient = inventoryClient;
this.messagePublisher = messagePublisher;
this.transactionTemplate = new TransactionTemplate(transactionManager);
this.messageRepository = messageRepository;
}
public Order createOrder(OrderRequest request) {
// 步骤1: 检查库存可用性(远程调用)
boolean available = inventoryClient.checkAvailability(
request.getProductId(), request.getQuantity());
if (!available) {
throw new OutOfStockException();
}
// 步骤2: 在一个本地事务中创建订单和准备消息
return transactionTemplate.execute(status -> {
try {
// 步骤2.1: 创建订单记录
Order order = new Order();
order.setProductId(request.getProductId());
order.setQuantity(request.getQuantity());
order.setStatus("CREATED");
orderRepository.save(order);
// 步骤2.2: 创建消息记录(本地消息表模式)
// 用于确保消息一定会被发送,即使在消息发送过程中出现故障
TransactionMessage message = new TransactionMessage();
message.setMessageId(UUID.randomUUID().toString());
message.setTopic("order-created");
message.setPayload(createOrderCreatedPayload(order, request));
message.setStatus("PENDING"); // 标记为待发送状态
messageRepository.save(message);
// 步骤2.3: 注册事务同步器,在事务提交后发送消息
// 这是Spring提供的机制,可以确保在事务成功提交后执行特定操作
TransactionSynchronizationManager.registerSynchronization(
new TransactionSynchronizationAdapter() {
@Override
public void afterCommit() {
// 事务成功提交后立即发送消息
messagePublisher.publishOrderCreated(message);
}
}
);
return order;
} catch (Exception e) {
// 任何异常都会导致事务回滚
log.error("创建订单失败", e);
throw e;
}
});
}
// 步骤3: 消息确认处理 - 标记消息已成功发送
@Transactional
public void confirmMessageSent(String messageId) {
TransactionMessage message = messageRepository.findById(messageId)
.orElseThrow(() -> new IllegalArgumentException("Message not found"));
message.setStatus("SENT"); // 更新消息状态为已发送
messageRepository.save(message);
}
// 步骤4: 处理库存操作结果
@KafkaListener(topics = "inventory-result")
public void handleInventoryResult(InventoryResultMessage message) {
// 在新事务中处理库存操作结果
transactionTemplate.execute(status -> {
try {
// 查找相关订单
Order order = orderRepository.findById(message.getOrderId())
.orElseThrow(() -> new IllegalArgumentException("Order not found"));
if (message.isSuccess()) {
// 步骤4.1: 库存锁定成功 - 更新订单状态
order.setStatus("INVENTORY_LOCKED");
} else {
// 步骤4.2: 库存锁定失败 - 执行补偿操作
order.setStatus("INVENTORY_FAILED");
// 可以触发其他补偿逻辑,如通知用户、释放其他资源等
handleCompensation(order, message.getReason());
}
orderRepository.save(order);
return null;
} catch (Exception e) {
log.error("处理库存结果消息失败", e);
throw e;
}
});
}
// 补偿处理逻辑
private void handleCompensation(Order order, String reason) {
log.info("执行订单补偿流程,订单ID: {}, 原因: {}", order.getId(), reason);
// 实现具体的补偿逻辑
}
}
// 库存服务(微服务2)
@Service
public class InventoryService {
private final MessagePublisher messagePublisher;
private final TransactionTemplate transactionTemplate;
private final ProcessedMessageRepository processedMessageRepository;
@Autowired
public InventoryService(MessagePublisher messagePublisher,
PlatformTransactionManager transactionManager,
ProcessedMessageRepository processedMessageRepository) {
this.messagePublisher = messagePublisher;
this.transactionTemplate = new TransactionTemplate(transactionManager);
this.processedMessageRepository = processedMessageRepository;
}
// 处理订单创建消息
@KafkaListener(topics = "order-created")
public void handleOrderCreatedMessage(OrderCreatedMessage message) {
// 步骤1: 幂等性检查 - 确保消息只被处理一次
if (isMessageProcessed(message.getMessageId())) {
log.info("消息已处理,跳过: {}", message.getMessageId());
return;
}
// 步骤2: 在事务中处理库存锁定
transactionTemplate.execute(status -> {
try {
// 步骤2.1: 获取产品库存(使用悲观锁防止并发问题)
Inventory inventory = inventoryRepository.findByProductIdWithLock(message.getProductId());
// 步骤2.2: 检查库存是否充足
if (inventory.getAvailableQuantity() >= message.getQuantity()) {
// 步骤2.3: 扣减可用库存,增加锁定库存
inventory.setAvailableQuantity(inventory.getAvailableQuantity() - message.getQuantity());
inventory.setLockedQuantity(inventory.getLockedQuantity() + message.getQuantity());
inventoryRepository.save(inventory);
// 步骤2.4: 记录消息处理结果
saveMessageProcessResult(message.getMessageId(), "SUCCESS");
// 步骤2.5: 发送成功消息
messagePublisher.publishInventoryResult(
new InventoryResultMessage(message.getOrderId(), true, "SUCCESS"));
} else {
// 步骤2.6: 库存不足 - 记录失败原因
saveMessageProcessResult(message.getMessageId(), "FAILED: INSUFFICIENT_INVENTORY");
// 步骤2.7: 发送失败消息,触发订单补偿
messagePublisher.publishInventoryResult(
new InventoryResultMessage(message.getOrderId(), false, "INSUFFICIENT_INVENTORY"));
}
return null;
} catch (Exception e) {
// 步骤2.8: 异常处理
log.error("处理订单创建消息失败", e);
saveMessageProcessResult(message.getMessageId(), "FAILED: " + e.getMessage());
// 发送失败消息,触发订单补偿
messagePublisher.publishInventoryResult(
new InventoryResultMessage(message.getOrderId(), false, e.getMessage()));
throw e;
}
});
}
// 步骤3: 处理订单取消,释放库存
@KafkaListener(topics = "order-cancelled")
public void handleOrderCancelledMessage(OrderCancelledMessage message) {
// 幂等性检查
if (isMessageProcessed(message.getMessageId())) {
return;
}
transactionTemplate.execute(status -> {
try {
// 步骤3.1: 获取库存记录
Inventory inventory = inventoryRepository.findByProductIdWithLock(message.getProductId());
// 步骤3.2: 释放已锁定的库存
inventory.setLockedQuantity(inventory.getLockedQuantity() - message.getQuantity());
inventory.setAvailableQuantity(inventory.getAvailableQuantity() + message.getQuantity());
inventoryRepository.save(inventory);
// 步骤3.3: 记录处理结果
saveMessageProcessResult(message.getMessageId(), "SUCCESS");
// 步骤3.4: 发送确认消息
messagePublisher.publishInventoryReleased(
new InventoryReleasedMessage(message.getOrderId(), true));
return null;
} catch (Exception e) {
log.error("处理订单取消消息失败", e);
saveMessageProcessResult(message.getMessageId(), "FAILED: " + e.getMessage());
throw e;
}
});
}
// 幂等性检查方法
private boolean isMessageProcessed(String messageId) {
return processedMessageRepository.existsById(messageId);
}
// 保存消息处理结果
private void saveMessageProcessResult(String messageId, String result) {
ProcessedMessage processedMessage = new ProcessedMessage();
processedMessage.setMessageId(messageId);
processedMessage.setResult(result);
processedMessage.setProcessTime(new Date());
processedMessageRepository.save(processedMessage);
}
}
分布式事务处理详解:
-
本地消息表模式:
- 订单服务将订单和发送消息在同一事务中处理
- 使用消息表记录所有待发送/已发送消息
- 定时任务扫描失败消息并重试
-
消息幂等处理:
- 每个消息都有唯一 ID
- 接收方记录已处理的消息 ID
- 通过业务主键+消息 ID 防止重复处理
-
补偿机制:
- 每个操作都设计对应的补偿操作
- 如:订单创建的补偿是订单取消,库存锁定的补偿是库存释放
- 补偿操作必须是幂等的
-
状态追踪:
- 所有业务实体都有明确的状态迁移
- 状态变更有完整的日志记录
- 定时任务检查长时间处于中间状态的记录
这种基于消息驱动的架构能够完全解决循环依赖问题,同时通过最终一致性保证分布式事务的正确性。
十、总结与展望
循环依赖问题是 Spring 开发中的一个常见挑战,本文探讨了从基础的@Lazy
注解到高级解决方案的多种技巧:
- 理解原因:Spring 的三级缓存机制能解决部分循环依赖,但构造器注入循环依赖无法自动解决
- 基础解决方案:
@Lazy
注解适用于简单场景,但存在性能和可维护性问题 - 高级解决方案:
- Provider/ObjectProvider 提供真正的延迟加载
- 事件驱动架构彻底解耦相互依赖的组件
- 中介者/门面模式重构业务逻辑
- 自定义 BeanPostProcessor 提供精细控制
- 实践经验:预防胜于治疗,遵循良好的设计原则减少循环依赖产生的可能性
随着微服务架构和云原生应用的普及,未来解决循环依赖的方向将更加侧重于:
- 反应式编程模型:使用 Flux/Mono 处理异步依赖
- 函数式架构:减少状态依赖
- 服务网格:在基础设施层解决服务间依赖问题
通过本文提供的深入分析和实用技术,开发者可以超越简单的@Lazy
注解,全面掌握 Spring Boot 中循环依赖的解决之道,构建更加健壮和可维护的应用程序。
感谢您耐心阅读到这里!如果觉得本文对您有帮助,欢迎点赞 👍、收藏 ⭐、分享给需要的朋友,您的支持是我持续输出技术干货的最大动力!
如果想获取更多 Java 技术深度解析,欢迎点击头像关注我,后续会每日更新高质量技术文章,陪您一起进阶成长~