プロジェクトのシナリオ:
最近行っているJavaバックエンドプロジェクトでは、まず他のサーバーから添付ファイルをダウンロードし、それをこのプロジェクトのMinIOサーバーにアップロードし、取得した添付ファイル情報を保存してからWebClientで実装する必要があります。
1. 基本的な紹介
1. Webクライアントとは
Spring 5 から、リアクティブ応答プログラミングが Spring に完全に導入されました。WebClient は、Spring WebFlux モジュールによって提供されるリアクティブ プログラミングに基づく、HTTP リクエスト用のノンブロッキング クライアント ツールです。
WebClient のリクエスト モードは非同期でノンブロッキングであるため、少数の固定スレッドで高度な同時 HTTP リクエストを処理できます。したがって、Spring 5 からは、HTTP サービス間の通信に以前の RestTemplate を WebClient で置き換えることを検討できます。
2. WebClient の利点
(1) WebClient は RestTemplate と比較して以下の利点があります。
ノンブロッキング、リアクティブで、より高い同時実行性とより少ないハードウェア リソースをサポートします。
Java 8 ラムダを利用する機能的な API を提供します。
同期シナリオと非同期シナリオの両方がサポートされています。
サーバーからの上りまたは下りのストリーミングをサポートします。
(2) RestTemplate はノンブロッキング アプリケーションでの使用には適していないため、Spring WebFlux アプリケーションでは常に WebClient を使用する必要があります。WebClient は、ほとんどの同時実行性の高いシナリオや、相互依存する一連のリモート呼び出しを構成する場合にも Spring MVC の最初の選択肢となるはずです。
WebClient を使用できるように、pom.xml ファイルを編集して Spring WebFlux 依存関係を追加します。
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webflux</artifactId>
</dependency>
<dependency>
<groupId>io.projectreactor.netty</groupId>
<artifactId>reactor-netty-http</artifactId>
</dependency>
2. WebClient インスタンスを作成する
Builder を使用して作成する (推奨)
次に、 builder() を使用して WebClient.Builder を返し、 build を呼び出して WebClient オブジェクトを返します。そして、オブジェクトを使用してネットワーク インターフェイスを要求し、最後に結果を文字列の形式で出力します。
注: 返される型は WebClient ではなく WebClient.Builder であるため、返された WebClient.Builder を通じていくつかの構成パラメータ (baseUrl、ヘッダー、Cookie など) を設定し、build を呼び出して WebClient オブジェクトを返すことができます。
WebClient webClient = WebClient.builder()
.baseUrl("http://better.blog.csdn.net")
.defaultHeader(HttpHeaders.USER_AGENT,"Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko)")
.defaultCookie("ACCESS_TOKEN", "test_token")
.build();
Mono<String> mono = webClient
.get() // GET 请求
.uri("/posts/1") // 请求路径
.retrieve() // 获取响应体
.bodyToMono(String.class); //响应数据类型转换
System.out.println(mono.block());
ファイルのダウンロードとファイルのアップロードの詳細な例
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import org.apache.commons.io.FileUtils;
import org.springframework.core.io.FileSystemResource;
import org.springframework.core.io.Resource;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.reactive.function.BodyInserters;
import org.springframework.web.reactive.function.client.ClientResponse;
import org.springframework.web.reactive.function.client.WebClient;
import java.io.File;
import java.io.IOException;
import java.net.URLDecoder;
import java.time.Instant;
import java.time.temporal.ChronoUnit;
import java.util.Map;
/**
* 文件 工具类
* 工具类编写规范
* 工具类定义为public final class 工具类名称;
* 工具类名称的命名应为Utils结尾,例如 DateUtils;
* 工具类的构造方法最好为private,及这个类无法实例化,无法new出一个工具类;
* 工具类的方法应为static修饰的,表示静态方法,可直接通过类名.方法名的格式去访问;
* 工具类应使用单例模式,不要使用new,重复创建对象,浪费系统资源
*
* @author QC 班长
* @version V1.0
* @since 2023/7/5 14:37
*/
public final class FileUtil {
private static FileUtil fileUtil = null;
static {
// 类加载创建时只会创建一个对象
if (fileUtil == null) {
fileUtil = new FileUtil();
}
}
private FileUtil() {
}
private static final int TEN = 10;
private static final int HUNDRED = 100;
private static final int ONE_ZERO_TWO_FOUR = 1024;
/**
* 调用接口的-认证秘钥
*/
private static final String AUTHORIZATION_VALUE = "xxx";
/**
* 下载文件
* @param dowUrl 完整下载地址
* @return 本地文件路径
* @throws IOException 异常
*/
public static String downloadFile(String dowUrl) throws IOException {
// 记录下开始下载时的时间
Instant now = Instant.now();
// 创建 WebClient 对象
WebClient webClient = WebClient.builder()
.codecs(obj -> obj.defaultCodecs().maxInMemorySize(HUNDRED * ONE_ZERO_TWO_FOUR * ONE_ZERO_TWO_FOUR))
.build();
//发送请求
ClientResponse response = webClient
.get() // GET 请求
.uri(dowUrl) // 请求路径
.accept(MediaType.APPLICATION_OCTET_STREAM)
.exchange()
.block(); // 获取响应体
String fileName = "";
// 从header中获取原始文件名
if (response != null) {
HttpHeaders httpHeaders = response.headers().asHttpHeaders();
String content = httpHeaders.getFirst(HttpHeaders.CONTENT_DISPOSITION);
if (content != null) {
fileName = content.substring(content.lastIndexOf("filename=") + TEN, content.length() - 1);
}
//解码已编码的url字符串,将十六进制转换成中文。
fileName = System.getProperty("user.dir") + File.separator + URLDecoder.decode(fileName, "UTF-8");
File out = new File(fileName);
// 将文件保存(文件名不变)
Resource resource = response.bodyToMono(Resource.class).block();
if (resource != null) {
FileUtils.copyInputStreamToFile(resource.getInputStream(), out);
}
}
System.out.println("文件下载完成,耗时:" + ChronoUnit.SECONDS.between(now, Instant.now()) + " 秒");
return fileName;
}
/**
* 上传文件
* @param filePath 本地文件路径
* @return 文件信息
* @throws IOException 异常
*/
public static String uploadFile(String filePath) throws IOException {
// 记录下开始下载时的时间
Instant now = Instant.now();
FileSystemResource upload = new FileSystemResource(new File(filePath));
// 封装请求参数
MultiValueMap<String, Object> param = new LinkedMultiValueMap<>();
param.add("file", upload);
param.add("formCode", "form_5964");
param.add("bucketName", "education");
WebClient webClient = WebClient.builder().build();
String response = webClient
.post() // POST 请求
.uri("http://10.1.1.204/gw/form-api/file/upload") // 请求路径
.header(HttpHeaders.AUTHORIZATION, AUTHORIZATION_VALUE)
.contentType(MediaType.APPLICATION_FORM_URLENCODED)
.body(BodyInserters.fromMultipartData(param))
.retrieve() // 获取响应体
.bodyToMono(String.class).block(); //响应数据类型转换
System.out.println("文件下载完成,耗时:" + ChronoUnit.SECONDS.between(now, Instant.now()) + " 秒");
// 将 JSON 响应转换为 Map
Map result = JSON.parseObject(response, Map.class);
if (result != null) {
//获取文件信息并返回
JSONObject jsonObject = (JSONObject) result.get("data");
return jsonObject.getString("uid");
} else {
return "";
}
}
public static void main(String[] args) throws IOException {
String fileName = downloadFile("http://ibeastidea.com/file/downFile/a86727dfe5f449f29ae262d50aad98");
System.out.println(fileName);
String uid = uploadFile(fileName);
}
}
問題の説明
ダウンロード中に報告されるエラーは次のとおりです。
org.springframework.core.io.buffer.DataBufferLimitException:
Exceeded limit on max bytes to buffer : 262144
at org.springframework.core.io.buffer.LimitedDataBufferList.raiseLimitException(LimitedDataBufferList.java:98)
Suppressed: reactor.core.publisher.FluxOnAssembly$OnAssemblyException:
原因分析:
DataBufferLimitException: バッファーへの最大バイト数の制限を超えました:262144。アップロードできるのは 256KB 未満のファイルのみであることがわかりましたが、現在ファイルは 10M 以上です。応答データを受信するためのバッファーが十分に大きくないため、制限は 256k です。(AbstractDataBufferDecoder クラスにはハードコーディングされた制限があります)
アップロードできるのは 256KB 未満のファイルだけですが、今では気軽に 10M 以上のファイルもアップロードできるようになりました。
解決:
codecsメソッドを使用して、 WebClient.build 中にmaxInMemorySize 値を変更できます。
private static final int HUNDRED = 100;
private static final int ONE_ZERO_TWO_FOUR = 1024;
// 创建 WebClient 对象
WebClient webClient = WebClient.builder()
//修改maxInMemorySize的缓存值,默认是256k。修改为100MB
.codecs(obj -> obj.defaultCodecs().maxInMemorySize(HUNDRED * ONE_ZERO_TWO_FOUR * ONE_ZERO_TWO_FOUR))
.build();
参考文献
1. SpringBoot - ネットワークリクエストクライアントWebClient使い方 詳細解説5(ファイルアップロード)
2. spring-boot – Spring BootがWebClient経由であるAPIから別のAPIにファイルを渡したりアップロードしたりする - コードログ
3.春 - レスポンシブ Web クライアントについて集中的に語る その 4 - ファイルのアップロードとダウンロード - 個人的な記事 - SegmentFault 思否
4. 【WEB連載】WebClientファイルアップロード | 灰色のブログ
6. WebFlux_webfux_Cheat Coke ブログ - CSDN ブログ
7. WebFlux フレームワーク webClient リクエスト データ エラー: バッファーの最大バイト数の制限を超えました: 262144_Reality、あまりにも残酷なブログ-CSDN ブログ