【FAQ】为啥MultipartFile 的InputStream available会为0

news2024/9/21 8:03:05

背景

在Spring boot 文件上传案例中可能会存在获取MultipartFile InputStream.available()方法为0的情况,导致在文件上传到Minio后对象大小为0的情况

问题原因

在介绍问题原因前我们先探究下MultipartFile 是怎么实现的

这里只是剖析InputStream,所以我们直接断点getInputStream()就可以了,通过断点可以发现传进来的MultipartFile实际上是StandardMultipartFile

  1. 通过源码可以看到,它实际上是通过Part.getInputStream()获取的一个输入流,关于Part类的介绍可以在网上搜索
package org.springframework.web.multipart.support;

private static class StandardMultipartFile implements MultipartFile, Serializable {
 	private final Part part;
    private final String filename;
    
 	public StandardMultipartFile(Part part, String filename) {
     	this.part = part;
        this.filename = filename;
    }

	public InputStream getInputStream() throws IOException {
    	return this.part.getInputStream();
    }
}
  1. 通过断点可以看出StandardMultipartFile 中的part实际上是ApplicationPart,而ApplicationPart.getInputStream()又是通过FileItem.getInputStream()获取的,下面接着断点
package org.apache.catalina.core;
public class ApplicationPart implements Part {
    private final FileItem fileItem;
    private final File location;

    public ApplicationPart(FileItem fileItem, File location) {
        this.fileItem = fileItem;
        this.location = location;
    }
    
	public InputStream getInputStream() throws IOException {
        return this.fileItem.getInputStream();
    }
}
  1. FileItem.getInputStream()这里判断了内存中是否已经存在输入流了,如果内存中没有输入流,
  • 通过this.dfos.getFile().toPath()去获取一个Path对象,然后再通过Files.newInputStream去新建一个输入流
package org.apache.tomcat.util.http.fileupload.disk;

public class DiskFileItem implements FileItem {
	private byte[] cachedContent;

	private transient DeferredFileOutputStream dfos;

	public boolean isInMemory() {
        return this.cachedContent != null ? true : this.dfos.isInMemory();
    }
    
	public InputStream getInputStream() throws IOException {
		//判断是否是在内存中,没在内存,这新建输入流,内存中有则直接新建ByteArrayInputStream输入流
        if (!this.isInMemory()) {
            return Files.newInputStream(this.dfos.getFile().toPath());
        } else {
            if (this.cachedContent == null) {
                this.cachedContent = this.dfos.getData();
            }

            return new ByteArrayInputStream(this.cachedContent);
        }
    }
}
  • Files.newInputStream(this.dfos.getFile().toPath())方法通过provider(path).newInputStream(path, options)新建输入流
public final class Files {
	private static FileSystemProvider provider(Path path) {
        return path.getFileSystem().provider();
    }
	public static InputStream newInputStream(Path path, OpenOption... options) throws IOException {
        return provider(path).newInputStream(path, options);
    }
}
  • 通过源码可以看出FileSystemProvider.newInputStream()最终返回了一个ChannelInputStream输入流(重点:本文获取到的输入流就是ChannelInputStream输入流)
public abstract class FileSystemProvider {
 	public InputStream newInputStream(Path path, OpenOption... options)  throws IOException {
        if (options.length > 0) {
            for (OpenOption opt: options) {
                // All OpenOption values except for APPEND and WRITE are allowed
                if (opt == StandardOpenOption.APPEND ||
                    opt == StandardOpenOption.WRITE)
                    throw new UnsupportedOperationException("'" + opt + "' not allowed");
            }
        }
        ReadableByteChannel rbc = Files.newByteChannel(path, options);
        if (rbc instanceof FileChannelImpl) {
            ((FileChannelImpl) rbc).setUninterruptible();
        }
        return Channels.newInputStream(rbc);
    }
}
public final class Channels {
	public static InputStream newInputStream(ReadableByteChannel ch) {
	        Objects.requireNonNull(ch, "ch");
	        return new ChannelInputStream(ch);
   }
}

ChannelInputStream

通过源码可以看出,他这里继承了InputStream,并重写了available方法,而available方法中是通过position获取当前的读取位置判断文件大小的,所以如果position改变了,那么他就有可能获取为0

而类中能改变position 的只有read方法,所以如果你在调用available方法之前掉了read方法,则会出现available返回值为0的情况,

package sun.nio.ch;

public class ChannelInputStream
    extends InputStream
{

    public static int read(ReadableByteChannel ch, ByteBuffer bb,
                           boolean block)
        throws IOException
    {
        if (ch instanceof SelectableChannel) {
            SelectableChannel sc = (SelectableChannel)ch;
            synchronized (sc.blockingLock()) {
                boolean bm = sc.isBlocking();
                if (!bm)
                    throw new IllegalBlockingModeException();
                if (bm != block)
                    sc.configureBlocking(block);
                int n = ch.read(bb);
                if (bm != block)
                    sc.configureBlocking(bm);
                return n;
            }
        } else {
            return ch.read(bb);
        }
    }

    protected final ReadableByteChannel ch;
    private ByteBuffer bb = null;
    private byte[] bs = null;           // Invoker's previous array
    private byte[] b1 = null;

    public ChannelInputStream(ReadableByteChannel ch) {
        this.ch = ch;
    }

    public synchronized int read() throws IOException {
        if (b1 == null)
            b1 = new byte[1];
        int n = this.read(b1);
        if (n == 1)
            return b1[0] & 0xff;
        return -1;
    }

    public synchronized int read(byte[] bs, int off, int len)
        throws IOException
    {
        Objects.checkFromIndexSize(off, len, bs.length);
        if (len == 0)
            return 0;

        ByteBuffer bb = ((this.bs == bs)
                         ? this.bb
                         : ByteBuffer.wrap(bs));
        bb.limit(Math.min(off + len, bb.capacity()));
        bb.position(off);
        this.bb = bb;
        this.bs = bs;
        return read(bb);
    }

    protected int read(ByteBuffer bb)
        throws IOException
    {
        return ChannelInputStream.read(ch, bb, true);
    }

    public int available() throws IOException {
        // special case where the channel is to a file
        if (ch instanceof SeekableByteChannel) {
            SeekableByteChannel sbc = (SeekableByteChannel)ch;
            long rem = Math.max(0, sbc.size() - sbc.position());
            return (rem > Integer.MAX_VALUE) ? Integer.MAX_VALUE : (int)rem;
        }
        return 0;
    }

    public synchronized long skip(long n) throws IOException {
        // special case where the channel is to a file
        if (ch instanceof SeekableByteChannel) {
            SeekableByteChannel sbc = (SeekableByteChannel)ch;
            long pos = sbc.position();
            long newPos;
            if (n > 0) {
                newPos = pos + n;
                long size = sbc.size();
                if (newPos < 0 || newPos > size) {
                    newPos = size;
                }
            } else {
                newPos = Long.max(pos + n, 0);
            }
            sbc.position(newPos);
            return newPos - pos;
        }
        return super.skip(n);
    }

    public void close() throws IOException {
        ch.close();
    }

}

整体流程

在这里插入图片描述

解决办法

通过解析源码,可以知道解决解决办就是:

通过MultipartFile.getInputStream再获取一次输入流就行,因为他会在DiskFileItem 中再次创建一个新的ByteArrayInputStream输入流

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1982976.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Linux驱动入门实验班day03-另一种注册cdev的方式

问题&#xff1a;原来的函数/*major register_chrdev(0, "100ask_hello", &hello_drv);*/会将主设备号major对应的所有次设备号&#xff0c;对应的设备节点&#xff0c;总是访问到驱动程序hello_drv。 这个问题&#xff0c;会导致主设备号不够用。 解决方式&am…

c++ - unordered_set与unordered_map模拟实现

文章目录 前言一、unordered_set模拟实现二、unordered_map模拟实现 前言 1、unordered_set与unordered_map的介绍与接口使用可参考&#xff1a;unordered_set 、 unordered_map。 2、unordered_set和 unordered_map 的底层实现都是基于哈希表的。哈希表是一种通过哈希函数组织…

HarmonyOS(48) 挂载卸载事件 UI组件的添加和删除监听

UI组件的添加和删除监听 一级目录示例代码参考资料 一级目录 我们通过if条件添加组件的时候&#xff0c;是可以通过onAttach、onDetach、onAppear、onDisAppear来监听组件的添加和删除。 示例代码 // xxx.ets// xxx.ets import { promptAction } from kit.ArkUIEntry Compo…

2024华数杯数学建模A题完整论文讲解(含每一问python代码+结果+可视化图)

大家好呀&#xff0c;从发布赛题一直到现在&#xff0c;总算完成了2024 年华数杯全国大学生数学建模竞赛A题机器臂关节角路径的优化设计完整的成品论文。 本论文可以保证原创&#xff0c;保证高质量。绝不是随便引用一大堆模型和代码复制粘贴进来完全没有应用糊弄人的垃圾半成…

VBA信息获取与处理:VBA代码分类及如何利用代码自动关闭空闲文件

《VBA信息获取与处理》教程(版权10178984)是我推出第六套教程&#xff0c;目前已经是第一版修订了。这套教程定位于最高级&#xff0c;是学完初级&#xff0c;中级后的教程。这部教程给大家讲解的内容有&#xff1a;跨应用程序信息获得、随机信息的利用、电子邮件的发送、VBA互…

LC65---2164.对奇偶下标分别排序(排序)--Java版

1.题目 2.思路 &#xff08;1&#xff09;分别提取奇数下标和偶数下标的元素。 &#xff08;2&#xff09;对奇数下标的元素按非递增顺序排序&#xff0c;对偶数下标的元素按非递减顺序排序。 (3)最后将排列好的数字进行合并。 补充&#xff1a; 3.代码实现 class Solution…

PyCharm 2024.1 总结和最新变化

​ 您好&#xff0c;我是程序员小羊&#xff01; 前言 PyCharm 2024.1 是 JetBrains 最新发布的Python集成开发环境&#xff08;IDE&#xff09;&#xff0c;旨在提供更强大的功能和更好的用户体验。以下是对这个版本的总结和最新变化的介绍 智能代码建议和自动完成&#xff1a…

C语言 ——— 学习并使用 strerror 函数

目录 学习strerror函数 使用strerror函数 学习strerror函数 库函数在执行的时候&#xff0c;发生了错误&#xff0c;会将这个错误码存放在errno这个变量中&#xff0c;而errno是C语言提供的一个全局变量 而strerror函数是一个错误报告函数&#xff0c;可以将对应的错误码转…

roomformer-端到端矢量检测模型

论文&#xff1a;Connecting the Dots: Floorplan Reconstruction Using Two-Level Queries 论文地址&#xff1a;https://arxiv.org/pdf/2211.15658 code&#xff1a;https://github.com/ywyue/RoomFormer or https://github.com/woodfrog/poly-diffuse 参考&#xff1a;ht…

指针的指针作为形参实测

1. VS2019里面创建C控制台工程 2. 代码 #include <iostream>uint8_t buf[3][10] { {1,2,3},{4,5,6,7,8},{9,0} }; uint8_t len1 3,len2 5,len3 2;void f1(uint8_t **dstBuf, uint8_t *dstLen) {*dstBuf buf[0];*dstLen len1; }void f2(uint8_t** dstBuf, uint8_t*…

密码学基础-数据加密

密码学基础-对称加密与非对称加密 概述 安全通常从四个方面来定义&#xff1a; 机密性完整性合法性&#xff08;可用性&#xff0c;合法的数据才可用&#xff09;不可否认性&#xff08;发送方不可否认发送过的消息&#xff0c;接收方不可否认接收过的消息&#xff09; 对当…

低代码: 开发难点分析,核心技术架构设计

开发难点分析 1 &#xff09;怎样实现组件 核心问题&#xff1a;编辑器 和 页面其实整个就是一系列元素构成的这些元素的自然应该抽象成组件&#xff0c;这些组件的属性应该怎样设计在不同的项目中怎样做到统一的使用 2 &#xff09;跨项目使用 在不同的项目中怎样做到统一的…

最强开源文生图模型一夜易主!SD一作、Stabililty AI核心成员Robin Rombach下场创业了,一出手就是王炸。

时隔4个月&#xff0c;开源文生图模型霸主Stable Diffusion原班人马再创业&#xff01;2024年8月1日官宣&#xff1a;Black Forest Labs成立&#xff0c;公司的第一个产品FLUX.1系列模型包含专业版、开发者版、快速版三种模型&#xff0c;效果直接秒杀Midjourney、DALL-E和Stab…

解决报错:AssertionError: Torch not compiled with CUDA enabled

首先查看自己的cuda是否可用 torch.cuda.is_available()这里我的cuda是不适配torch的&#xff0c;所以需要重新安装适配的torch 查看自己的cuda版本 方法1 方法2 在cmd处输入nvidia-smi 这样可以找到的自己的CUDA版本安装符合自己版本的pytorch 进入pytorch官网https://pyt…

双指针实现删除字符串中的所有相邻重复项

class Solution:def removeDuplicates(self, s: str) -> str:res list(s)slow fast 0length len(res)while fast < length:# 如果一样直接换&#xff0c;不一样会把后面的填在slow的位置res[slow] res[fast]# 如果发现和前一个一样&#xff0c;就退一格指针if slow …

app逆向实战:某监管app2.0.5版本ROOT检测绕过

本篇博客旨在记录学习过程&#xff0c;不可用于商用等其它途径 场景 如下图&#xff0c;在我们打开APP时页面提示如此样式说明被检测到ROOT了&#xff0c;这种情况下无法进入页面请求抓包。 查壳 如果这个APP没有加固&#xff0c;那我们可以通过反编译修改检测的代码或者F…

Django与数据库

目录 创建项目app 路由子表 数据库 创建数据库 什么是ORM 定义数据库表 Django Admin 管理数据 过滤条件 代码直接生成HTML 使用模板 前后端分离架构 对资源的增删改查处理 列出客户 添加客户 临时取消 CSRF 校验 修改客户信息 删除客户 Django中ORM的处理 数据模…

QThread::wait: Thread tried to wait on itself

调用QThread的wait()方法时&#xff0c;报警&#xff1a;QThread::wait: Thread tried to wait on itself 原因&#xff1a;在自己的线程中调用线程的wait()方法。 解决方法&#xff1a;在线程外的其他线程中&#xff0c;调用线程的wait()方法。 示例代码如下&#xff1a; …

MATLAB学习之绘图篇(二维图)

目录 1.1基础图形绘制 1.1.1使用plot函数进行图形绘制 1.1.2为图像增加图例 1.1.3为图片增加标题以及坐标轴的描述 1.1.4控制坐标轴&#xff0c;边框以及网络 1.1.5在一个图像上绘制多条曲线 1.1.6在一个窗口绘制多个图像 1.1.7对图形的对象进行操作( 坐标属性&#xff…

LeetCode Hot100 二叉搜索树中第K小的元素

给定一个二叉搜索树的根节点 root &#xff0c;和一个整数 k &#xff0c;请你设计一个算法查找其中第 k 小的元素&#xff08;从 1 开始计数&#xff09;。 示例 1&#xff1a; 输入&#xff1a;root [3,1,4,null,2], k 1 输出&#xff1a;1示例 2&#xff1a; 输入&#xf…