Spring Boot投票与帖子/评论服务循环依赖的优化方案及@Lazy有效性咨询
问题描述
我的应用包含Post、Comment、Vote三个实体。目前在VoteService中注入了PostService和CommentService,用于投票时的数据校验,VoteService的投票方法代码如下:
@Transactional public boolean votePost(Member currentMember, Long postId) { Post post = postService.getPostById(postId); Optional<Vote> optionalVote = voteRepository.findByMemberAndPost(currentMember, post); if(optionalVote.isPresent()) { voteRepository.delete(optionalVote.get()); return false; } else { voteRepository.save(new Vote(currentMember, post)); return true; } }
同时,PostService和CommentService需要调用VoteService的方法来检查登录用户的投票状态,VoteService中对应的检查方法如下:
public boolean checkIfMemberVotedPost(Long memberId, Long postId) { return voteRepository.existsVoteByMemberIdAndPostId(memberId, postId); } public boolean checkIfMemberVotedComment(Long memberId, Long commentId) { return voteRepository.existsVoteByMemberIdAndCommentId(memberId, commentId); }
现在出现了Bean循环依赖错误,我想了解:
- 更优的设计方案是什么?
- 使用@Lazy是不是无需重构应用的有效解决方案?
回答
一、@Lazy是无需重构的有效解决方案
是的。Spring中的@Lazy注解通过延迟初始化Bean的方式打破循环依赖,不需要修改业务逻辑,只需要调整注入方式即可。
你可以在VoteService注入PostService和CommentService时添加@Lazy:
@Service public class VoteService { private final PostService postService; private final CommentService commentService; private final VoteRepository voteRepository; public VoteService(@Lazy PostService postService, @Lazy CommentService commentService, VoteRepository voteRepository) { this.postService = postService; this.commentService = commentService; this.voteRepository = voteRepository; } // 原有业务方法... }
或者在PostService/CommentService中注入VoteService时添加@Lazy,两种方式都能解决问题。延迟初始化的Bean会在第一次被调用时完成初始化,这对绝大多数业务场景没有影响。
二、更优的设计方案
循环依赖的核心原因是Service职责边界模糊,以下两种重构方式能从根源上解决问题:
1. 直接依赖Repository,简化Service依赖
VoteService调用PostService.getPostById(postId)只是为了校验Post是否存在,完全可以直接注入PostRepository完成校验,不需要通过PostService。同理,Comment相关的校验也可以直接用CommentRepository。
重构后的VoteService投票方法:
@Transactional public boolean votePost(Member currentMember, Long postId) { // 直接通过PostRepository校验Post存在 Post post = postRepository.findById(postId) .orElseThrow(() -> new IllegalArgumentException("指定的Post不存在")); Optional<Vote> optionalVote = voteRepository.findByMemberAndPost(currentMember, post); if(optionalVote.isPresent()) { voteRepository.delete(optionalVote.get()); return false; } else { voteRepository.save(new Vote(currentMember, post)); return true; } }
这样VoteService只依赖各个Repository,PostService和CommentService依赖VoteService,不会形成循环依赖,同时也符合“单一职责”原则。
2. 拆分投票状态检查职责到独立组件
如果需要保持Service层封装Repository的规范,可以把投票状态检查的逻辑抽离到独立组件中,比如创建VoteStatusChecker:
@Component public class VoteStatusChecker { private final VoteRepository voteRepository; public VoteStatusChecker(VoteRepository voteRepository) { this.voteRepository = voteRepository; } public boolean checkIfMemberVotedPost(Long memberId, Long postId) { return voteRepository.existsVoteByMemberIdAndPostId(memberId, postId); } public boolean checkIfMemberVotedComment(Long memberId, Long commentId) { return voteRepository.existsVoteByMemberIdAndCommentId(memberId, commentId); } }
之后PostService和CommentService注入VoteStatusChecker来检查投票状态,而不是依赖VoteService;VoteService只依赖Post/CommentRepository,彻底打破循环依赖。
内容的提问来源于stack exchange,提问作者geon

