• 深入源码!详解MultipartFile


    MultipartFile大家想必不陌生,在SpringMVC的控制器方法中,我们可以通过MultipartFile自动注入上传的文件。我们从一个小案例引入,深入了解下MultipartFile

    1、一个小问题

    此问题来自真实案例,大家可以先想想当我们通过生产者端 /producer/produce上传文件时,消费者端会输出什么

    1.1、整体结构

    我们定义了以下结构:

    • 服务端

      • 生产者,将文件上传至生产者,经过处理后再发送到消费者
      • 消费者,消费生产者发送来的文件
    • 远程调用client

      • 提供消费者的远程调用服务

    1.2、生产者端

    我们简单定义了一个上传功能,如下所示

    1. @RestController
    2. public class ProducerController {
    3. @Autowired
    4. private ConsumerFeignClient consumerFeignClient;
    5. @PostMapping("/producer/produce")
    6. public String produce(@RequestBody MultipartFile video) {
    7. System.out.println("video:"+video);
    8. //经过一系列操作,将视频转为图片...
    9. MultipartFile photo=video;
    10. consumerFeignClient.consume(photo);
    11. return "video";
    12. }
    13. }
    14. 复制代码

    1.3、消费者端

    在消费者端我们直接输出photo

    1. @RestController
    2. public class ConsumerController {
    3. @PostMapping("/consumer/consume")
    4. public String consume(@RequestBody MultipartFile photo) {
    5. System.out.println("photo:"+photo);
    6. return "photo";
    7. }
    8. }
    9. 复制代码

    1.4、远程调用消费者端

    这里仅仅定义了远程调用接口,以提供给生产者调用

    1. @FeignClient("service-consumer")
    2. @Repository
    3. public interface ConsumerFeignClient {
    4. @PostMapping(value = "/consumer/consume",consumes = "multipart/form-data")
    5. public String consume(@RequestBody MultipartFile photo);
    6. }
    7. 复制代码

    1.5、问题

    消费者端会输出什么?

    问题的答案放在文章的末尾。

    2、MultipartFile接口

    MultipartFile其实是一个接口,其中定义提供了上传文件的各方面信息。话不多说,直接上源码。通过注释的方式解读一下各个方法的意义。

    1. public interface MultipartFile extends InputStreamSource {
    2. //获取文件的名字,这里指的是post表单里面定义的名字
    3. String getName();
    4. //获取文件的原名字,这里指的是本地文件真正的名字
    5. @Nullable
    6. String getOriginalFilename();
    7. //文件的类型
    8. @Nullable
    9. String getContentType();
    10. //文件是否为空
    11. boolean isEmpty();
    12. //文件的大小
    13. long getSize();
    14. //获取文件的byte数组
    15. byte[] getBytes() throws IOException;
    16. //以流的方式获取文件
    17. InputStream getInputStream() throws IOException;
    18. //将其转为Resource类型,可以将其视为文件资源
    19. default Resource getResource() {
    20. return new MultipartFileResource(this);
    21. }
    22. //将其转换为文件
    23. void transferTo(File var1) throws IOException, IllegalStateException;
    24. //通过提供文件路径的方式将其转换为文件
    25. default void transferTo(Path dest) throws IOException, IllegalStateException {
    26. FileCopyUtils.copy(this.getInputStream(), Files.newOutputStream(dest));
    27. }
    28. }
    29. 复制代码

    其中关于提供文件基础信息的方法为:

    • String getName();
    • String getOriginalFilename();
    • String getContentType();
    • byte[] getBytes() throws IOException; 或 InputStream getInputStream() throws IOException;

    这些方法描述一个文件所必须的几要素,只要有这些方法就可以获得一个文件了,其他几个方法为方便我们操作的API

    3、自己写一个MultipartFile子类

    参考org.springframework.mock.web包下MockMultipartFile的实现,我写出了MultipartFileDto。

    只需要根据接口中定义的规范,提供所需的要素即可

    1. public class MultipartFileDto implements MultipartFile {
    2. public MultipartFileDto() {
    3. super();
    4. }
    5. //用于储存post表单里的文件名
    6. private String name;
    7. //用于储存文件原名
    8. private String originalFilename;
    9. //用于储存文件类型
    10. private String contentType;
    11. //用于储存文件数据
    12. private byte[] content;
    13. public MultipartFileDto(String name, byte[] content) {
    14. this(name, "", null, content);
    15. }
    16. public MultipartFileDto(String name, InputStream contentStream) throws IOException {
    17. this(name, "", null, FileCopyUtils.copyToByteArray(contentStream));
    18. }
    19. public MultipartFileDto(String name, String originalFilename, String contentType, byte[] content) {
    20. this.name = name;
    21. this.originalFilename = (originalFilename != null ? originalFilename : "");
    22. this.contentType = contentType;
    23. this.content = (content != null ? content : new byte[0]);
    24. }
    25. public MultipartFileDto(String name, String originalFilename, String contentType, InputStream contentStream)
    26. throws IOException {
    27. this(name, originalFilename, contentType, FileCopyUtils.copyToByteArray(contentStream));
    28. }
    29. @Override
    30. public String getName() {
    31. return this.name;
    32. }
    33. @Override
    34. public String getOriginalFilename() {
    35. return this.originalFilename;
    36. }
    37. @Override
    38. public String getContentType() {
    39. return this.contentType;
    40. }
    41. @Override
    42. public boolean isEmpty() {
    43. return (this.content.length == 0);
    44. }
    45. @Override
    46. public long getSize() {
    47. return this.content.length;
    48. }
    49. @Override
    50. public byte[] getBytes() throws IOException {
    51. return this.content;
    52. }
    53. @Override
    54. public InputStream getInputStream() throws IOException {
    55. return new ByteArrayInputStream(this.content);
    56. }
    57. @Override
    58. public void transferTo(File dest) throws IOException, IllegalStateException {
    59. FileCopyUtils.copy(this.content, dest);
    60. }
    61. }
    62. 复制代码

    4、答案与正确实践

    4.1、答案

    通过swagger上传文件进行测试,结果如下所示

    生产者端:

    消费者端:

    可以看到,消费者端并没有获取到生产者提供的文件。

    这是因为虽然生产者将video赋予了photo变量,但实质上只是指针的改变,photo文件中通过getName()方法获得的文件名还是 “video”,自然不会自动注入了

    4.2、正确实践

    在消费者端我们再提供一个正确的控制器方法,利用到了上文自己定义的MultipartFileDto,将文件名改成了“photo”,使得消费者端可以成功注入。

    1. @PostMapping("/producer/produceRight")
    2. public String produceRight(@RequestBody MultipartFile video) throws IOException {
    3. System.out.println("video:"+video);
    4. //经过一系列操作..
    5. MultipartFile photo=
    6. new MultipartFileDto("photo","video.mp4","text/plain", video.getInputStream());
    7. consumerFeignClient.consume(photo);
    8. return "video";
    9. }
    10. 复制代码

    提供swagger进行测试,消费者端正确地获得了文件:

    5、小结

    本文通过阅读MultipartFile接口的源码,了解了其对外提供的功能,并且自己定义了可以实现其功能的子类,从而解决了远程调用时文件获取异常的问题。

    通过MultipartFile接口阅读,我们可以得到简单的结论:

    接口的定义是为了定义规范,方便使用者编写实现类与调用。而一个接口所需要向外提供的API是根据实际生产需求而定的,就如同MultipartFile接口,其核心是提供文件信息,对其进行描述。源码并没有想象中的那么晦涩,了解其核心需求能更好地帮助我们理解作者的编写意图与思路

  • 相关阅读:
    R语言dplyr中的Select函数变量列名
    cisco asa学习笔记
    linux中文字体不一致的解决方法
    弘玑Cyclone荣登2022「Cloud 100 China」榜单
    如何将gif变成视频?3个转换方法
    【教程】超简单!如何将“在VSCode中打开”添加到右键菜单中
    Flink SQl 语法(hint,with,select,分组窗口聚合,时间属性(处理,事件))
    RocketMQ(五)RocketMQ集群架构
    一文搞懂漏洞严重程度分析
    随着裁员浪潮滚滚而来,科技工作者的泡沫是否即将破灭?
  • 原文地址:https://blog.csdn.net/m0_73311735/article/details/126816822