Spring Feign大文件上传踩坑记
引入依赖
<dependencies>
<dependency>
<groupId>io.github.openfeign.form</groupId>
<artifactId>feign-form</artifactId>
<version>3.8.0</version>
</dependency>
<dependency>
<groupId>io.github.openfeign.form</groupId>
<artifactId>feign-form-spring</artifactId>
<version>3.8.0</version>
</dependency>
</dependencies>
编写Feign Client
/**
* @author hushow
* @Descrption
* @date 2022/03/23 10:25
*/
@FeignClient(name = "${hushow.feign.service-name:ability-service}", contextId = "ability-lfile", configuration = StorageLocalFeignService.FeignUploadConfig.class)
public interface StorageLocalFeignService {
class FeignUploadConfig {
@Bean
public Encoder springEncoder(ObjectFactory<HttpMessageConverters> messageConverters) {
return new SpringFormEncoder(new SpringEncoder(messageConverters));
}
@Bean
Request.Options feignOptions() {
return new Request.Options(2000, TimeUnit.SECONDS, -1, TimeUnit.SECONDS, true);
}
}
/**
* 文件上传
* hushowly
* @param bucketName
* @param file
* @param fileName
* @param relativeUrl
* @param isPrivate
* @return
*/
@PostMapping(value = "/ability/v1/lfile/uploadFile", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
Result<StorageFileResp> uploadFile(@RequestParam(value = "bucketName", required = true) String bucketName,
@RequestPart(value = "file", required = true) MultipartFile file,
@RequestParam(value = "fileName", required = true) String fileName,
@RequestParam(value = "relativeUrl", required = false) String relativeUrl,
@RequestParam(value = "isPrivate", required = false) Boolean isPrivate);
客户端调用
StreamMultipartFile streamMultipartFile = new StreamMultipartFile(key, fileName, inputStream, fileSize, mimeTypeStr);
Result<StorageFileResp> response = storageLocalFeignService.uploadFile(bucketName, streamMultipartFile, fileName, key, isPrivate);
上传和下载时超时的坑
-
第一步,FeignUploadConfig配置超时
@Bean Request.Options feignOptions() { return new Request.Options(2000, TimeUnit.SECONDS, -1, TimeUnit.SECONDS, true); }
-
结果发现不起作用,调试源码后发现,配置文件中feign超时配置优先级高,通过单独配置上传feign解决问是:
feign: client: config: default: loggerLevel: FULL connectTimeout: 2000 readTimeout: 2000 ability-lfile: loggerLevel: FULL connectTimeout: 2000 readTimeout: -1
-
feign超时配置逻辑源码位置(FeignClientFactoryBean类)
protected void configureFeign(FeignContext context, Feign.Builder builder) { FeignClientProperties properties = this.applicationContext .getBean(FeignClientProperties.class); if (properties != null) { if (properties.isDefaultToProperties()) { # 读取类加载的时候认配置 configureUsingConfiguration(context, builder); # 如果存在使用属性文件中默认配置,则覆盖 configureUsingProperties( properties.getConfig().get(properties.getDefaultConfig()), builder); # 如果存在属性文件特定上contextId配置则覆盖配置 configureUsingProperties(properties.getConfig().get(this.contextId), builder); } else { configureUsingProperties( properties.getConfig().get(properties.getDefaultConfig()), builder); configureUsingProperties(properties.getConfig().get(this.contextId), builder); configureUsingConfiguration(context, builder); } } else { configureUsingConfiguration(context, builder); } }
尝试上传大文件后,出现OOM坑
经分析,feign暂时不支持大文件,github有人上报,至今未修复
分析源码后,发现竟读取字节数组到内存中! 本打算自己理写实现,发现feign上层根本未考虑Stream方式设计,所以暂时未打通feign上传方案,使restTemplate方案进行了代替
java.lang.OutOfMemoryError: Java heap space
at java.util.Arrays.copyOf(Arrays.java:3236)
at java.io.ByteArrayOutputStream.grow(ByteArrayOutputStream.java:118)
at java.io.ByteArrayOutputStream.ensureCapacity(ByteArrayOutputStream.java:93)
at java.io.ByteArrayOutputStream.write(ByteArrayOutputStream.java:153)
at java.io.OutputStream.write(OutputStream.java:75)
at feign.form.multipart.Output.write(Output.java:65)
at feign.form.multipart.Output.write(Output.java:53)
at feign.form.multipart.AbstractWriter.write(AbstractWriter.java:37)
at feign.form.MultipartFormContentProcessor.process(MultipartFormContentProcessor.java:87)
at feign.form.FormEncoder.encode(FormEncoder.java:105)
at feign.form.spring.SpringFormEncoder.encode(SpringFormEncoder.java:84)