背景

有个接口一直无法通过验证。

跟踪了下,发现是在获取用户ID的时候,直接抛了异常。

使用的是sa-token

代码如下:

 public Integer getUserId{
     if (StpUtil.isLogin()) {
        ...
         return Integer.parseInt(StpUtil.getLoginId().toString());
      } 
       ...
}

跟踪代码的时候,在StpUtil.isLogin()这里抛出了SaTokenException的问题。

源码分析

跟踪了下代码,出错代码定位如下:

public class SpringMVCUtil {
	
	/**
	 * 获取当前会话的 request 
	 * @return request
	 */
	public static HttpServletRequest getRequest() {
		ServletRequestAttributes servletRequestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
		if(servletRequestAttributes == null) {
			throw new SaTokenException("非Web上下文无法获取Request");
		}
		return servletRequestAttributes.getRequest();
	}
	
	/**
	 * 获取当前会话的 response
	 * @return response
	 */
	public static HttpServletResponse getResponse() {
		ServletRequestAttributes servletRequestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
		if(servletRequestAttributes == null) {
			throw new SaTokenException("非Web上下文无法获取Response");
		}
		return servletRequestAttributes.getResponse();
	}

}

我们发现,因为无法获取到ServletRequestAttributes对象。所以报错了。

 ServletRequestAttributes对象获取不到的情况一般分为以下几种情况:

1、异步线程调用。例如@Async注解的情况

2、子线程调用的情况。由于RequestContextHolder使用ThreadLocal共享数据,子线程会导致丢掉ThreadLocal中原来线程的数据。

3、非Web上下文的情况下调用。例如rpc框架这种。

查看了下代码,发现是使用了CompletableFuture的子线程方式,导致在RequestContextHolder中获取不到对象。

解决方法

解决方法比较简单:

1、参数透传

修改接口,把需要在ThreadLocal中获取的参数透传到其它方法中。

2、重置上下文对象

在子线程中重置RequestContextHolder对象。

// 从主线程获取用户数据 放到局部变量中
RequestAttributes attributes = RequestContextHolder.getRequestAttributes();
CompletableFuture<Void> testFuture = CompletableFuture.runAsync(() -> {
    // 把旧RequestAttributes放到新线程的RequestContextHolder中
    RequestContextHolder.setRequestAttributes(attributes);
    ...
    }

 使用方式1 测试了下,发现问题圆满解决。

总结

来张网络图片

 因为异步的原因,他会丢掉ThreadLocal中原来线程的数据,从而获取不到loginUser,这种情况下我们可以在方法内的局部变量中先保存原来线程的信息,在异步编排的新线程中拿着局部变量的值重新设置到新线程中即可。

Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐