记一次SpringCloud OpenFeign 服务调用传递 token @Async 上下文信息获取失败

一、场景

在异步方法中使用了feign调用,发现提示"您还未登录或登录已失效"。那原因很明了就是我的登录信息没办法传入到feign的调用方法里。

二、考虑的解决办法

1)尝试一:ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();

java 复制代码
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
sendAsyncService.statisticsSendSms(idList, userTokenValue, sendSMSNotificationParams, coreDescriptionParams, attributes);
java 复制代码
public void statisticsSendSms(List<Long> reportIdList,
	                              UserTokenValue userTokenValue,
	                              SendSMSNotificationParams sendSMSNotificationParams,
	                              CoreDescriptionParams coreDescriptionParams,
	                              ServletRequestAttributes attributes) {
		// 设置子线程共享
		RequestContextHolder.setRequestAttributes(attributes, true);
		//figen 调用,需要token
		ResponseResult<String> responseResult = smsApi.sendSMSNotification(sendSMSNotificationParams);

这种情况犹豫反复在测试环境测试,都无任何问题,心里自信满满,但是上线后,却暴漏了,依旧提示"您还未登录或登录已失效"。这是什么情况?

1-1)RequestContextHolder跨线程获取不到requests请求对象的原因

为何会失败呢?因为异步注解,顾名思义,是开启了一个新的线程去执行一些代码。

java 复制代码
/**
 * 给当前线程绑定属性
 * @param inheritable 是否要将属性暴露给子线程
 * */
public static void setRequestAttributes(@Nullable RequestAttributes attributes, boolean inheritable) {
		if (attributes == null) {
			resetRequestAttributes();
		}
		else {
			if (inheritable) {
				inheritableRequestAttributesHolder.set(attributes);
				requestAttributesHolder.remove();
			}
			else {
				requestAttributesHolder.set(attributes);
				inheritableRequestAttributesHolder.remove();
			}
		}
	}

那源代码理解,已经将属性暴漏给子线程了,为何上线后依旧是获取不到呢?那其实从根本思考,主线程加入相应速度快于子线程呢,结果会是如何?结果就是主线程结束,所有的信息都已经销毁了,谁还管你子线程是否执行没执行呢,哈哈哈。测试环境未复现,是因为环境配置低,可能测试的几次都一定概率的主线程相应时间大于了子线程的相应时间,到了线上,由于高配置的环境,这种平衡性被打破了,主线程先行一步了。

缘由了解了,解决的办法也就来了----肯定就是让主线程等一下,等子线程执行完再结束,可是主线程会那么礼貌吗?该如何解决这个问题呢?

2)自定义异步线程池,配置中做相关处理

java 复制代码
// 解决使用@Async注解,获取不到上下文信息的问题
        executor.setTaskDecorator(runnable -> {

            ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
            AsyncContext asyncContext;
            if (attributes!=null){
                HttpServletRequest request=attributes.getRequest();
                asyncContext=request.startAsync();
            } else {
                asyncContext = null;
            }
            return ()->{
                try {
                    // 我们set 进去 ,其实是一个ThreadLocal维护的.
                    RequestContextHolder.setRequestAttributes(attributes);
                    runnable.run();
                }finally {
                    RequestContextHolder.resetRequestAttributes();
                    if (asyncContext!=null){
                        asyncContext.complete();
                    }
                }

            };
        });

2-1)Servlet的异步处理

是由AsyncContext接口来实现的。

java 复制代码
AsyncContext asyncContext;
            if (attributes!=null){
                HttpServletRequest request=attributes.getRequest();
                asyncContext=request.startAsync();
            } else {
                asyncContext = null;
            }

2-3)异步注解使用了自定义线程池,避免一个方法多个异步注解使用

java 复制代码
@Async("asyncServiceExecutor")
java 复制代码
AsyncContext asyncContext;
            if (attributes!=null){
                HttpServletRequest request=attributes.getRequest();
                asyncContext=request.startAsync();
            } else {
                asyncContext = null;
            }

由于线程池中我们使用了Servlet的异步处理来 set我们需要的token信息,所以避免重复调用。多次调用会报错,即使不影响信息获取。

在Servlet中多次调用自定义线程池可能导致错误,这通常是因为Servlet的单实例模式导致的线程安全问题。Servlet容器通常会为每个Servlet维护一个实例,当多个请求同时访问同一个Servlet实例时,可能会出现竞态条件,比如多个线程尝试使用相同的资源或状态。

相关推荐
橙子家21 分钟前
使用方法 ToDictionary() 来优化查询时间复杂度:O(N*M) -> O(1*M)【C# 基础】
后端
IT_陈寒1 小时前
我又被JavaScript的隐式类型转换坑了
前端·人工智能·后端
用户8356290780511 小时前
Python Word 转 PDF 和 PDF 转 Word 指南
后端·python
用户8356290780511 小时前
如何使用 Python 加密和保护 Word 文档
后端·python
他们叫我秃子2 小时前
前端开发转 Go 全栈(五):终于遇到熟人了,Go 的闭包和高阶函数原来这么像 JavaScript
前端·后端·go
SomeB1oody2 小时前
【RustyML入门】2.6. 线性判别分析
开发语言·后端·机器学习·rust·教程
妙码生花2 小时前
从 PHP 到 AI + Golang,程序员自救转型手记(五十七):AI时代对组件封装的新理解、agInput 统一入口,动态表单预备
前端·后端·go
Yao8062 小时前
SpringAI + Ollama + DeepSeek + RAG 搭建 AI 航线规划系统
后端
大勇前进2 小时前
SQL Server 分页查询多种写法对比:哪种性能最高?
后端
妙码生花2 小时前
从 PHP 到 AI + Golang,程序员自救转型手记(五十六):附件管理、增加根据文件后缀生成 SVG 文件图标的接口
前端·后端·go