EasyExcel & cn.dev33.satoken.exception.NotWebContextException
使用 RuoYi-Vue-Plus 框架,在生成 Excel 的方法上添加了 @Async
注解,且数据模型的字段上包含 @ExcelDictFormat(dictType = "mission_type")
注解时,导出 Excel 时报错:
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
属性,则会通过 SysDictTypeServiceImpl
的 getDictLabel()
方法获取字典信息,其中会优先从 SaHolder.getStorage()
中获取缓存的字典数据,而 SaHolder.getStorage()
方法需要访问当前 Web 请求的上下文。
由于使用 @Async
将处理改为异步,线程发生了切换,导致 SaHolder.getStorage()
获取不到 Web 请求上下文。
建议
这里如果改成本地缓存就可以避免这个问题了。
另外如果考虑字典数据的时效性,可以将本地缓存的时间设置的短一些。
解决方案
在 ServletUtils.getRequestAttributes
方法中添加 RequestContextHolder.setRequestAttributes(attributes, true);
处理 [1]。
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
中的数据会随着线程切换而传递到子线程中。