Fegin异步情况下丢失上下文问题
问题:
Order服务需要远程调用member服务进而查询所有的地址列表,也需要远程调用cart服务进而查询购物车所有选中的购物项,串行执行远程调用会浪费大量时间,因此我们进行异步编排优化
@Override
public OrderConfirmVo confirmOrder() throws ExecutionException, InterruptedException {
OrderConfirmVo confirmVo = new OrderConfirmVo();
MemberRespVo memberRespVo = LoginUserInterceptor.loginUser.get();
// 1、远程查询所有的地址列表
CompletableFuture<Void> getAddressFuture = CompletableFuture.runAsync(() -> {
List<MemberAddressVo> address = memberFeignService.getAddress(memberRespVo.getId());
confirmVo.setAddressVos(address);
}, executor);
// 2、远程查询购物车所有选中的购物项
CompletableFuture<Void> cartFuture = CompletableFuture.runAsync(() -> {
List<OrderItemVo> items = cartFeignService.getCurrentUserCartItems();
confirmVo.setItems(items);
}, executor);
// feign在远程调用请求之前要构造
// 3、查询用户积分
Integer integration = memberRespVo.getIntegration();
confirmVo.setIntegration(integration);
// 4、其他数据自动计算
// TODO 5、防重令牌
CompletableFuture.allOf(getAddressFuture,cartFuture).get();
return confirmVo;
}
开启异步时获取不到老请求的信息即拦截器中会发生空指针的错误,即老request对象获取不到,是null。
@Configuration
public class GulimallFeignConfig {
/**
* feign在远程调用之前会执行所有的RequestInterceptor拦截器
*
* @return
*/
@Bean("requestInterceptor")
public RequestInterceptor requestInterceptor() {
return new RequestInterceptor() {
@Override
public void apply(RequestTemplate requestTemplate) {
// 1、使用 RequestContextHolder 拿到请求数据,RequestContextHolder底层使用过线程共享数据 ThreadLocal<RequestAttributes>
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletRequest request = attributes.getRequest();
// 2、同步请求头数据,Cookie
String cookie = request.getHeader("Cookie");
// 给新请求同步了老请求的cookie
requestTemplate.header("Cookie", cookie);
}
};
}
}
原因:
@Configuration
public class GulimallFeignConfig {
/**
* feign在远程调用之前会执行所有的RequestInterceptor拦截器
*
* @return
*/
@Bean("requestInterceptor")
public RequestInterceptor requestInterceptor() {
return new RequestInterceptor() {
@Override
public void apply(RequestTemplate requestTemplate) {
// 1、使用 RequestContextHolder 拿到请求数据,RequestContextHolder底层使用过线程共享数据 ThreadLocal<RequestAttributes>
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
System.out.println("RequestInterceptor线程...."+Thread.currentThread().getId());
if (attributes != null) {
HttpServletRequest request = attributes.getRequest();
// 2、同步请求头数据,Cookie
String cookie = request.getHeader("Cookie");
// 给新请求同步了老请求的cookie
requestTemplate.header("Cookie", cookie);
}
}
};
}
}
@Override
public OrderConfirmVo confirmOrder() throws ExecutionException, InterruptedException {
OrderConfirmVo confirmVo = new OrderConfirmVo();
MemberRespVo memberRespVo = LoginUserInterceptor.loginUser.get();
System.out.println("主线程线程...."+Thread.currentThread().getId());
// 1、远程查询所有的地址列表
CompletableFuture<Void> getAddressFuture = CompletableFuture.runAsync(() -> {
System.out.println("member线程...."+Thread.currentThread().getId());
List<MemberAddressVo> address = memberFeignService.getAddress(memberRespVo.getId());
confirmVo.setAddressVos(address);
}, executor);
// 2、远程查询购物车所有选中的购物项
CompletableFuture<Void> cartFuture = CompletableFuture.runAsync(() -> {
System.out.println("cart线程...."+Thread.currentThread().getId());
List<OrderItemVo> items = cartFeignService.getCurrentUserCartItems();
confirmVo.setItems(items);
}, executor);
// feign在远程调用请求之前要构造
// 3、查询用户积分
Integer integration = memberRespVo.getIntegration();
confirmVo.setIntegration(integration);
// 4、其他数据自动计算
// TODO 5、防重令牌
CompletableFuture.allOf(getAddressFuture,cartFuture).get();
return confirmVo;
}
测试打印线程:
我们给feign的拦截器所拦截的请求加上了请求头,但这个获取请求头的过程是在一个线程执行过程中进行的,但是由于 RequestContextHolder底层使用的是线程共享数据 ThreadLocal<RequestAttributes>,线程共享数据的域是当前线程下,线程之间是不共享的。
解决:
向 RequestContextHolder线程域中放主线程的请求域。
即先在主线程中获取到请求头相关信息(RequestContextHolder.getRequestAttributes();),然后在异步调用的过程中将情求头加到正在执行异步操作的线程中( RequestContextHolder.setRequestAttributes(requestAttributes);),这样请求头就不会丢失。
@Override
public OrderConfirmVo confirmOrder() throws ExecutionException, InterruptedException {
OrderConfirmVo confirmVo = new OrderConfirmVo();
MemberRespVo memberRespVo = LoginUserInterceptor.loginUser.get();
// 获取主线程的域,主线程中获取请求头信息
RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
// 1、远程查询所有的地址列表
CompletableFuture<Void> getAddressFuture = CompletableFuture.runAsync(() -> {
RequestContextHolder.setRequestAttributes(requestAttributes);
// 将主线程的域放在该线程的域中
List<MemberAddressVo> address = memberFeignService.getAddress(memberRespVo.getId());
confirmVo.setAddressVos(address);
}, executor);
// 2、远程查询购物车所有选中的购物项
CompletableFuture<Void> cartFuture = CompletableFuture.runAsync(() -> {
// 将老请求的域放在该线程的域中
RequestContextHolder.setRequestAttributes(requestAttributes);
List<OrderItemVo> items = cartFeignService.getCurrentUserCartItems();
confirmVo.setItems(items);
}, executor);
// feign在远程调用请求之前要构造
// 3、查询用户积分
Integer integration = memberRespVo.getIntegration();
confirmVo.setIntegration(integration);
// 4、其他数据自动计算
// TODO 5、防重令牌
CompletableFuture.allOf(getAddressFuture,cartFuture).get();
return confirmVo;
}
虽然都是同一个 RequestContextHolder类在调用方法,但是 RequestContextHolder在不同线程中的意义是不同的,它仅代表当前线程。
@Configuration
public class GulimallFeignConfig {
/**
* feign在远程调用之前会执行所有的RequestInterceptor拦截器
*
* @return
*/
@Bean("requestInterceptor")
public RequestInterceptor requestInterceptor() {
return new RequestInterceptor() {
@Override
public void apply(RequestTemplate requestTemplate) {
// 1、使用 RequestContextHolder 拿到请求数据,RequestContextHolder底层使用过线程共享数据 ThreadLocal<RequestAttributes>
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
if (attributes != null) {
HttpServletRequest request = attributes.getRequest();
// 2、同步请求头数据,Cookie
String cookie = request.getHeader("Cookie");
// 给新请求同步了老请求的cookie
requestTemplate.header("Cookie", cookie);
}
}
};
}
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY