主要问题:
SecurityHandler
在 handlerOnAuthenticationSuccess
方法中调用了 userService.userLoginStatus
,这会导致 UserService
需要依赖 SecurityHandler
。这种双向依赖形成了一个循环依赖,Spring 容器无法正确处理这个循环,导致应用启动失败。
解决思路:
1. 避免循环依赖:
通过重构代码,消除这种循环依赖。常见的做法是将其中某个依赖提取出来,或改变它们的关系。以下是几种可能的解决方法:
方法 1:延迟加载 UserService
如果 UserService
仅在 SecurityHandler
中的某些方法中使用,你可以使用 @Lazy
注解来延迟加载 UserService
,从而避免循环依赖。
代码修改如下:
@Component
@RequiredArgsConstructor
public class SecurityHandler {@Resourceprivate JwtUtils jwtUtils;@Resourceprivate RedisCache redisCache;@Resourceprivate 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的idString uuid = UUID.randomUUID().toString();// 生成jwtString token = jwtUtils.createJwt(uuid, user, id, name);// 转换VOAuthorizeVO 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:重构 SecurityHandler
和 UserService
的依赖关系
另一种方式是重构 SecurityHandler
和 UserService
的关系,避免它们之间直接的依赖。如果 UserService
和 SecurityHandler
中有互相依赖的功能,可以考虑通过引入一个新的中间服务来拆解这两个类之间的关系。例如,将 userLoginStatus
方法移到一个新的业务类中,而不是放在 UserService
中。
@Service
public class UserStatusService {private final UserService userService;@Autowiredpublic UserStatusService(UserService userService) {this.userService = userService;}public void updateUserLoginStatus(Long userId, int registerType) {// 这里执行用户登录状态更新userService.userLoginStatus(userId, registerType);}
}@Component
@RequiredArgsConstructor
public class SecurityHandler {@Resourceprivate JwtUtils jwtUtils;@Resourceprivate RedisCache redisCache;@Resourceprivate LoginLogService loginLogService;@Resourceprivate 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的idString uuid = UUID.randomUUID().toString();// 生成jwtString token = jwtUtils.createJwt(uuid, user, id, name);// 转换VOAuthorizeVO 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
注解:最简单的一种解决循环依赖的方法,适用于延迟加载的场景。 - 重构依赖关系:将一个类中的某些依赖移到其他服务中,避免直接的循环依赖。
- 事件驱动:如果操作是异步的,可以考虑通过发布/订阅机制来解耦操作。