MultipartFile大家想必不陌生,在SpringMVC的控制器方法中,我们可以通过MultipartFile自动注入上传的文件。我们从一个小案例引入,深入了解下MultipartFile
此问题来自真实案例,大家可以先想想当我们通过生产者端 /producer/produce上传文件时,消费者端会输出什么

我们定义了以下结构:
服务端
远程调用client
我们简单定义了一个上传功能,如下所示
- @RestController
- public class ProducerController {
- @Autowired
- private ConsumerFeignClient consumerFeignClient;
- @PostMapping("/producer/produce")
- public String produce(@RequestBody MultipartFile video) {
- System.out.println("video:"+video);
- //经过一系列操作,将视频转为图片...
- MultipartFile photo=video;
- consumerFeignClient.consume(photo);
- return "video";
- }
- }
- 复制代码
在消费者端我们直接输出photo
- @RestController
- public class ConsumerController {
- @PostMapping("/consumer/consume")
- public String consume(@RequestBody MultipartFile photo) {
- System.out.println("photo:"+photo);
- return "photo";
- }
- }
- 复制代码
这里仅仅定义了远程调用接口,以提供给生产者调用
- @FeignClient("service-consumer")
- @Repository
- public interface ConsumerFeignClient {
- @PostMapping(value = "/consumer/consume",consumes = "multipart/form-data")
- public String consume(@RequestBody MultipartFile photo);
- }
- 复制代码
消费者端会输出什么?
问题的答案放在文章的末尾。
MultipartFile其实是一个接口,其中定义提供了上传文件的各方面信息。话不多说,直接上源码。通过注释的方式解读一下各个方法的意义。
- public interface MultipartFile extends InputStreamSource {
- //获取文件的名字,这里指的是post表单里面定义的名字
- String getName();
- //获取文件的原名字,这里指的是本地文件真正的名字
- @Nullable
- String getOriginalFilename();
- //文件的类型
- @Nullable
- String getContentType();
- //文件是否为空
- boolean isEmpty();
- //文件的大小
- long getSize();
- //获取文件的byte数组
- byte[] getBytes() throws IOException;
- //以流的方式获取文件
- InputStream getInputStream() throws IOException;
- //将其转为Resource类型,可以将其视为文件资源
- default Resource getResource() {
- return new MultipartFileResource(this);
- }
- //将其转换为文件
- void transferTo(File var1) throws IOException, IllegalStateException;
- //通过提供文件路径的方式将其转换为文件
- default void transferTo(Path dest) throws IOException, IllegalStateException {
- FileCopyUtils.copy(this.getInputStream(), Files.newOutputStream(dest));
- }
- }
- 复制代码
其中关于提供文件基础信息的方法为:
这些方法描述一个文件所必须的几要素,只要有这些方法就可以获得一个文件了,其他几个方法为方便我们操作的API
参考org.springframework.mock.web包下MockMultipartFile的实现,我写出了MultipartFileDto。
只需要根据接口中定义的规范,提供所需的要素即可
- public class MultipartFileDto implements MultipartFile {
- public MultipartFileDto() {
- super();
- }
- //用于储存post表单里的文件名
- private String name;
- //用于储存文件原名
- private String originalFilename;
- //用于储存文件类型
- private String contentType;
- //用于储存文件数据
- private byte[] content;
-
- public MultipartFileDto(String name, byte[] content) {
- this(name, "", null, content);
- }
-
- public MultipartFileDto(String name, InputStream contentStream) throws IOException {
- this(name, "", null, FileCopyUtils.copyToByteArray(contentStream));
- }
-
- public MultipartFileDto(String name, String originalFilename, String contentType, byte[] content) {
- this.name = name;
- this.originalFilename = (originalFilename != null ? originalFilename : "");
- this.contentType = contentType;
- this.content = (content != null ? content : new byte[0]);
- }
-
- public MultipartFileDto(String name, String originalFilename, String contentType, InputStream contentStream)
- throws IOException {
-
- this(name, originalFilename, contentType, FileCopyUtils.copyToByteArray(contentStream));
- }
-
- @Override
- public String getName() {
- return this.name;
- }
-
- @Override
- public String getOriginalFilename() {
- return this.originalFilename;
- }
-
- @Override
- public String getContentType() {
- return this.contentType;
- }
-
- @Override
- public boolean isEmpty() {
- return (this.content.length == 0);
- }
-
- @Override
- public long getSize() {
- return this.content.length;
- }
-
- @Override
- public byte[] getBytes() throws IOException {
- return this.content;
- }
-
- @Override
- public InputStream getInputStream() throws IOException {
- return new ByteArrayInputStream(this.content);
- }
-
- @Override
- public void transferTo(File dest) throws IOException, IllegalStateException {
- FileCopyUtils.copy(this.content, dest);
- }
- }
- 复制代码
通过swagger上传文件进行测试,结果如下所示
生产者端:

消费者端:

可以看到,消费者端并没有获取到生产者提供的文件。
这是因为虽然生产者将video赋予了photo变量,但实质上只是指针的改变,photo文件中通过getName()方法获得的文件名还是 “video”,自然不会自动注入了
在消费者端我们再提供一个正确的控制器方法,利用到了上文自己定义的MultipartFileDto,将文件名改成了“photo”,使得消费者端可以成功注入。
- @PostMapping("/producer/produceRight")
- public String produceRight(@RequestBody MultipartFile video) throws IOException {
- System.out.println("video:"+video);
- //经过一系列操作..
- MultipartFile photo=
- new MultipartFileDto("photo","video.mp4","text/plain", video.getInputStream());
- consumerFeignClient.consume(photo);
- return "video";
- }
- 复制代码
提供swagger进行测试,消费者端正确地获得了文件:

本文通过阅读MultipartFile接口的源码,了解了其对外提供的功能,并且自己定义了可以实现其功能的子类,从而解决了远程调用时文件获取异常的问题。
通过MultipartFile接口阅读,我们可以得到简单的结论:
接口的定义是为了定义规范,方便使用者编写实现类与调用。而一个接口所需要向外提供的API是根据实际生产需求而定的,就如同MultipartFile接口,其核心是提供文件信息,对其进行描述。源码并没有想象中的那么晦涩,了解其核心需求能更好地帮助我们理解作者的编写意图与思路