Skip to content

EasyExcel & cn.dev33.satoken.exception.NotWebContextException

🏷️ Spring Boot EasyExcel

使用 RuoYi-Vue-Plus 框架,在生成 Excel 的方法上添加了 @Async 注解,且数据模型的字段上包含 @ExcelDictFormat(dictType = "mission_type") 注解时,导出 Excel 时报错:

java
Caused by: cn.dev33.satoken.exception.NotWebContextException: 非 web 上下文无法获取 HttpServletRequest
	at cn.dev33.satoken.spring.SpringMVCUtil.getRequest(SpringMVCUtil.java:44)
	at cn.dev33.satoken.spring.SaTokenContextForSpringInJakartaServlet.getStorage(SaTokenContextForSpringInJakartaServlet.java:56)
	at cn.dev33.satoken.context.SaHolder.getStorage(SaHolder.java:69)
	at ....service.impl.SysDictTypeServiceImpl.getDictLabel(SysDictTypeServiceImpl.java:224)

原因

@ExcelDictFormat 注解中如果指定了 dictType 属性,则会通过 SysDictTypeServiceImplgetDictLabel() 方法获取字典信息,其中会优先从 SaHolder.getStorage() 中获取缓存的字典数据,而 SaHolder.getStorage() 方法需要访问当前 Web 请求的上下文。

由于使用 @Async 将处理改为异步,线程发生了切换,导致 SaHolder.getStorage() 获取不到 Web 请求上下文。

建议

这里如果改成本地缓存就可以避免这个问题了。
另外如果考虑字典数据的时效性,可以将本地缓存的时间设置的短一些。

解决方案

ServletUtils.getRequestAttributes 方法中添加 RequestContextHolder.setRequestAttributes(attributes, true); 处理 [1]

java
public static ServletRequestAttributes getRequestAttributes() {
    try {
        RequestAttributes attributes = RequestContextHolder.getRequestAttributes();
        RequestContextHolder.setRequestAttributes(attributes, true);
        return (ServletRequestAttributes) attributes;
    } catch (Exception e) {
        return null;
    }
}

RequestContextHolder.setRequestAttributes(attributes, true); 方法中,第二个参数为 true 时,会将 attibutes 放入 NamedInheritableThreadLocal 类型的 ThreadLocal 中,而 NamedInheritableThreadLocal 继承自 InheritableThreadLocal,所以 NamedInheritableThreadLocal 中的数据会随着线程切换而传递到子线程中。


  1. RequestContextHolder 跨线程获取不到 requests 请求对象的解决方法 ↩︎