记录一些遇见的bug——记录一个使用多线程异步调用openfeign时子线程丢失request请求头导致的空指针异常错误
1.出现问题的业务逻辑
List<MyData> demo(){
//异步往CompletableFuture中存数据
List<CompletableFuture<MyData>> futures = new ArrayList<>();
for (int i = 0; i < 10; i++) {
//开启10个线程异步调用openfeign请求,往CompletableFuture中存方法返回的数据
futures.add(this.loadData(args));
}
//从CompletableFuture中取数据
List<MyData> myDataList = new ArrayList<>();
for (CompletableFuture<MyData> future : futures) {
MyData myData = future.get();
if (myData == null) {
continue;
}
myDataList.add(myData);
}
return myDataList;
}
CompletableFuture<MyData> loadData(String args){
return CompletableFuture.supplyAsync(() -> {
//异步调用openfeign请求获取数据
MyData myData = myGetDataClient.getData(args);
return myData;
});
}
2.错误日志
Caused by: java.lang.NullPointerException: null
at com.flybees.config.FeignConfig.apply(FeignConfig.java:78)
at feign.SynchronousMethodHandler.targetRequest(SynchronousMethodHandler.java:176)
at feign.SynchronousMethodHandler.executeAndDecode(SynchronousMethodHandler.java:101)
at feign.SynchronousMethodHandler.invoke(SynchronousMethodHandler.java:80)
at feign.hystrix.HystrixInvocationHandler$1.run(HystrixInvocationHandler.java:109)
at com.netflix.hystrix.HystrixCommand$2.call(HystrixCommand.java:302)
at com.netflix.hystrix.HystrixCommand$2.call(HystrixCommand.java:298)
at rx.internal.operators.OnSubscribeDefer.call(OnSubscribeDefer.java:46)
... 67 common frames omitted
3.原因分析
查看feign配置FeignConfig.java
获取request对象的请求头时报了空指针异常,意思就是主线程中的请求头并没有带过来
@Configuration
public class FeignConfig implements RequestInterceptor {
@Override
public void apply(RequestTemplate requestTemplate) {
/*
* 获取原线程的request对象的请求头中的token
* RequestContextHolder.getRequestAttributes():获取request原始的请求头对象
* 接口类RequestAttributes不能使用,所以强转为ServletRequestAttributes类型
*/
ServletRequestAttributes servletRequestAttributes =
(ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
//获取原Request对象
HttpServletRequest request = servletRequestAttributes.getRequest();
//把原request的请求头的所有参数都拿出来
Enumeration<String> headerNames = request.getHeaderNames();
while (headerNames.hasMoreElements()) {
//获取每个请求头参数的名字
String name = headerNames.nextElement();
//获取值
String value = request.getHeader(name);
// 跳过 content-length,用于解决Caused by: feign.RetryableException: too many bytes written executing POST http://xx报错
if (name.equalsIgnoreCase("content-length")){
continue;
}
//放到feign调用对象的request中去
requestTemplate.header(name, value);
}
}
}
4.解决方案
在主线程里调用子线程时将request传递过去,并设置子线程的请求头
List<MyData> demo(String args){
//异步往CompletableFuture中存数据
List<CompletableFuture<MyData>> futures = new ArrayList<>();
//获取当前线程(主线程)请求头对象(增)
RequestAttributes requestAttributes = RequestContextHolder.currentRequestAttributes();
for (int i = 0; i < 10; i++) {
//开启10个线程异步调用openfeign请求,往CompletableFuture中存方法返回的数据(改)
futures.add(this.loadData(args,requestAttributes);
}
//从CompletableFuture中取数据
List<MyData> myDataList = new ArrayList<>();
for (CompletableFuture<MyData> future : futures) {
MyData myData = future.get();
if (myData == null) {
continue;
}
myDataList.add(myData);
}
return myDataList;
}
CompletableFuture<MyData> loadData(String args, RequestAttributes requestAttributes){
return CompletableFuture.supplyAsync(() -> {
try {
//将主线程的request请求头传递到子线程,防止异步时发生子线程request请求头丢失的情况(改)
RequestContextHolder.setRequestAttributes(requestAttributes);
//在异步子线程中调用openfeign请求获取数据
MyData myData = myGetDataClient.getData(args);
return myData;
} finally {
//清除子线程请求头信息(增)
RequestContextHolder.resetRequestAttributes();
}
});
}