异步请求时,主线程结束请求被销毁,调用feign接口失败

总结:异步操作时,因主线程结束,请求被销毁,调用feign接口而导致的几个问题。经过查找网上的资料,发现这个问题也出现在定时任务调用feign上,基本上网大佬总结的方法我都试过了,但是多数大佬的业务场景是主线程并没有在异步线程结束前被销毁,因此他们可以直接开启线程请求共享就好了。

   尝试多总方法后,提炼出两种可以通用的方法

1. 继承RequestAttributes类,在子线程中new出来设置到子线程的RequestAttributes,然后创建一个空的SecurityContext,将主线程的context赋值进去

2.请求进来后强制返回响应,后面的代码继续执行(不推荐)

3.定时任务的问题需要重写apply方法

以下是具体过程:

----碰到个需求需要异步执行,直接

@Async注解整上,霹雳吧啦一顿操作,postman发送请求,舒服!

恩??经典空指针??就知道没这么简单。

 

定位报错代码 

//获取当前登录用户信息
User user = SecurityUtils.getUser();

   经过查找资料一顿分析,应该是主线程结束后,请求被销毁,导致获取不到用户信息。

  好了知道问题在哪里就好解决了,SecurityContext跟随主线程请求一起被销毁了,参考网上各位大佬的办法,那就把SecurityContext的值从主线程传递给子线程

//从主线程获取SecurityContext 
final SecurityContext context = SecurityContextHolder.getContext();


//在子线程中设置SecurityContext 
SecurityContextHolder.setContext(context );

再次调试发现异步线程可以获取到当前用户信息了,本以为万事大吉了,结果控制台又爆红啦!!!

 com.netflix.hystrix.exception.HystrixRuntimeException: Remotexxxxxxx#getxxxxxxx(Integer,String,String,String) failed and fallback failed.
    at com.netflix.hystrix.AbstractCommand$22.call(AbstractCommand.java:832)
    at com.netflix.hystrix.AbstractCommand$22.call(AbstractCommand.java:807)

Caused by: java.lang.IllegalStateException: No thread-bound request found: Are you referring to request attributes outside of an actual web request, or processing a request outside of the originally receiving thread? If you are actually operating within a web request and still receive this message, your code is probably running outside of DispatcherServlet: In this case, use RequestContextListener or RequestContextFilter to expose the current request.
    at org.springframework.web.context.request.RequestContextHolder.currentRequestAttributes(RequestContextHolder.java:131)
    at org.springframework.web.context.request.AbstractRequestAttributesScope.get(AbstractRequestAttributesScope.java:42)
    at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:353)
    ... 80 more

Error creating bean with name 'scopedTarget.oauth2ClientContext': Scope 'request' is not active for the current thread; consider defining a scoped proxy for this bean if you intend to refer to it from a singleton; nested exception is java.lang.IllegalStateException: No thread-bound request found: Are you referring to request attributes outside of an actual web request, or processing a request outside of the originally receiving thread? If you are actually operating within a web request and still receive this message, your code is probably running outside of DispatcherServlet: In this case, use RequestContextListener or RequestContextFilter to expose the current request.

  由于异步线程中有调用另外一个微服务的方法所以调用了feign,根据控制台的提示,发现是在构造请求时,在还原token的方法中引用了非本次请求的变量(大概是这个意思),所以报错了,好吧,再上csdn查查有没有前辈(倒霉蛋)也遇到过这个问题,没有。。。。

     好吧只能自力更生了,既然主线程的请求都被销毁了不能用了 ,那我就再自己创建一个,在赋值不就得了,ok,曙光就在眼前!!

accessTokenContextRelay.copyToken();

 在异步线程中创建一个空的SecurityContext,然后再把主线程的context赋值进去,完美!

    SecurityContext emptyContext = SecurityContextHolder.createEmptyContext();
	emptyContext.setAuthentication(context.getAuthentication());
	SecurityContextHolder.setContext(emptyContext);
这个就不用了
//		SecurityContextHolder.setContext(context);

。。。。发现还是报错,推测是主线程被销毁了的原因,那就再整个请求,这是我参考别的大佬的代码

public class FeignRequestScopeAttr implements RequestAttributes {
	private Map<String, Object> requestAttributeMap = new HashMap<>();
	@Override
	public Object getAttribute(String name, int scope) {
		if(scope== RequestAttributes.SCOPE_REQUEST) {
			return this.requestAttributeMap.get(name);
		}
		return null;
	}
	@Override
	public void setAttribute(String name, Object value, int scope) {
		if(scope== RequestAttributes.SCOPE_REQUEST){
			this.requestAttributeMap.put(name, value);
		}
	}
	@Override
	public void removeAttribute(String name, int scope) {
		if(scope== RequestAttributes.SCOPE_REQUEST) {
			this.requestAttributeMap.remove(name);
		}
	}
	@Override
	public String[] getAttributeNames(int scope) {
		if(scope== RequestAttributes.SCOPE_REQUEST) {
			return this.requestAttributeMap.keySet().toArray(new String[0]);
		}
		return  new String[0];
	}
	@Override
	public void registerDestructionCallback(String name, Runnable callback, int scope) {
		// Not Supported
	}
	@Override
	public Object resolveReference(String key) {
		// Not supported
		return null;
	}
	@Override
	public String getSessionId() {
		return null;
	}
	@Override
	public Object getSessionMutex() {
		return null;
	}
}
//异步线程新设置一个请求 
 RequestContextHolder.setRequestAttributes(new FeignRequestScopeAttr());
		
SecurityContext emptyContext = SecurityContextHolder.createEmptyContext();
		emptyContext.setAuthentication(context.getAuthentication());
		SecurityContextHolder.setContext(emptyContext);

至此问题解决。

再记录下另一个方法

	AsyncContext  asyncContext = request.startAsync();
	HttpServletResponse response = WebUtils.getResponse();
	response.setHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE);
		response.setHeader(HttpHeaders.CONNECTION,"close");
		PrintWriter out = response.getWriter();
		out.print(RVo.ok());//设置响应报文
		out.close();//强制返回
    //已经返回给前端,但下面的代码还能执行
   //继续执行业务代码。。。

另外可以将请求头放在新的请求中。

感谢各位大佬指点!

  • 4
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值