Feign異步模式丟失上下文問題
問題描述
當我們使用異步對我們代碼進行操作優化時,代碼中使用了RequestContextHolder去獲取上下文的數據,當我們執行原來可以執行的業務時發現報了空指針異常或數據為空,這是為什么呢?
原理解釋
通過源碼我們可以看出來,RequestContextHolder本質的是使用了ThreadLocal作為上下文的實現方式,但ThreadLocal只在自己線程中才可以讀取到數據,但我們開啟了異步線程,導致數據在不同的線程中為空,那我們怎么解決呢?
解決方法
1、在主線程中讀取出主線程的數據,通過RequestContextHolder將數據注入到子線程中即可解決此問題
@Overridepublic OrderConfirmVo confirmOrder() throws ExecutionException, InterruptedException {OrderConfirmVo confirmVo = new OrderConfirmVo();MemberLoginTo memberLoginTo = loginToThreadLocal.get();System.out.println("主線程的id:"+Thread.currentThread().getId());// 主線程的threadLocal數據,注意threadLocal中的數據只是在本線程中生效,若啟用異步線程則會出現線程讀取不到數據的問題RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();// 1、遠程查詢所有的收貨地址的列表CompletableFuture<Void> addressFuture = CompletableFuture.runAsync(() -> {// 將主線程的threadLocal共享到子線程,避免出現讀取不到子線程上下文數據的問題System.out.println("當前線程的id:"+Thread.currentThread().getId());RequestContextHolder.setRequestAttributes(requestAttributes);List<MemberAddressVo> address = memberFeignService.getAddress(memberLoginTo.getId());confirmVo.setAddress(address);}, executor);CompletableFuture<Void> CartItemFuture = CompletableFuture.runAsync(() -> {// 2、遠程查詢購物車的購物項列表// 將主線程的threadLocal共享到子線程,避免出現讀取不到子線程上下文數據的問題System.out.println("當前線程的id:"+Thread.currentThread().getId());RequestContextHolder.setRequestAttributes(requestAttributes);List<OrderItemVo> userCartItems = cartFeignService.getUserCartItems();confirmVo.setOrderItems(userCartItems);// feign 在遠程調用之前要構造請求,會調用很多的攔截器}, executor);return confirmVo;}
2、在主線程中使用RequestContextHolder.setRequestAttributes(requestAttributes,true);
將主線程的數據共享即可解決此問題
@Overridepublic OrderConfirmVo confirmOrder() throws ExecutionException, InterruptedException {OrderConfirmVo confirmVo = new OrderConfirmVo();MemberLoginTo memberLoginTo = loginToThreadLocal.get();System.out.println("主線程的id:"+Thread.currentThread().getId());// 主線程的threadLocal數據,注意threadLocal中的數據只是在本線程中生效,若啟用異步線程則會出現線程讀取不到數據的問題RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();// 開啟線程數據共享RequestContextHolder.setRequestAttributes(requestAttributes,true);// 1、遠程查詢所有的收貨地址的列表CompletableFuture<Void> addressFuture = CompletableFuture.runAsync(() -> {// 將主線程的threadLocal共享到子線程,避免出現讀取不到子線程上下文數據的問題System.out.println("當前線程的id:"+Thread.currentThread().getId());
// RequestContextHolder.setRequestAttributes(requestAttributes);List<MemberAddressVo> address = memberFeignService.getAddress(memberLoginTo.getId());confirmVo.setAddress(address);}, executor);CompletableFuture<Void> CartItemFuture = CompletableFuture.runAsync(() -> {// 2、遠程查詢購物車的購物項列表// 將主線程的threadLocal共享到子線程,避免出現讀取不到子線程上下文數據的問題System.out.println("當前線程的id:"+Thread.currentThread().getId());
// RequestContextHolder.setRequestAttributes(requestAttributes);List<OrderItemVo> userCartItems = cartFeignService.getUserCartItems();confirmVo.setOrderItems(userCartItems);// feign 在遠程調用之前要構造請求,會調用很多的攔截器}, executor);return confirmVo;}
要構造請求,會調用很多的攔截器
}, executor);
return confirmVo;
}