2024.11.12
主要问题:
SecurityHandler
在 handlerOnAuthenticationSuccess
方法中调用了 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:重构 SecurityHandler
和 UserService
的依赖关系
另一种方式是重构 SecurityHandler
和 UserService
的关系,避免它们之间直接的依赖。如果 UserService
和 SecurityHandler
中有互相依赖的功能,可以考虑通过引入一个新的中间服务来拆解这两个类之间的关系。例如,将 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
注解:最简单的一种解决循环依赖的方法,适用于延迟加载的场景。 - 重构依赖关系:将一个类中的某些依赖移到其他服务中,避免直接的循环依赖。
- 事件驱动:如果操作是异步的,可以考虑通过发布/订阅机制来解耦操作。