MultipartFile大家想必不陌生,在SpringMVC的控制器方法中,我们可以通过MultipartFile自动注入上传的文件。我们从一个小案例引入,深入了解下MultipartFile
1、一个小问题
此问题来自真实案例,大家可以先想想当我们通过生产者端 /producer/produce 上传文件时,消费者端会输出什么
1.1、整体结构
我们定义了以下结构:
远程调用client
1.2、生产者端
我们简单定义了一个上传功能,如下所示
@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";
1.3、消费者端
在消费者端我们直接输出photo
@RestController
public class ConsumerController {
@PostMapping("/consumer/consume")
public String consume(@RequestBody MultipartFile photo) {
System.out.println("photo:"+photo);
return "photo";
1.4、远程调用消费者端
这里仅仅定义了远程调用接口,以提供给生产者调用
@FeignClient("service-consumer")
@Repository
public interface ConsumerFeignClient {
@PostMapping(value = "/consumer/consume",consumes = "multipart/form-data")
public String consume(@RequestBody MultipartFile photo);
1.5、问题
消费者端会输出什么?
问题的答案放在文章的末尾。
2、MultipartFile接口
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));
其中关于提供文件基础信息的方法为:
String getName();
String getOriginalFilename();
String getContentType();
byte[] getBytes() throws IOException; 或 InputStream getInputStream() throws IOException;
这些方法描述一个文件所必须的几要素,只要有这些方法就可以获得一个文件了,其他几个方法为方便我们操作的API
3、自己写一个MultipartFile子类
参考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);
4、答案与正确实践
4.1、答案
通过swagger上传文件进行测试,结果如下所示
生产者端:
消费者端:
可以看到,消费者端并没有获取到生产者提供的文件。
这是因为虽然生产者将video赋予了photo变量,但实质上只是指针的改变,photo文件中通过getName()方法获得的文件名还是 “video”,自然不会自动注入了
4.2、正确实践
在消费者端我们再提供一个正确的控制器方法,利用到了上文自己定义的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接口,其核心是提供文件信息,对其进行描述。源码并没有想象中的那么晦涩,了解其核心需求能更好地帮助我们理解作者的编写意图与思路