类之间的循环依赖,你头疼了吗?

buguge - Keep it simple,stupid / 2023-07-24 / 原文

类之间的循环依赖指的的A依赖B,B也依赖A,这就出现循环依赖。如果是spring容器中的两个bean出现循环依赖,则在容器启动时,可能会报错,导致无法启动。

 

如果解决循环依赖?

首先要理清各类的职责,尤其是分层职责————相同层级不要相互调用,调用下级API。

下面是职责不清晰导致的循环依赖。解决方案是,调用下层方法。UserService和LoginAccountService是同一层服务,那么,他们在调用对方的数据时,应调用其下层的方法。例如:UserService#selectUserInfo在获取LoginAccount数据时,改为调用loginAccountMapper#selectByUserId。这样就解决了bean直接的循环依赖。

// -------------------------  UserService  ------------------------------------
@Service
public class UserService {
    @Resource
    private UserMapper userMapper;
    @Resource
    private LoginAccountService loginAccountService;


    public UserVO selectUserInfo(int userId) {
        User user = userMapper.selectByUserId(userId);
        if (user == null) {
            throw new ResponseException("用户不存在");
        }
        UserVO userVO = BeanMapper.map(user, UserVO.class);
        LoginAccount loginAccount= loginAccountService.selectByUserId(userId);
        if (loginAccount!=null){
            userVO.setLoginAccount(loginAccount.getLoginAccount());
        }
        return userVO;
    }

    public void validUser(int userId) {
        User user = userMapper.selectByUserId(userId);
        if (user == null) {
            throw new ResponseException("用户不存在");
        }
        if (!"NORMAL".equalsIgnoreCase(user.getStatus())) {
            throw new ResponseException("用户已禁用");
        }
    }
}

// ---------------------------  LoginAccountService ----------------------------------
@Service
public class LoginAccountService {
    @Resource
    private UserService userService;
    @Resource
    private LoginAccountMapper loginAccountMapper;

    public LoginAccount login(String loginAccount, String password) {
        // 获取登陆账号
        LoginAccount entity = loginAccountMapper.selectByAccount(loginAccount);
        if (entity == null) {
            throw new ResponseException("账号不存在");
        }
        if (!password.equals(entity.getPassword())) {
            throw new ResponseException("用户密码错误");
        }
        userService.validUser(entity.getUserId());
        return entity;

    }

    public LoginAccount selectByUserId(int userId) {
        return loginAccountMapper.selectByUserId(userId);
    }
}
View Code

当然,上面的循环依赖,单从技术层面,加@Lasy是可以解决的,但还是应该从类的职责方面来进行改进。职责不清晰会带来诸多问题,绝不仅仅是重复依赖的问题。

 

还有一种情况,也是我今天碰到的一个情况。跟大家分享一下。
我们的中台channel服务,对接了数家银行通道。其中的一个YiLian通道有个特殊需求,在支付时需要对收款人进行报备,报备完成才可下发。见下图示意图,YiLianPayStrategy 有注入PayUserSignService,PayUserSignService有注入YiLianSignStrategy,YiLianSignStrategy有注入YiLianPayStrategy。 这就导致了YiLianPayStrategy 与YiLianSignStrategy的相互循环依赖。

 

这在企业应用开发中是个比较典型的正常案例。那么,如果解决这种循环依赖呢?
解决方案有二:
1. 不使用bean注入的方式,改为需要时才获取bean。


2. 使用spring的事件监听器,实现类之间的解耦。