使用Spring WebClient下载文件遇空指针异常,BufferedInputStream可正常运行
WebClient代码的问题排查
1. 变量类型写错,直接触发NPE
你这段代码最致命的错误是变量类型声明和实际返回值不匹配:Mono<byte[]> block = ...bodyToMono(byte[].class)).block()
这里block()方法是同步阻塞拿到Mono的最终结果,返回的是byte[]类型,根本不是Mono<byte[]>。这种错误会导致:
- 如果请求返回空内容,
block()会返回null,此时block变量(被错误声明成Mono类型)实际就是null,调用block.share()必然抛出NullPointerException。 - 就算请求有内容,
byte[]对象也没有share()方法,正常编译阶段就该报错,你能运行起来大概率是类型转换的问题,最终还是会炸NPE。
2. 重复调用block()纯属画蛇添足
就算修正了变量类型,block.share().block()也是完全多余的操作:
第一次block()已经同步拿到了文件的字节数组,没必要再把它当成Mono去调用share()和block()——Mono的block()只能触发一次订阅,重复调用的行为不可控,反而容易出问题。
3. 内存隐患(隐性问题)
用bodyToMono(byte[].class)会把整个文件一次性塞进内存,要是下载的文件很大(比如几百MB以上),很容易直接OOM。而BufferedInputStream是流式读取,不会一次性占满内存,这也是两种方式表现差别的隐性原因。
修正后的基础版代码
final WebClient client = WebClient.create(); // 修正变量类型,直接获取字节数组 byte[] fileBytes = client.get() .uri("urlxxx") .header("Authorization", "xxx") .retrieve() // 加上错误状态码处理,避免直接抛异常中断程序 .onStatus(HttpStatus::isError, response -> Mono.error(new RuntimeException("下载失败,状态码:" + response.statusCode())) ) .bodyToMono(byte[].class) .block(); // 写入文件,确保内容非空 Files.write(Paths.get("abc.tar.gz"), Objects.requireNonNull(fileBytes, "下载的文件内容为空"), StandardOpenOption.CREATE);
大文件友好的流式下载方案
如果是大文件,建议用流式处理,和BufferedInputStream的思路一致,避免内存溢出:
final WebClient client = WebClient.create(); client.get() .uri("urlxxx") .header("Authorization", "xxx") .retrieve() .onStatus(HttpStatus::isError, response -> Mono.error(new RuntimeException("下载失败,状态码:" + response.statusCode())) ) .bodyToFlux(DataBuffer.class) .map(dataBuffer -> { byte[] bytes = new byte[dataBuffer.readableByteCount()]; dataBuffer.read(bytes); DataBufferUtils.release(dataBuffer); return bytes; }) .collectList() .map(list -> { int totalLen = list.stream().mapToInt(arr -> arr.length).sum(); byte[] result = new byte[totalLen]; int pos = 0; for (byte[] arr : list) { System.arraycopy(arr, 0, result, pos, arr.length); pos += arr.length; } return result; }) .subscribe(fileBytes -> { try { Files.write(Paths.get("abc.tar.gz"), fileBytes, StandardOpenOption.CREATE); } catch (IOException e) { throw new RuntimeException("写入文件失败", e); } });
内容的提问来源于stack exchange,提问作者user1298426
相关产品推荐
相关产品推荐

