Spring Boot Java循環(huán)依賴的解決方案
循環(huán)依賴詳解
一、什么是循環(huán)依賴
1.1 定義
循環(huán)依賴(Circular Dependency)指的是兩個(gè)或多個(gè)模塊之間相互引用,形成閉環(huán)的情況。簡(jiǎn)單來說,就是A依賴B,B又依賴A,或者A→B→C→A這樣的依賴鏈。
1.2 為什么會(huì)產(chǎn)生
循環(huán)依賴通常在以下情況下產(chǎn)生:
- 架構(gòu)設(shè)計(jì)不當(dāng):模塊劃分不夠清晰,職責(zé)邊界模糊
- 功能耦合過緊:相關(guān)功能被錯(cuò)誤地分散在不同模塊中
- 缺乏統(tǒng)一接口:模塊間直接依賴具體實(shí)現(xiàn)而非抽象接口
1.3 危害
循環(huán)依賴會(huì)帶來以下幾個(gè)嚴(yán)重問題:
- 編譯錯(cuò)誤:在某些語(yǔ)言中(如C++),循環(huán)依賴會(huì)導(dǎo)致編譯失敗
- 運(yùn)行時(shí)錯(cuò)誤:模塊初始化順序不確定,可能導(dǎo)致null pointer異常
- 維護(hù)困難:修改一個(gè)模塊可能影響多個(gè)其他模塊
- 測(cè)試復(fù)雜:?jiǎn)卧獪y(cè)試難以獨(dú)立進(jìn)行
二、循環(huán)依賴的場(chǎng)景
2.1 后端開發(fā)中的循環(huán)依賴
以下代碼展示了一個(gè)典型的Spring Boot項(xiàng)目中用戶服務(wù)和訂單服務(wù)的循環(huán)依賴場(chǎng)景:
// UserService.java - 用戶服務(wù)實(shí)現(xiàn)
@Service
public class UserService {
@Autowired
private OrderService orderService; // 直接注入OrderService
public void updateUser(Long userId, UserUpdateDTO userData) {
// 更新用戶信息
System.out.println("更新用戶信息: " + userId);
// 當(dāng)用戶VIP等級(jí)變化時(shí),需要更新相關(guān)訂單的優(yōu)惠策略
if (userData.getVipLevel() != null) {
orderService.updateOrdersForUser(userId, userData.getVipLevel());
}
}
public User getUserById(Long userId) {
// 實(shí)際的用戶查詢邏輯
return new User(userId, "張三", "VIP1");
}
}// OrderService.java - 訂單服務(wù)實(shí)現(xiàn)
@Service
public class OrderService {
@Autowired
private UserService userService; // 直接注入U(xiǎn)serService
public void updateOrdersForUser(Long userId, String vipLevel) {
// 更新訂單邏輯
System.out.println("更新用戶訂單,新的VIP等級(jí): " + vipLevel);
// 訂單總額變化可能影響用戶等級(jí)
User user = userService.getUserById(userId);
BigDecimal totalAmount = calculateTotalOrderAmount(userId);
String newLevel = calculateVipLevel(totalAmount);
if (!newLevel.equals(user.getVipLevel())) {
user.setVipLevel(newLevel);
userService.updateUser(userId, UserUpdateDTO.builder().vipLevel(newLevel).build());
}
}
private BigDecimal calculateTotalOrderAmount(Long userId) {
// 計(jì)算用戶總訂單金額
return new BigDecimal("5000.00");
}
private String calculateVipLevel(BigDecimal amount) {
return amount.compareTo(new BigDecimal("10000")) > 0 ? "VIP2" : "VIP1";
}
}場(chǎng)景說明:這是一個(gè)真實(shí)的電商系統(tǒng)案例。在我之前負(fù)責(zé)的微服務(wù)電商項(xiàng)目中,用戶VIP等級(jí)會(huì)影響訂單優(yōu)惠,而訂單總額又會(huì)影響用戶VIP等級(jí)。這種雙向依賴在Spring容器啟動(dòng)時(shí)就會(huì)報(bào)錯(cuò),提示"Bean currently in creation"。
2.2 Spring MVC中的循環(huán)依賴
// UserController.java - 用戶控制器
@RestController
@RequestMapping("/api/users")
public class UserController {
@Autowired
private UserService userService;
@Autowired
private OrderController orderController; // 錯(cuò)誤的做法:控制器間相互依賴
@PutMapping("/{userId}")
public ResponseEntity<String> updateUser(@PathVariable Long userId, @RequestBody UserUpdateDTO dto) {
userService.updateUser(userId, dto);
// 更新用戶后,觸發(fā)相關(guān)訂單的重新計(jì)算
orderController.recalculateUserOrders(userId);
return ResponseEntity.ok("用戶更新成功");
}
}// OrderController.java - 訂單控制器
@RestController
@RequestMapping("/api/orders")
public class OrderController {
@Autowired
private OrderService orderService;
@Autowired
private UserController userController; // 錯(cuò)誤的做法:控制器間相互依賴
@PostMapping("/recalculate/{userId}")
public ResponseEntity<String> recalculateUserOrders(@PathVariable Long userId) {
orderService.recalculateOrdersForUser(userId);
// 計(jì)算完成后,可能需要更新用戶統(tǒng)計(jì)信息
userController.updateUserStatistics(userId);
return ResponseEntity.ok("訂單重新計(jì)算完成");
}
}場(chǎng)景說明:這個(gè)案例來自我之前的一個(gè)ERP項(xiàng)目。當(dāng)時(shí)為了方便,直接在控制器層相互調(diào)用,結(jié)果造成了循環(huán)依賴。正確的做法是將共享邏輯提取到服務(wù)層處理。
2.3 Spring Data JPA中的循環(huán)依賴
// User.java - 用戶實(shí)體
@Entity
@Table(name = "users")
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String username;
private String email;
@OneToMany(mappedBy = "user", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
private List<Order> orders;
@ManyToOne
@JoinColumn(name = "managed_department_id")
private Department managedDepartment;
// getters and setters
}// Order.java - 訂單實(shí)體
@Entity
@Table(name = "orders")
public class Order {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private BigDecimal amount;
@ManyToOne
@JoinColumn(name = "user_id")
private User user;
@OneToMany(mappedBy = "relatedOrder", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
private List<OrderItem> items;
// getters and setters
}// Department.java - 部門實(shí)體
@Entity
@Table(name = "departments")
public class Department {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
@OneToMany(mappedBy = "managedDepartment")
private List<User> members;
@OneToOne
@JoinColumn(name = "manager_user_id")
private User manager;
// getters and setters
}場(chǎng)景說明:在企業(yè)級(jí)應(yīng)用中,這種雙向關(guān)聯(lián)非常常見。用戶屬于部門,部門經(jīng)理又是一個(gè)用戶,形成一個(gè)循環(huán)。如果處理不當(dāng),序列化時(shí)就會(huì)出現(xiàn)棧溢出。我在一個(gè)HR管理系統(tǒng)中就遇到過這個(gè)問題。
三、循環(huán)依賴的解決方案
3.1 Spring ApplicationEvent事件驅(qū)動(dòng)解耦
以下代碼展示使用Spring ApplicationEvent解決循環(huán)依賴的方法:
// 領(lǐng)域事件定義
public class UserUpdatedEvent extends ApplicationEvent {
private final Long userId;
private final UserUpdateDTO userData;
private final User oldUserData;
public UserUpdatedEvent(Object source, Long userId, UserUpdateDTO userData, User oldUserData) {
super(source);
this.userId = userId;
this.userData = userData;
this.oldUserData = oldUserData;
}
// getters
}// 訂單更新事件
public class OrderUpdatedEvent extends ApplicationEvent {
private final Long userId;
private final BigDecimal totalAmount;
private final List<Order> updatedOrders;
public OrderUpdatedEvent(Object source, Long userId, BigDecimal totalAmount, List<Order> updatedOrders) {
super(source);
this.userId = userId;
this.totalAmount = totalAmount;
this.updatedOrders = updatedOrders;
}
// getters
}// UserServiceImpl.java - 使用事件驅(qū)動(dòng)解耦
@Service
public class UserServiceImpl implements UserService {
@Autowired
private ApplicationEventPublisher eventPublisher;
@Autowired
private UserRepository userRepository;
@Override
@Transactional
public void updateUser(Long userId, UserUpdateDTO userData) {
// 獲取舊數(shù)據(jù)
User oldUser = userRepository.findById(userId)
.orElseThrow(() -> new UserNotFoundException("用戶不存在"));
// 更新用戶信息
User updatedUser = updateUserEntity(oldUser, userData);
userRepository.save(updatedUser);
System.out.println("更新用戶信息: " + userId);
// 發(fā)布用戶更新事件,而不是直接調(diào)用OrderService
UserUpdatedEvent event = new UserUpdatedEvent(this, userId, userData, oldUser);
eventPublisher.publishEvent(event);
}
@Override
public User getUserById(Long userId) {
return userRepository.findById(userId)
.orElseThrow(() -> new UserNotFoundException("用戶不存在"));
}
private User updateUserEntity(User user, UserUpdateDTO dto) {
if (dto.getUsername() != null) {
user.setUsername(dto.getUsername());
}
if (dto.getVipLevel() != null) {
user.setVipLevel(dto.getVipLevel());
}
return user;
}
}// OrderServiceImpl.java - 使用事件驅(qū)動(dòng)解耦
@Service
public class OrderServiceImpl implements OrderService {
@Autowired
private ApplicationEventPublisher eventPublisher;
@Autowired
private OrderRepository orderRepository;
@Override
@Transactional
public void updateOrdersForUser(Long userId, String vipLevel) {
// 更新訂單邏輯
List<Order> orders = orderRepository.findByUserId(userId);
// 根據(jù)新的VIP等級(jí)更新訂單優(yōu)惠
for (Order order : orders) {
order.setDiscountRate(calculateDiscountRate(vipLevel));
}
orderRepository.saveAll(orders);
// 計(jì)算用戶總訂單金額
BigDecimal totalAmount = orders.stream()
.map(Order::getAmount)
.reduce(BigDecimal.ZERO, BigDecimal::add);
System.out.println("更新用戶訂單,總金額: " + totalAmount);
// 發(fā)布訂單更新事件
OrderUpdatedEvent event = new OrderUpdatedEvent(this, userId, totalAmount, orders);
eventPublisher.publishEvent(event);
}
private BigDecimal calculateDiscountRate(String vipLevel) {
switch (vipLevel) {
case "VIP1": return new BigDecimal("0.95");
case "VIP2": return new BigDecimal("0.90");
case "VIP3": return new BigDecimal("0.85");
default: return BigDecimal.ONE;
}
}
}// 事件監(jiān)聽器 - 處理跨模塊的業(yè)務(wù)邏輯
@Component
public class OrderEventListener {
@Autowired
private OrderService orderService;
@EventListener
@Async("taskExecutor") // 異步處理,避免阻塞主流程
public void handleUserUpdated(UserUpdatedEvent event) {
System.out.println("監(jiān)聽到用戶更新事件,用戶ID: " + event.getUserId());
// 根據(jù)用戶更新調(diào)整相關(guān)訂單
if (event.getUserData().getVipLevel() != null) {
orderService.updateOrdersForUser(event.getUserId(), event.getUserData().getVipLevel());
}
}
}
@Component
public class UserEventListener {
@Autowired
private UserService userService;
@EventListener
@Async("taskExecutor")
public void handleOrderUpdated(OrderUpdatedEvent event) {
System.out.println("監(jiān)聽到訂單更新事件,用戶ID: " + event.getUserId());
// 根據(jù)訂單總金額調(diào)整用戶等級(jí)
String newVipLevel = calculateVipLevel(event.getTotalAmount());
User currentUser = userService.getUserById(event.getUserId());
if (!newVipLevel.equals(currentUser.getVipLevel())) {
UserUpdateDTO updateDTO = UserUpdateDTO.builder()
.vipLevel(newVipLevel)
.build();
userService.updateUser(event.getUserId(), updateDTO);
}
}
private String calculateVipLevel(BigDecimal totalAmount) {
if (totalAmount.compareTo(new BigDecimal("50000")) > 0) return "VIP3";
if (totalAmount.compareTo(new BigDecimal("20000")) > 0) return "VIP2";
if (totalAmount.compareTo(new BigDecimal("5000")) > 0) return "VIP1";
return "普通用戶";
}
}// 異步配置
@Configuration
@EnableAsync
public class AsyncConfig {
@Bean("taskExecutor")
public TaskExecutor taskExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(5);
executor.setMaxPoolSize(10);
executor.setQueueCapacity(100);
executor.setThreadNamePrefix("Async-");
executor.initialize();
return executor;
}
}實(shí)戰(zhàn)說明:在我之前的社交電商項(xiàng)目中,用戶系統(tǒng)和商品系統(tǒng)就通過Spring事件驅(qū)動(dòng)機(jī)制解耦。用戶發(fā)布動(dòng)態(tài)時(shí),商品系統(tǒng)監(jiān)聽事件更新推薦算法;商品價(jià)格變動(dòng)時(shí),用戶系統(tǒng)監(jiān)聽事件更新購(gòu)物車。這種方式不僅解決了循環(huán)依賴,還提高了系統(tǒng)的響應(yīng)速度和可擴(kuò)展性。
3.2 Spring @Lazy延遲加載和接口抽象
以下代碼展示使用Spring的@Lazy注解和接口抽象解決循環(huán)依賴:
// 定義服務(wù)接口
public interface UserService {
void updateUser(Long userId, UserUpdateDTO userData);
User getUserById(Long userId);
String getUserVipLevel(Long userId);
}
public interface OrderService {
void updateOrdersForUser(Long userId, String vipLevel);
BigDecimal getTotalOrderAmount(Long userId);
List<Order> getOrdersByUserId(Long userId);
}// UserServiceImpl.java - 使用@Lazy延遲加載
@Service
public class UserServiceImpl implements UserService {
@Autowired
@Lazy // 延遲注入,避免循環(huán)依賴
private OrderService orderService;
@Autowired
private UserRepository userRepository;
@Override
@Transactional
public void updateUser(Long userId, UserUpdateDTO userData) {
User user = userRepository.findById(userId)
.orElseThrow(() -> new UserNotFoundException("用戶不存在"));
System.out.println("更新用戶信息: " + userId);
// 更新用戶實(shí)體
if (userData.getUsername() != null) {
user.setUsername(userData.getUsername());
}
if (userData.getVipLevel() != null) {
user.setVipLevel(userData.getVipLevel());
// VIP等級(jí)變化時(shí),延遲調(diào)用訂單服務(wù)
// orderService此時(shí)可能還未完全初始化,但@Lazy確保了延遲加載
orderService.updateOrdersForUser(userId, userData.getVipLevel());
}
userRepository.save(user);
}
@Override
public User getUserById(Long userId) {
return userRepository.findById(userId)
.orElseThrow(() -> new UserNotFoundException("用戶不存在"));
}
@Override
public String getUserVipLevel(Long userId) {
User user = getUserById(userId);
return user.getVipLevel();
}
}// OrderServiceImpl.java - 同樣使用@Lazy延遲加載
@Service
public class OrderServiceImpl implements OrderService {
@Autowired
@Lazy // 延遲注入
private UserService userService;
@Autowired
private OrderRepository orderRepository;
@Override
@Transactional
public void updateOrdersForUser(Long userId, String vipLevel) {
List<Order> orders = orderRepository.findByUserId(userId);
System.out.println("更新用戶訂單,新的VIP等級(jí): " + vipLevel);
// 更新訂單的折扣率
for (Order order : orders) {
BigDecimal discount = calculateDiscountByVipLevel(vipLevel);
order.setDiscountRate(discount);
order.setFinalAmount(order.getAmount().multiply(discount));
}
orderRepository.saveAll(orders);
// 檢查是否需要更新用戶等級(jí)
BigDecimal totalAmount = getTotalOrderAmount(userId);
String recommendedVipLevel = calculateVipLevelByAmount(totalAmount);
// 延遲調(diào)用用戶服務(wù)
if (!recommendedVipLevel.equals(vipLevel)) {
UserUpdateDTO updateDTO = UserUpdateDTO.builder()
.vipLevel(recommendedVipLevel)
.build();
userService.updateUser(userId, updateDTO);
}
}
@Override
public BigDecimal getTotalOrderAmount(Long userId) {
List<Order> orders = orderRepository.findByUserId(userId);
return orders.stream()
.map(Order::getAmount)
.reduce(BigDecimal.ZERO, BigDecimal::add);
}
@Override
public List<Order> getOrdersByUserId(Long userId) {
return orderRepository.findByUserId(userId);
}
private BigDecimal calculateDiscountByVipLevel(String vipLevel) {
switch (vipLevel) {
case "VIP1": return new BigDecimal("0.95");
case "VIP2": return new BigDecimal("0.90");
case "VIP3": return new BigDecimal("0.85");
default: return BigDecimal.ONE;
}
}
private String calculateVipLevelByAmount(BigDecimal amount) {
if (amount.compareTo(new BigDecimal("100000")) > 0) return "VIP3";
if (amount.compareTo(new BigDecimal("50000")) > 0) return "VIP2";
if (amount.compareTo(new BigDecimal("10000")) > 0) return "VIP1";
return "普通用戶";
}
}// 另一種方案:使用中間服務(wù)協(xié)調(diào)
@Service
public class UserOrderCoordinationService {
@Autowired
private UserService userService;
@Autowired
private OrderService orderService;
/**
* 協(xié)調(diào)用戶和訂單的更新邏輯
* 避免兩個(gè)服務(wù)直接相互調(diào)用
*/
@Transactional
public void coordinateUserAndOrderUpdate(Long userId, UserUpdateDTO userData) {
// 1. 先更新用戶信息
User user = userService.getUserById(userId);
String oldVipLevel = user.getVipLevel();
if (userData.getVipLevel() != null) {
userService.updateUser(userId, userData);
}
// 2. 根據(jù)用戶更新訂單
if (userData.getVipLevel() != null && !userData.getVipLevel().equals(oldVipLevel)) {
orderService.updateOrdersForUser(userId, userData.getVipLevel());
}
// 3. 檢查訂單總額是否需要調(diào)整用戶等級(jí)
BigDecimal totalAmount = orderService.getTotalOrderAmount(userId);
String recommendedLevel = calculateVipLevel(totalAmount);
String currentLevel = userService.getUserVipLevel(userId);
if (!recommendedLevel.equals(currentLevel)) {
UserUpdateDTO newUserData = UserUpdateDTO.builder()
.vipLevel(recommendedLevel)
.build();
userService.updateUser(userId, newUserData);
}
}
private String calculateVipLevel(BigDecimal amount) {
if (amount.compareTo(new BigDecimal("50000")) > 0) return "VIP2";
if (amount.compareTo(new BigDecimal("10000")) > 0) return "VIP1";
return "普通用戶";
}
}// 控制器使用協(xié)調(diào)服務(wù)
@RestController
@RequestMapping("/api/users")
public class UserController {
@Autowired
private UserOrderCoordinationService coordinationService;
@PutMapping("/{userId}")
public ResponseEntity<String> updateUser(@PathVariable Long userId,
@RequestBody @Valid UserUpdateDTO userData) {
coordinationService.coordinateUserAndOrderUpdate(userId, userData);
return ResponseEntity.ok("用戶及關(guān)聯(lián)訂單更新成功");
}
}實(shí)戰(zhàn)說明:在金融項(xiàng)目中,我使用過接口隔離的原則解決支付模塊和賬戶模塊的循環(huán)依賴。支付時(shí)需要驗(yàn)證賬戶,賬戶變動(dòng)時(shí)又要觸發(fā)支付回調(diào)。通過引入IPaymentGateway接口和IAccountService接口,并使用Spring的@Lazy注解延遲加載,成功解決了這個(gè)問題。@Lazy注解是Spring提供的強(qiáng)大工具,它可以讓Bean在第一次使用時(shí)才被初始化,從而避免循環(huán)依賴問題。
3.3 Spring AOP切面和協(xié)調(diào)者模式
以下代碼展示使用Spring AOP和協(xié)調(diào)者模式解決循環(huán)依賴:
// 定義協(xié)調(diào)者接口
public interface BusinessProcessCoordinator {
void processUserUpdate(Long userId, UserUpdateDTO userData);
void processOrderUpdate(Long userId, OrderUpdateDTO orderData);
}
// 實(shí)現(xiàn)協(xié)調(diào)者 - 統(tǒng)一處理跨服務(wù)業(yè)務(wù)邏輯
@Service
public class UserOrderCoordinator implements BusinessProcessCoordinator {
@Autowired
private UserService userService;
@Autowired
private OrderService orderService;
@Autowired
private NotificationService notificationService;
/**
* 協(xié)調(diào)用戶更新的完整流程
* 避免服務(wù)間的直接相互調(diào)用
*/
@Override
@Transactional
public void processUserUpdate(Long userId, UserUpdateDTO userData) {
// 1. 獲取當(dāng)前用戶狀態(tài)
User currentUser = userService.getUserById(userId);
String oldVipLevel = currentUser.getVipLevel();
// 2. 更新用戶信息
userService.updateUser(userId, userData);
// 3. 如果VIP等級(jí)變化,處理相關(guān)訂單
if (userData.getVipLevel() != null && !userData.getVipLevel().equals(oldVipLevel)) {
List<Order> affectedOrders = orderService.getOrdersByUserId(userId);
for (Order order : affectedOrders) {
orderService.updateOrderDiscount(order.getId(), userData.getVipLevel());
}
}
// 4. 發(fā)送通知(通過協(xié)調(diào)者調(diào)用,避免循環(huán)依賴)
if (userData.getVipLevel() != null && !userData.getVipLevel().equals(oldVipLevel)) {
notificationService.sendVipLevelChangeNotification(userId, oldVipLevel, userData.getVipLevel());
}
}
@Override
@Transactional
public void processOrderUpdate(Long userId, OrderUpdateDTO orderData) {
// 1. 處理訂單更新
Order updatedOrder = orderService.updateOrder(userId, orderData);
// 2. 重新計(jì)算用戶總消費(fèi)金額
BigDecimal totalAmount = orderService.getTotalOrderAmount(userId);
// 3. 根據(jù)總消費(fèi)金額調(diào)整用戶等級(jí)
String newVipLevel = calculateVipLevel(totalAmount);
User currentUser = userService.getUserById(userId);
if (!newVipLevel.equals(currentUser.getVipLevel())) {
UserUpdateDTO userUpdate = UserUpdateDTO.builder()
.vipLevel(newVipLevel)
.build();
userService.updateUser(userId, userUpdate);
// 4. 發(fā)送等級(jí)提升通知
notificationService.sendVipLevelUpgradeNotification(userId, currentUser.getVipLevel(), newVipLevel);
}
}
private String calculateVipLevel(BigDecimal totalAmount) {
if (totalAmount.compareTo(new BigDecimal("100000")) > 0) return "VIP3";
if (totalAmount.compareTo(new BigDecimal("50000")) > 0) return "VIP2";
if (totalAmount.compareTo(new BigDecimal("10000")) > 0) return "VIP1";
return "普通用戶";
}
}// 使用AOP切面記錄審計(jì)日志,避免在業(yè)務(wù)服務(wù)中直接調(diào)用審計(jì)服務(wù)
@Aspect
@Component
public class BusinessAuditAspect {
@Autowired
private AuditService auditService;
// 用戶操作審計(jì)切面
@Around("execution(* com.example.service.UserService.updateUser(..))")
public Object auditUserOperation(ProceedingJoinPoint joinPoint) throws Throwable {
Long userId = (Long) joinPoint.getArgs()[0];
UserUpdateDTO userData = (UserUpdateDTO) joinPoint.getArgs()[1];
// 記錄操作前狀態(tài)
User oldUser = getUserById(userId);
try {
// 執(zhí)行原方法
Object result = joinPoint.proceed();
// 記錄審計(jì)日志(通過切面,避免循環(huán)依賴)
auditService.logUserUpdate(userId, oldUser, userData, "SUCCESS");
return result;
} catch (Exception e) {
// 記錄失敗日志
auditService.logUserUpdate(userId, oldUser, userData, "FAILED: " + e.getMessage());
throw e;
}
}
// 訂單操作審計(jì)切面
@Around("execution(* com.example.service.OrderService.updateOrdersForUser(..))")
public Object auditOrderOperation(ProceedingJoinPoint joinPoint) throws Throwable {
Long userId = (Long) joinPoint.getArgs()[0];
String vipLevel = (String) joinPoint.getArgs()[1];
// 記錄操作前狀態(tài)
List<Order> oldOrders = getOrdersByUserId(userId);
try {
Object result = joinPoint.proceed();
// 記錄審計(jì)日志
auditService.logOrderUpdate(userId, oldOrders, vipLevel, "SUCCESS");
return result;
} catch (Exception e) {
auditService.logOrderUpdate(userId, oldOrders, vipLevel, "FAILED: " + e.getMessage());
throw e;
}
}
// 輔助方法(實(shí)際項(xiàng)目中應(yīng)注入相應(yīng)服務(wù))
private User getUserById(Long userId) {
// 模擬實(shí)現(xiàn)
return new User(userId, "用戶名", "VIP1");
}
private List<Order> getOrdersByUserId(Long userId) {
// 模擬實(shí)現(xiàn)
return Arrays.asList(new Order(1L, userId, new BigDecimal("1000")));
}
}// 重構(gòu)后的UserService - 只關(guān)注用戶相關(guān)業(yè)務(wù)邏輯
@Service
public class UserServiceImpl implements UserService {
@Autowired
private UserRepository userRepository;
// 不再直接依賴OrderService,避免循環(huán)依賴
@Override
@Transactional
public void updateUser(Long userId, UserUpdateDTO userData) {
User user = userRepository.findById(userId)
.orElseThrow(() -> new UserNotFoundException("用戶不存在"));
System.out.println("更新用戶信息: " + userId);
// 只處理用戶相關(guān)的業(yè)務(wù)邏輯
if (userData.getUsername() != null) {
user.setUsername(userData.getUsername());
}
if (userData.getEmail() != null) {
user.setEmail(userData.getEmail());
}
if (userData.getVipLevel() != null) {
user.setVipLevel(userData.getVipLevel());
}
userRepository.save(user);
}
@Override
public User getUserById(Long userId) {
return userRepository.findById(userId)
.orElseThrow(() -> new UserNotFoundException("用戶不存在"));
}
@Override
public String getUserVipLevel(Long userId) {
return getUserById(userId).getVipLevel();
}
}// 重構(gòu)后的OrderService - 只關(guān)注訂單相關(guān)業(yè)務(wù)邏輯
@Service
public class OrderServiceImpl implements OrderService {
@Autowired
private OrderRepository orderRepository;
// 不再直接依賴UserService,避免循環(huán)依賴
@Override
@Transactional
public void updateOrdersForUser(Long userId, String vipLevel) {
List<Order> orders = orderRepository.findByUserId(userId);
System.out.println("更新用戶訂單折扣,VIP等級(jí): " + vipLevel);
// 只處理訂單相關(guān)的業(yè)務(wù)邏輯
for (Order order : orders) {
BigDecimal discount = calculateDiscountByVipLevel(vipLevel);
order.setDiscountRate(discount);
order.setFinalAmount(order.getAmount().multiply(discount));
}
orderRepository.saveAll(orders);
}
@Override
public BigDecimal getTotalOrderAmount(Long userId) {
List<Order> orders = orderRepository.findByUserId(userId);
return orders.stream()
.map(Order::getAmount)
.reduce(BigDecimal.ZERO, BigDecimal::add);
}
@Override
public List<Order> getOrdersByUserId(Long userId) {
return orderRepository.findByUserId(userId);
}
private BigDecimal calculateDiscountByVipLevel(String vipLevel) {
switch (vipLevel) {
case "VIP1": return new BigDecimal("0.95");
case "VIP2": return new BigDecimal("0.90");
case "VIP3": return new BigDecimal("0.85");
default: return BigDecimal.ONE;
}
}
public Order updateOrder(Long userId, OrderUpdateDTO orderData) {
// 訂單更新邏輯
return new Order();
}
public void updateOrderDiscount(Long orderId, String vipLevel) {
// 更新單個(gè)訂單折扣的邏輯
}
}// 控制器使用協(xié)調(diào)者而不是直接調(diào)用服務(wù)
@RestController
@RequestMapping("/api/users")
public class UserController {
@Autowired
private BusinessProcessCoordinator coordinator;
@PutMapping("/{userId}")
public ResponseEntity<String> updateUser(@PathVariable Long userId,
@RequestBody @Valid UserUpdateDTO userData) {
// 通過協(xié)調(diào)者處理完整的業(yè)務(wù)流程
coordinator.processUserUpdate(userId, userData);
return ResponseEntity.ok("用戶更新成功");
}
}
@RestController
@RequestMapping("/api/orders")
public class OrderController {
@Autowired
private BusinessProcessCoordinator coordinator;
@PutMapping("/user/{userId}")
public ResponseEntity<String> updateOrdersForUser(@PathVariable Long userId,
@RequestBody @Valid OrderUpdateDTO orderData) {
// 通過協(xié)調(diào)者處理完整的業(yè)務(wù)流程
coordinator.processOrderUpdate(userId, orderData);
return ResponseEntity.ok("訂單更新成功");
}
}實(shí)戰(zhàn)說明:這個(gè)協(xié)調(diào)者模式是我在一個(gè)大型ERP項(xiàng)目中應(yīng)用的。原來的設(shè)計(jì)是用戶服務(wù)、訂單服務(wù)、通知服務(wù)、審計(jì)服務(wù)互相依賴,系統(tǒng)維護(hù)非常困難。重構(gòu)后引入?yún)f(xié)調(diào)者統(tǒng)一管理跨服務(wù)業(yè)務(wù)流程,使用AOP處理橫切關(guān)注點(diǎn)(如審計(jì)、日志),徹底消除了循環(huán)依賴問題。各服務(wù)職責(zé)單一,測(cè)試和維護(hù)變得非常簡(jiǎn)單。
3.4 Spring代理模式和ObjectProvider
以下代碼展示使用Spring的ObjectProvider和代理模式解決循環(huán)依賴:
// 使用ObjectProvider解決循環(huán)依賴
@Service
public class UserServiceImpl implements UserService {
private final ObjectProvider<OrderService> orderServiceProvider;
private final UserRepository userRepository;
// 通過構(gòu)造函數(shù)注入ObjectProvider而不是直接注入OrderService
public UserServiceImpl(ObjectProvider<OrderService> orderServiceProvider,
UserRepository userRepository) {
this.orderServiceProvider = orderServiceProvider;
this.userRepository = userRepository;
}
@Override
@Transactional
public void updateUser(Long userId, UserUpdateDTO userData) {
User user = userRepository.findById(userId)
.orElseThrow(() -> new UserNotFoundException("用戶不存在"));
System.out.println("更新用戶信息: " + userId);
// 更新用戶實(shí)體
if (userData.getVipLevel() != null) {
user.setVipLevel(userData.getVipLevel());
}
if (userData.getUsername() != null) {
user.setUsername(userData.getUsername());
}
userRepository.save(user);
// 需要時(shí)才獲取OrderService實(shí)例
if (userData.getVipLevel() != null) {
OrderService orderService = orderServiceProvider.getObject();
orderService.updateOrdersForUser(userId, userData.getVipLevel());
}
}
@Override
public User getUserById(Long userId) {
return userRepository.findById(userId)
.orElseThrow(() -> new UserNotFoundException("用戶不存在"));
}
@Override
public String getUserVipLevel(Long userId) {
return getUserById(userId).getVipLevel();
}
}// OrderService也使用ObjectProvider
@Service
public class OrderServiceImpl implements OrderService {
private final ObjectProvider<UserService> userServiceProvider;
private final OrderRepository orderRepository;
public OrderServiceImpl(ObjectProvider<UserService> userServiceProvider,
OrderRepository orderRepository) {
this.userServiceProvider = userServiceProvider;
this.orderRepository = orderRepository;
}
@Override
@Transactional
public void updateOrdersForUser(Long userId, String vipLevel) {
List<Order> orders = orderRepository.findByUserId(userId);
System.out.println("更新用戶訂單,VIP等級(jí): " + vipLevel);
// 更新訂單折扣
for (Order order : orders) {
BigDecimal discount = calculateDiscountByVipLevel(vipLevel);
order.setDiscountRate(discount);
order.setFinalAmount(order.getAmount().multiply(discount));
}
orderRepository.saveAll(orders);
// 檢查是否需要調(diào)整用戶等級(jí)
BigDecimal totalAmount = getTotalOrderAmount(userId);
String recommendedLevel = calculateVipLevel(totalAmount);
// 延遲獲取UserService實(shí)例
String currentLevel = userServiceProvider.getObject().getUserVipLevel(userId);
if (!recommendedLevel.equals(currentLevel)) {
UserService userService = userServiceProvider.getObject();
UserUpdateDTO updateDTO = UserUpdateDTO.builder()
.vipLevel(recommendedLevel)
.build();
userService.updateUser(userId, updateDTO);
}
}
@Override
public BigDecimal getTotalOrderAmount(Long userId) {
List<Order> orders = orderRepository.findByUserId(userId);
return orders.stream()
.map(Order::getAmount)
.reduce(BigDecimal.ZERO, BigDecimal::add);
}
@Override
public List<Order> getOrdersByUserId(Long userId) {
return orderRepository.findByUserId(userId);
}
private BigDecimal calculateDiscountByVipLevel(String vipLevel) {
switch (vipLevel) {
case "VIP1": return new BigDecimal("0.95");
case "VIP2": return new BigDecimal("0.90");
case "VIP3": return new BigDecimal("0.85");
default: return BigDecimal.ONE;
}
}
private String calculateVipLevel(BigDecimal amount) {
if (amount.compareTo(new BigDecimal("100000")) > 0) return "VIP3";
if (amount.compareTo(new BigDecimal("50000")) > 0) return "VIP2";
if (amount.compareTo(new BigDecimal("10000")) > 0) return "VIP1";
return "普通用戶";
}
}// 自定義代理工廠,提供更靈活的延遲加載
@Component
public class LazyServiceProxyFactory implements BeanFactoryAware {
private BeanFactory beanFactory;
public <T> T createLazyProxy(Class<T> serviceClass) {
return (T) Proxy.newProxyInstance(
serviceClass.getClassLoader(),
new Class[]{serviceClass},
new LazyServiceInvocationHandler<>(serviceClass, beanFactory)
);
}
@Override
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
this.beanFactory = beanFactory;
}
private static class LazyServiceInvocationHandler<T> implements InvocationHandler {
private final Class<T> serviceClass;
private final BeanFactory beanFactory;
private volatile T target;
public LazyServiceInvocationHandler(Class<T> serviceClass, BeanFactory beanFactory) {
this.serviceClass = serviceClass;
this.beanFactory = beanFactory;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if (target == null) {
synchronized (this) {
if (target == null) {
target = beanFactory.getBean(serviceClass);
}
}
}
return method.invoke(target, args);
}
}
}// 使用自定義代理的服務(wù)
@Service
public class UserServiceWithCustomProxy implements UserService {
private final LazyServiceProxyFactory proxyFactory;
private final OrderService orderService; // 使用代理對(duì)象
private final UserRepository userRepository;
public UserServiceWithCustomProxy(LazyServiceProxyFactory proxyFactory,
UserRepository userRepository) {
this.proxyFactory = proxyFactory;
this.orderService = proxyFactory.createLazyProxy(OrderService.class);
this.userRepository = userRepository;
}
@Override
@Transactional
public void updateUser(Long userId, UserUpdateDTO userData) {
User user = userRepository.findById(userId)
.orElseThrow(() -> new UserNotFoundException("用戶不存在"));
System.out.println("更新用戶信息: " + userId);
// 更新用戶信息
if (userData.getVipLevel() != null) {
user.setVipLevel(userData.getVipLevel());
}
userRepository.save(user);
// 代理對(duì)象會(huì)在第一次調(diào)用時(shí)才真正初始化OrderService
if (userData.getVipLevel() != null) {
orderService.updateOrdersForUser(userId, userData.getVipLevel());
}
}
@Override
public User getUserById(Long userId) {
return userRepository.findById(userId)
.orElseThrow(() -> new UserNotFoundException("用戶不存在"));
}
@Override
public String getUserVipLevel(Long userId) {
return getUserById(userId).getVipLevel();
}
}實(shí)戰(zhàn)說明:在我處理的一個(gè)微服務(wù)項(xiàng)目中,訂單服務(wù)和庫(kù)存服務(wù)存在嚴(yán)重的循環(huán)依賴問題。通過使用Spring的ObjectProvider和自定義代理模式,我們實(shí)現(xiàn)了真正的按需加載,服務(wù)只有在真正被調(diào)用時(shí)才會(huì)被初始化,徹底避免了啟動(dòng)時(shí)的循環(huán)依賴問題。這種方案特別適合于大型微服務(wù)架構(gòu),其中服務(wù)間的依賴關(guān)系復(fù)雜,但又必須保持解耦。
四、面試回答技巧
4.1 面試官提問時(shí)的標(biāo)準(zhǔn)回答結(jié)構(gòu)
面試官:你遇到過循環(huán)依賴嗎?是怎么解決的?
標(biāo)準(zhǔn)回答:
"是的,我在實(shí)際開發(fā)中確實(shí)遇到過循環(huán)依賴問題。讓我從三個(gè)方面來回答:
第一,我遇到的循環(huán)依賴場(chǎng)景:
在我之前負(fù)責(zé)的Spring Boot電商項(xiàng)目中,用戶服務(wù)(UserService)和訂單服務(wù)(OrderService)存在循環(huán)依賴。用戶VIP等級(jí)變化需要更新訂單優(yōu)惠策略,而訂單總額變化又要影響用戶VIP等級(jí)。這導(dǎo)致了Spring容器啟動(dòng)時(shí)出現(xiàn)’Bean currently in creation’的錯(cuò)誤。
第二,我的解決思路和方案:
我采用了Spring ApplicationEvent事件驅(qū)動(dòng)架構(gòu)來解決這個(gè)問題的。具體做法是:
- 定義了UserUpdatedEvent和OrderUpdatedEvent領(lǐng)域事件
- 使用ApplicationEventPublisher發(fā)布事件,而不是直接調(diào)用其他服務(wù)
- 創(chuàng)建了對(duì)應(yīng)的事件監(jiān)聽器處理跨服務(wù)業(yè)務(wù)邏輯
- 配置了異步執(zhí)行器(@Async)提高系統(tǒng)性能
第三,解決后的效果:
- 消除了循環(huán)依賴,Spring容器可以正常啟動(dòng)
- 提高了系統(tǒng)的可擴(kuò)展性,后續(xù)其他服務(wù)也可以訂閱這些事件
- 降低了耦合度,各服務(wù)可以獨(dú)立測(cè)試和部署
- 通過異步處理提升了系統(tǒng)的響應(yīng)速度"
4.2 體現(xiàn)技術(shù)深度的回答要點(diǎn)
1. 展現(xiàn)理論深度:
“循環(huán)依賴本質(zhì)上是違反了依賴倒置原則,高層模塊不應(yīng)該依賴低層模塊,兩者都應(yīng)該依賴抽象。在Spring生態(tài)中,我們可以通過多種技術(shù)手段來解決:@Lazy延遲加載、ApplicationEvent事件驅(qū)動(dòng)、ObjectProvider延遲注入、AOP切面處理、以及協(xié)調(diào)者模式等。關(guān)鍵是要理解Spring容器的Bean生命周期和依賴注入機(jī)制。”
2. 展現(xiàn)實(shí)踐經(jīng)驗(yàn):
"在Spring Boot項(xiàng)目中,我處理過幾種不同的循環(huán)依賴場(chǎng)景:
- 服務(wù)層循環(huán)依賴:使用@Lazy注解或事件驅(qū)動(dòng)解決
- 實(shí)體類雙向關(guān)聯(lián):使用@JsonIgnore避免序列化問題,或者使用DTO模式
- 控制器層循環(huán)依賴:通過協(xié)調(diào)者模式重構(gòu),將業(yè)務(wù)邏輯下沉到服務(wù)層
- 跨模塊循環(huán)依賴:引入領(lǐng)域事件和事件總線,實(shí)現(xiàn)模塊解耦
每種場(chǎng)景都有最適合的解決方案,關(guān)鍵是要根據(jù)具體的業(yè)務(wù)需求和技術(shù)架構(gòu)來選擇。"
3. 展現(xiàn)架構(gòu)思維:
"預(yù)防循環(huán)依賴比解決更重要。在Spring項(xiàng)目設(shè)計(jì)階段,我會(huì):
- 遵循DDD領(lǐng)域驅(qū)動(dòng)設(shè)計(jì),按業(yè)務(wù)邊界劃分限界上下文
- 建立清晰的依賴關(guān)系圖,確保依賴方向是單向的
- 使用Spring Boot Actuator的beans端點(diǎn)監(jiān)控Bean依賴關(guān)系
- 在CI/CD流程中加入依賴關(guān)系檢查
- 團(tuán)隊(duì)代碼審查時(shí)重點(diǎn)關(guān)注模塊間依賴設(shè)計(jì)
我習(xí)慣用ArchUnit這樣的架構(gòu)測(cè)試工具來確保架構(gòu)規(guī)則的執(zhí)行。"
4.3 可能的追問及回答
追問:如果業(yè)務(wù)邏輯必須要有循環(huán)依賴怎么辦?
回答:
"這種情況下,我會(huì)根據(jù)Spring框架的特性來處理:
- 重新審視業(yè)務(wù)流程:看看是否可以通過調(diào)整業(yè)務(wù)邏輯來避免,比如引入最終一致性概念
- 使用Spring技術(shù)解耦:
- @Lazy延遲加載解決服務(wù)層循環(huán)依賴
- ApplicationEvent事件驅(qū)動(dòng)實(shí)現(xiàn)異步解耦
- @TransactionalEventListener確保事務(wù)一致性
- ObjectProvider實(shí)現(xiàn)延遲注入
- 協(xié)調(diào)者模式:引入BusinessProcessCoordinator統(tǒng)一管理復(fù)雜業(yè)務(wù)流程
- 分布式事務(wù)處理:如果是微服務(wù)場(chǎng)景,考慮使用Saga模式或TCC模式
關(guān)鍵是要將循環(huán)依賴的控制權(quán)收歸到一個(gè)協(xié)調(diào)者手中,讓各個(gè)Spring Bean都依賴這個(gè)協(xié)調(diào)者,而不是互相依賴。"
追問:如何預(yù)防循環(huán)依賴的產(chǎn)生?
回答:
"我會(huì)在幾個(gè)方面著手,特別是在Spring Boot項(xiàng)目中:
- 架構(gòu)設(shè)計(jì)階段:
- 使用DDD劃分限界上下文,確保依賴方向正確
- 繪制Spring Bean依賴關(guān)系圖,確保沒有環(huán)形依賴
- 定義清晰的分層架構(gòu):Controller → Service → Repository
- 代碼規(guī)范階段:
- 建立團(tuán)隊(duì)代碼規(guī)范,禁止@Service層之間的直接依賴
- 要求使用接口編程,面向抽象而非具體實(shí)現(xiàn)
- 工具支持:
- 使用ArchUnit編寫架構(gòu)測(cè)試,自動(dòng)檢測(cè)循環(huán)依賴
- 配置IDE插件(如SonarLint)實(shí)時(shí)檢查依賴關(guān)系
- 使用Spring Boot Actuator的/beans端點(diǎn)監(jiān)控Bean依賴
- 持續(xù)監(jiān)控:
- 在CI/CD流程中加入依賴關(guān)系檢查
- 定期使用jdeps或類似工具分析項(xiàng)目依賴
- 使用Maven/Gradle的依賴分析插件"
4.4 體現(xiàn)解決問題能力的回答模板
"遇到Spring循環(huán)依賴問題時(shí),我的解決思路是:分析問題根源 → 設(shè)計(jì)解決方案 → 實(shí)施改造 → 驗(yàn)證效果。
在之前的Spring Boot電商項(xiàng)目中,我不僅解決了用戶服務(wù)和訂單服務(wù)的循環(huán)依賴問題,還做了以下系統(tǒng)性改進(jìn):
- 建立Spring依賴管理規(guī)范:制定了@Service層依賴規(guī)范,要求使用接口編程和@Lazy注解
- 引入架構(gòu)測(cè)試:使用ArchUnit編寫自動(dòng)化測(cè)試,防止新的循環(huán)依賴產(chǎn)生
- 完善監(jiān)控體系:配置Spring Boot Actuator,定期檢查Bean依賴關(guān)系
- 團(tuán)隊(duì)培訓(xùn):組織Spring最佳實(shí)踐分享,提升團(tuán)隊(duì)整體架構(gòu)意識(shí)
這種系統(tǒng)性的問題解決思路,讓我在后續(xù)項(xiàng)目中能夠更好地規(guī)避技術(shù)債務(wù),確保Spring應(yīng)用的健康性和可維護(hù)性。"
總結(jié)
循環(huán)依賴是軟件開發(fā)中常見但嚴(yán)重的問題。通過合理的設(shè)計(jì)模式選擇、架構(gòu)重構(gòu)和技術(shù)手段應(yīng)用,我們不僅可以解決已有的循環(huán)依賴問題,更重要的是從源頭上預(yù)防這類問題的產(chǎn)生。在實(shí)際項(xiàng)目中,要根據(jù)具體的業(yè)務(wù)場(chǎng)景和技術(shù)棧選擇最適合的解決方案,并建立長(zhǎng)期的管理機(jī)制來維護(hù)代碼庫(kù)的健康度。
到此這篇關(guān)于Spring Boot Java循環(huán)依賴詳解的文章就介紹到這了,更多相關(guān)Spring Boot Java循環(huán)依賴內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
SpringBoot生成License的實(shí)現(xiàn)示例
License指的是版權(quán)許可證,那么對(duì)于SpringBoot項(xiàng)目,如何增加License呢?本文就來介紹一下,感興趣的可以了解一下2021-06-06
Java連接MySQL數(shù)據(jù)庫(kù)命令行程序過程
SQL編程包括兩種形式,一種是過程化編程,主要通過數(shù)據(jù)庫(kù)交互式工具,通過存儲(chǔ)過程、觸發(fā)器、函數(shù)等形式的編程;另一種是嵌入式SQL編程,將SQL語(yǔ)句嵌入到高級(jí)開發(fā)語(yǔ)言,完成數(shù)據(jù)的各種操作2021-10-10
Java設(shè)計(jì)模式中的代理設(shè)計(jì)模式詳細(xì)解析
這篇文章主要介紹了Java設(shè)計(jì)模式中的代理設(shè)計(jì)模式詳細(xì)解析,代理模式,重要的在于代理二字,何為代理,我們可以聯(lián)想到生活中的例子,比如秘書、中介這類職業(yè),我們可以委托中介去幫我們完成某些事情,而我們自己只需要關(guān)注我們必須完成的事情,需要的朋友可以參考下2023-12-12
Mac使用Idea配置傳統(tǒng)SSM項(xiàng)目(非maven項(xiàng)目)
本文主要介紹了Mac使用Idea配置傳統(tǒng)SSM項(xiàng)目(非maven項(xiàng)目),將展示如何設(shè)置項(xiàng)目結(jié)構(gòu)、添加依賴關(guān)系等,具有一定的參考價(jià)值,感興趣的可以了解一下2024-01-01
Spring Boot Actuator未授權(quán)訪問漏洞的問題解決
Spring Boot Actuator 端點(diǎn)的未授權(quán)訪問漏洞是一個(gè)安全性問題,可能會(huì)導(dǎo)致未經(jīng)授權(quán)的用戶訪問敏感的應(yīng)用程序信息,本文就來介紹一下解決方法,感興趣的可以了解一下2023-09-09
詳解自定義SpringMVC的Http信息轉(zhuǎn)換器的使用
這篇文章主要介紹了詳解自定義SpringMVC的Http信息轉(zhuǎn)換器的使用,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2017-11-11

