使用ThreadLocal与RequestAttribute跨应用层访问userId

问题描述 投票:0回答:1

我试图在 Spring MVC 应用程序中跨不同层(控制器、服务、存储库)访问参数的两种方法之间做出决定。例如:

方法一:使用ThreadLocal

public class UserContext {
    private static final ThreadLocal<Long> userIdHolder = new ThreadLocal<>();
    
    public static void setUserId(Long userId) {
        userIdHolder.set(userId);
    }
    
    public static Long getUserId() {
        return userIdHolder.get();
    }
    
    public static void clear() {
        userIdHolder.remove();
    }
}

@Component
public class AuthInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
        Long userId = // get userId from token or somewhere
        UserContext.setUserId(userId);
        return true;
    }
    
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
        UserContext.clear();
    }
}

@RestController
public class UserController {
    @Autowired
    private UserService userService;

    @GetMapping("/user/info")
    public String getUserInfo() {
        return userService.getUserInfo();
    }
}

@Service
public class UserService {
    @Autowired
    private UserRepository userRepository;

    public String getUserInfo() {
        Long userId = UserContext.getUserId(); // Can access userId directly
        return userRepository.findUserInfo(userId);
    }
}

方法2:使用@RequestAttribute

@Component
public class AuthInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
        Long userId = // get userId from token or somewhere
        request.setAttribute("userId", userId);
        return true;
    }
}

@RestController
public class UserController {
    @Autowired
    private UserService userService;

    @GetMapping("/user/info")
    public String getUserInfo(@RequestAttribute("userId") Long userId) {
        return userService.getUserInfo(userId);
    }
}

@Service
public class UserService {
    @Autowired
    private UserRepository userRepository;

    public String getUserInfo(Long userId) { // Need to pass userId as parameter
        return userRepository.findUserInfo(userId);
    }
}

我的问题

哪种方法被认为是 Spring 应用程序中的最佳实践?我特别感兴趣的是:

  1. 经验丰富的 Spring 开发人员通常会选择什么?
  2. 有官方的 Spring 推荐吗?
  3. 是否有任何特定场景表明一种方法明显优于另一种方法?

这个问题更侧重于寻求最佳实践和建议,而不仅仅是比较技术差异。

java spring-boot spring-mvc thread-local
1个回答
0
投票

我建议第三种方法,它适用于 Spring MVC(不适用于反应式堆栈),但以更干净的方式包装第一个

ThreadLocal

您可以定义一个holder

UserIdHolder
类以及将其定义为请求范围bean的配置,如下所示:

public class UserIdHolder {
  private Long userId;
  // Getters, setters
}

@Configuration
public class UserIdHolderConfiguration {

  @Bean
  @Scope("request")
  UserIdHolder userIdHolder() {
    return new UserIdHolder;
  }
}

然后你可以通过

@Autowired
注解将其注入到拦截器和你需要的每个 bean 中; Spring 将管理此 bean 的实例,使其每次为任何请求创建,并在运行下游控制器/服务/存储库 bean 时引用正确的实例。

@Component
public class AuthInterceptor implements HandlerInterceptor {

  @Autowired
  private UserIdHolder userIdHolder;

  @Override
  public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
    Long userId = // get userId from token or somewhere
    userIdHolder.setUserId(userId);
    return true;
  }
}

等等。

使用这个,你不必在每个请求结束时手动清理本地线程,因为它都是由 Spring 管理的,并且你不必手动将此属性作为参数传递给每个方法或在控制器签名。

最新问题
© www.soinside.com 2019 - 2025. All rights reserved.