在Spring Cloud 的Feign组件中并不支持文件的传输,会出现这样的错误提示:
feign.codec.EncodeException: class [Lorg.springframework.web.multipart.MultipartFile; is not a type supported by this encoder.
at feign.codec.Encoder$Default.encode(Encoder.java:90) ~[feign-core-9.5.1.jar:na]
at feign.form.FormEncoder.encode(FormEncoder.java:87) ~[feign-form-3.3.0.jar:3.3.0]
at feign.form.spring.SpringFormEncoder.encode(SpringFormEncoder.java:64) ~[feign-form-spring-3.3.0.jar:3.3.0]
但是我们可以通过使用Feign的扩展包实现这个功能。
一. 示例介绍
服务名 | 端口号 | 角色 |
---|---|---|
feign_upload_first | 8100 | feign服务提供者 |
feign_upload_second | 8101 | feign服务消费者 |
我们调用feign_upload_second的上传文件接口上传文件,feign_upload_second内部使用feign调用feign_upload_first实现文件上传。
二 、单文件上传
2.1 feign_upload_first服务提供者
文件上传的服务提供者接口比较简单,如下所示:
@SpringBootApplication
public class FeignUploadFirstApplication {
@RestController
public class UploadController {
@RequestMapping(value = "/uploadFile",method = RequestMethod.POST, consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
public String handleFileUpload(@RequestPart(value = "file") MultipartFile file) {
return file.getOriginalFilename();
}
}
public static void main(String[] args) {
SpringApplication.run(FeignUploadFirstApplication.class, args);
}
}
2.2 feign_upload_second服务消费者
- 增加扩展包依赖
io.github.openfeign.form
feign-form
3.3.0
io.github.openfeign.form
feign-form-spring
3.3.0
commons-fileupload
commons-fileupload
1.3.3
- 新增feign实现文件上传的配置类
@Configuration
public class FeignSupportConfig {
@Bean
public Encoder feignFormEncoder() {
return new SpringFormEncoder();
}
}
- feign远程调用接口
@FeignClient(name = "file",url = "http://localhost:8100",configuration = FeignSupportConfig.class)
public interface UploadService {
@RequestMapping(value = "/uploadFile", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
String handleFileUpload(@RequestPart(value = "file") MultipartFile file);
}
- 上传文件接口
@RestController
public class UploadController {
@Autowired
UploadService uploadService;
@RequestMapping(value = "/uploadFile",method = RequestMethod.POST, consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
public String handleFileUpload(@RequestPart(value = "file") MultipartFile file) {
return uploadService.handleFileUpload(file);
}
}
2.3 测试
使用postman进行测试,可以正常上传文件
三、多文件上传
既然单个文件可以上传,那么多文件应该也没问题吧,我们对上面的代码进行修改
3.1 feign_upload_first服务提供者
文件上传的服务提供者接口比较简单,如下所示:
@SpringBootApplication
public class FeignUploadFirstApplication {
@RestController
public class UploadController {
@RequestMapping(value = "/uploadFile",method = RequestMethod.POST, consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
public String handleFileUpload(@RequestPart(value = "file") MultipartFile file) {
return file.getOriginalFilename();
}
@RequestMapping(value = "/uploadFile2",method = RequestMethod.POST, consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
public String handleFileUpload(@RequestPart(value = "file") MultipartFile[] file) {
String fileName = "";
for(MultipartFile f : file){
fileName += f.getOriginalFilename()+"---";
}
return fileName;
}
}
public static void main(String[] args) {
SpringApplication.run(FeignUploadFirstApplication.class, args);
}
}
3.2 feign_upload_second服务消费者
- feign远程调用接口
@FeignClient(name = "file",url = "http://localhost:8100",configuration = FeignSupportConfig.class)
public interface UploadService {
@RequestMapping(value = "/uploadFile", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
String handleFileUpload(@RequestPart(value = "file") MultipartFile file);
@RequestMapping(value = "/uploadFile2", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
String handleFileUpload(@RequestPart(value = "file") MultipartFile[] file);
}
- 上传文件接口
@RestController
public class UploadController {
@Autowired
UploadService uploadService;
@RequestMapping(value = "/uploadFile",method = RequestMethod.POST, consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
public String handleFileUpload(@RequestPart(value = "file") MultipartFile file) {
return uploadService.handleFileUpload(file);
}
@RequestMapping(value = "/uploadFile2",method = RequestMethod.POST, consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
public String handleFileUpload2(@RequestPart(value = "file") MultipartFile[] file) {
return uploadService.handleFileUpload(file);
}
}
3.3 测试
经过测试发现,无法上传多个文件。经过检查,发现源码里底层是有对MultipartFile[]类型的支持的,源码中有个类叫SpringManyMultipartFilesWriter,是专门针对文件数组类型进行操作的,但是配置到项目里的SpringFormEncoder类里却没有对文件数组类型的判断,以致不能支持文件数组的上传
- SpringManyMultipartFilesWriter源码
public class SpringManyMultipartFilesWriter extends AbstractWriter {
private final SpringSingleMultipartFileWriter fileWriter = new SpringSingleMultipartFileWriter();
public SpringManyMultipartFilesWriter() {
}
public void write(Output output, String boundary, String key, Object value) throws Exception {
if (value instanceof MultipartFile[]) {
MultipartFile[] files = (MultipartFile[])((MultipartFile[])value);
MultipartFile[] var6 = files;
int var7 = files.length;
for(int var8 = 0; var8 < var7; ++var8) {
MultipartFile file = var6[var8];
this.fileWriter.write(output, boundary, key, file);
}
} else if (value instanceof Iterable) {
Iterable> iterable = (Iterable)value;
Iterator var11 = iterable.iterator();
while(var11.hasNext()) {
Object file = var11.next();
this.fileWriter.write(output, boundary, key, file);
}
}
}
public boolean isApplicable(Object value) {
if (value == null) {
return false;
} else if (value instanceof MultipartFile[]) {
return true;
} else {
if (value instanceof Iterable) {
Iterable> iterable = (Iterable)value;
Iterator> iterator = iterable.iterator();
if (iterator.hasNext() && iterator.next() instanceof MultipartFile) {
return true;
}
}
return false;
}
}
}
- SpringFormEncoder源码
public class SpringFormEncoder extends FormEncoder {
public SpringFormEncoder() {
this(new Default());
}
public SpringFormEncoder(Encoder delegate) {
super(delegate);
MultipartFormContentProcessor processor = (MultipartFormContentProcessor)this.getContentProcessor(ContentType.MULTIPART);
processor.addWriter(new SpringSingleMultipartFileWriter());
processor.addWriter(new SpringManyMultipartFilesWriter());
}
public void encode(Object object, Type bodyType, RequestTemplate template) throws EncodeException {
if (!bodyType.equals(MultipartFile.class)) {
super.encode(object, bodyType, template);
} else {
MultipartFile file = (MultipartFile)object;
Map data = Collections.singletonMap(file.getName(), object);
super.encode(data, MAP_STRING_WILDCARD, template);
}
}
}
从上面SpringFormEncoder的源码上可以看到SpringFormEncoder类构造时把SpringManyMultipartFilesWriter实例添加到了处理器列表里了,但是在encode方法里又只判断了MultipartFile类型,没有判断数组类型,底层有对数组的支持但上层却缺少了相应判断。那么我们可以自己去扩展FormEncoder,仿照SpringFormEncoder源码,只修改encode方法。
3.3 扩展FormEncoder支持多文件上传
- 扩展FormEncoder,命名为FeignSpringFormEncoder
public class FeignSpringFormEncoder extends FormEncoder {
/**
* Constructor with the default Feign's encoder as a delegate.
*/
public FeignSpringFormEncoder() {
this(new Default());
}
/**
* Constructor with specified delegate encoder.
*
* @param delegate delegate encoder, if this encoder couldn't encode object.
*/
public FeignSpringFormEncoder(Encoder delegate) {
super(delegate);
MultipartFormContentProcessor processor = (MultipartFormContentProcessor) getContentProcessor(ContentType.MULTIPART);
processor.addWriter(new SpringSingleMultipartFileWriter());
processor.addWriter(new SpringManyMultipartFilesWriter());
}
@Override
public void encode(Object object, Type bodyType, RequestTemplate template) throws EncodeException {
if (bodyType.equals(MultipartFile.class)) {
MultipartFile file = (MultipartFile) object;
Map data = Collections.singletonMap(file.getName(), object);
super.encode(data, MAP_STRING_WILDCARD, template);
return;
} else if (bodyType.equals(MultipartFile[].class)) {
MultipartFile[] file = (MultipartFile[]) object;
if(file != null) {
Map data = Collections.singletonMap(file.length == 0 ? "" : file[0].getName(), object);
super.encode(data, MAP_STRING_WILDCARD, template);
return;
}
}
super.encode(object, bodyType, template);
}
}
- 注册配置类
@Configuration
public class FeignSupportConfig {
@Bean
public Encoder feignFormEncoder() {
return new FeignSpringFormEncoder();
}
}
经过测试可以上传多个文件。