2024.11.12

主要问题:

SecurityHandlerhandlerOnAuthenticationSuccess 方法中调用了 userService.userLoginStatus,这会导致 UserService 需要依赖 SecurityHandler。这种双向依赖形成了一个循环依赖,Spring 容器无法正确处理这个循环,导致应用启动失败。

解决思路:

1. 避免循环依赖

通过重构代码,消除这种循环依赖。常见的做法是将其中某个依赖提取出来,或改变它们的关系。以下是几种可能的解决方法:

方法 1:延迟加载 UserService

如果 UserService 仅在 SecurityHandler 中的某些方法中使用,你可以使用 @Lazy 注解来延迟加载 UserService,从而避免循环依赖。

代码修改如下:

@Component
@RequiredArgsConstructor
public class SecurityHandler {

    @Resource
    private JwtUtils jwtUtils;

    @Resource
    private RedisCache redisCache;

    @Resource
    private LoginLogService loginLogService;

    @Resource
    @Lazy  // 延迟加载,避免循环依赖
    private UserService userService;

    public void handlerOnAuthenticationSuccess(
            HttpServletRequest request,
            HttpServletResponse response,
            LoginUser user
    ) {
        String typeHeader = request.getHeader(Const.TYPE_HEADER);
        if ((!StringUtils.matches(typeHeader, List.of(Const.BACKEND_REQUEST, Const.FRONTEND_REQUEST)) && user.getUser().getRegisterType() == 1)) {
            throw new BadCredentialsException("非法请求");
        }
        Long id = user.getUser().getId();
        String name = user.getUser().getUsername();
        // UUID做jwt的id
        String uuid = UUID.randomUUID().toString();
        // 生成jwt
        String token = jwtUtils.createJwt(uuid, user, id, name);

        // 转换VO
        AuthorizeVO authorizeVO = user.getUser().asViewObject(AuthorizeVO.class, v -> {
            v.setToken(token);
            v.setExpire(jwtUtils.expireTime());
        });
        
        // 如果可以,尽量避免这里直接调用 UserService 的方法,改成通过事件或消息中间件等方式解耦
        userService.userLoginStatus(user.getUser().getId(), user.getUser().getRegisterType());
        
        loginLogService.loginLog(request, request.getParameter(USER_NAME), 0, RespConst.SUCCESS_LOGIN_MSG);
        WebUtil.renderString(response, ResponseResult.success(authorizeVO, RespConst.SUCCESS_LOGIN_MSG).asJsonString());
    }
}

使用 @Lazy 注解后,UserService 在初始化时不会立即注入,而是等到需要时才会被注入,从而避免了构造时的循环依赖问题。

方法 2:重构 SecurityHandlerUserService 的依赖关系

另一种方式是重构 SecurityHandlerUserService 的关系,避免它们之间直接的依赖。如果 UserServiceSecurityHandler 中有互相依赖的功能,可以考虑通过引入一个新的中间服务来拆解这两个类之间的关系。例如,将 userLoginStatus 方法移到一个新的业务类中,而不是放在 UserService 中。

@Service
public class UserStatusService {

    private final UserService userService;

    @Autowired
    public UserStatusService(UserService userService) {
        this.userService = userService;
    }

    public void updateUserLoginStatus(Long userId, int registerType) {
        // 这里执行用户登录状态更新
        userService.userLoginStatus(userId, registerType);
    }
}

@Component
@RequiredArgsConstructor
public class SecurityHandler {

    @Resource
    private JwtUtils jwtUtils;

    @Resource
    private RedisCache redisCache;

    @Resource
    private LoginLogService loginLogService;

    @Resource
    private UserStatusService userStatusService;

    public void handlerOnAuthenticationSuccess(
            HttpServletRequest request,
            HttpServletResponse response,
            LoginUser user
    ) {
        String typeHeader = request.getHeader(Const.TYPE_HEADER);
        if ((!StringUtils.matches(typeHeader, List.of(Const.BACKEND_REQUEST, Const.FRONTEND_REQUEST)) && user.getUser().getRegisterType() == 1)) {
            throw new BadCredentialsException("非法请求");
        }
        Long id = user.getUser().getId();
        String name = user.getUser().getUsername();
        // UUID做jwt的id
        String uuid = UUID.randomUUID().toString();
        // 生成jwt
        String token = jwtUtils.createJwt(uuid, user, id, name);

        // 转换VO
        AuthorizeVO authorizeVO = user.getUser().asViewObject(AuthorizeVO.class, v -> {
            v.setToken(token);
            v.setExpire(jwtUtils.expireTime());
        });

        // 使用新的 UserStatusService 来处理登录状态
        userStatusService.updateUserLoginStatus(user.getUser().getId(), user.getUser().getRegisterType());

        loginLogService.loginLog(request, request.getParameter(USER_NAME), 0, RespConst.SUCCESS_LOGIN_MSG);
        WebUtil.renderString(response, ResponseResult.success(authorizeVO, RespConst.SUCCESS_LOGIN_MSG).asJsonString());
    }
}

在这种方式下,SecurityHandler 不再直接依赖 UserService,而是依赖 UserStatusService,从而打破了循环依赖。

方法 3:使用事件驱动方式

如果 userService.userLoginStatus() 是一个异步操作或者涉及到其他业务逻辑,可以考虑使用 Spring 的 事件驱动模型 来解耦这个过程。你可以发布一个事件,并在另一个类中监听这个事件来执行相关逻辑。这样可以进一步解耦类与类之间的依赖关系。

总结:

  • 使用 @Lazy 注解:最简单的一种解决循环依赖的方法,适用于延迟加载的场景。
  • 重构依赖关系:将一个类中的某些依赖移到其他服务中,避免直接的循环依赖。
  • 事件驱动:如果操作是异步的,可以考虑通过发布/订阅机制来解耦操作。
posted @ 2024-11-12 23:01  258333  阅读(2)  评论(0编辑  收藏  举报