博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
云笔记项目- 上传文件报错"java.lang.IllegalStateException: File has been moved - cannot be read again"...
阅读量:4916 次
发布时间:2019-06-11

本文共 9885 字,大约阅读时间需要 32 分钟。

在做文件上传时,当写入上传的文件到文件时,会报错“java.lang.IllegalStateException: File has been moved - cannot be read again”,网上一般说需要配置maxInMemorySize,自己测试发现,maxInMemorySize确实会影响结果,并且项目还跟写入文件到文件夹的transferTo(File dest)方法也有关系,以下是报错截图:

问题说明

通过debug和浏览器提示,浏览器端代码没有问题,问题跟服务端专门处理上传代码并写出到文件夹的部分有关系。以下为代码,其中分别使用了transferTo(),getBytes()和getInputStream()来向文件系统写入文件。

1 package Web;  2   3 import java.io.File;  4 import java.io.FileOutputStream;  5 import java.io.IOException;  6 import java.io.InputStream;  7 import java.util.HashMap;  8 import java.util.Map;  9  10 import org.springframework.stereotype.Controller; 11 import org.springframework.web.bind.annotation.RequestMapping; 12 import org.springframework.web.bind.annotation.ResponseBody; 13 import org.springframework.web.multipart.MultipartFile; 14  15 /** 16  * 上传文件的控制器 17  * @author yangchaolin 18  * 19  */ 20 @Controller 21 @RequestMapping("/file") 22 public class UploadController { 23      24     @RequestMapping("/uploadFile.do") 25     @ResponseBody 26     public Object uploadFile(MultipartFile userfile1,MultipartFile userfile2) throws IllegalStateException, IOException{ 27         /** 28          * Spring MVC中可以使用MultipartFile接受上载的文件,文件中的一切数据都可以从此对象中获取 29          * 比如可以获取文件原始名,文件类型等 30          */ 31          32         //比如获取上载文件的原始文件名,就是文件系统中的文件名 33         String filename1=userfile1.getOriginalFilename(); 34         String filename2=userfile2.getOriginalFilename(); 35         System.out.println("文件1原始文件名为:"+filename1); 36         System.out.println("文件2原始文件名为:"+filename2); 37          38         Map
map=new HashMap
(); 39 40 /** 41 * 保存上传的文件有三种方法: 42 * 1 MultipartFile接口的transferTo(File dest)方法 43 * 将文件直接保存到目标文件,适用于大文件 44 * 2 MultipartFile接口的getBytes()方法 45 * 将文件全部读取,返回byte数组,保存在内存,适用于小文件,大文件有爆内存风险 46 * 3 MultipartFile接口的getInputStream()方法,将文件读取后返回一个InputStream 47 * 获取上载文件的流,适用于大文件 48 */ 49 50 //mac中保存文件地址 /Users/yangchaolin 51 //window中保存地址 D:/yangchaolin 52 //linux中保存地址 /home/soft01/yangchaolin 53 54 //1 使用transferTo(File dest) 55 // 创建目标文件夹 56 File dir=new File("/Users/yangchaolin"); 57 boolean makeDirectoryResult=dir.mkdirs(); 58 System.out.println("文件夹路径是否建立:"+makeDirectoryResult); 59 //往文件夹放第一个文件 60 File file=new File(dir,filename1); 61 userfile1.transferTo(file); 62 63 /** 64 * transferTo方法如果不注释掉,后面执行第二种方法写入文件到硬盘会报错 65 * 报错内容:java.lang.IllegalStateException: File has been moved - cannot be read again 66 * 原因为transferTo方法底层在执行时,会检查需要写入到OutputStream的文件字节数是否超过MultipartResolver配置的大小, 67 * 默认设置为10kib,如果超过了,执行完这个方法后会从内存中删除上传的文件,后面再想读取就会报错 68 */ 69 70 //2 使用getInputStream()方法 71 File file1=new File(dir,filename1); 72 InputStream isWithoutBuff=userfile1.getInputStream(); 73 //使用FileoutputStream写出到文件 74 FileOutputStream fosWithoutbuff=new FileOutputStream(file1); 75 //InputStream一个字节一个字节的读取,将读取到的结果写入到FileOutputStream 76 int b;//读取一个byte后,以int类型显示数值,范围0~255 77 while((b=isWithoutBuff.read())!=-1) { 78 //read()方法每次只读取文件的一个byte 79 fosWithoutbuff.write(b); 80 } 81 isWithoutBuff.close(); 82 fosWithoutbuff.close(); 83 84 //同样使用InputStream读取,将读取到的结果写入到FileOutputStream,但使用了缓冲字节数组 85 File file2=new File(dir,filename2); 86 InputStream isWithBuff=userfile2.getInputStream(); 87 FileOutputStream fosWithBuff=new FileOutputStream(file2); 88 int n;//保存返回读取到的字节数, 一次8192个字节,当不够时就是实际读取到的字节数 89 byte[] buff=new byte[8*1024];//8kib的缓冲字节数组 90 while((n=isWithBuff.read(buff))!=-1) { 91 System.out.println("读取后的字节数:"+n); 92 fosWithBuff.write(buff, 0, n); 93 } 94 isWithBuff.close(); 95 fosWithBuff.close(); 96 97 //3 使用getBytes()方法 98 byte[] data=userfile2.getBytes(); 99 //写出byte数组到文件100 File file3=new File(dir,filename2);101 FileOutputStream fosWithByte=new FileOutputStream(file3);102 fosWithByte.write(data,0,data.length);103 fosWithByte.close();104 105 map.put("Result", "upload Success"); 106 107 return map;//需要导入jackson的三个核心包,否则无法正常转换成JSON108 109 }110 111 }

此外还跟解析器的属性maxInMemorySize配置也有关系,以下是解析器配置:

1 
2
14 15
16
17
18
19 20
21
22
23
24
25
26 27
28
29
30 31 32

下面简单测试下代码和配置对上传结果的影响:

(1)保留transferTo()代码,并对maxInMemorySize配置10M大小

(2)保留transferTo()代码,对maxInMemorySize不进行配置

(3)注释transferTo()代码,对maxInMemorySize不进行配置

保留transferTo()代码,并对maxInMemorySize配置10M大小

测试结果:可以上传,并且两张图片大小都25KB以上

保留transferTo()代码,对maxInMemorySize不进行配置

测试结果:服务端报错"File has been moved - cannot be read again" ,页面显示和文件查看表明没有上传成功。

注释transferTo()代码,对maxInMemorySize不进行配置

 测试结果:

可以上传,并且两张图片大小都25KB以上

问题分析

从测试结果如下:

(1)当maxInMemorySize配置足够大时,就算有transferTo()方法执行也能正常上传

(2)当maxInMemorySize不配置,当文件比较大时,有transferTo()方法执行会报错

(3)当maxInMemorySize不配置,没有transferTo()方法执行,将正常上传文件

影响报错的主要原因为transferTo()方法和maxInMemorySize两者,因此需要查看transferTo方法的源码。

源码查看

transferTo()方法是MultipartFile接口方法,需要查看其实现类方法具体实现,查看发现其实现类为CommonsMultipartFile,查看其具体实现方法,发现其需要确认isAvailable()方法返回的结果,根据其抛出报警内容,发现刚好是项目抛出的异常内容,因此需要继续查看isAvailable()方法的执行。

1     @Override 2     public void transferTo(File dest) throws IOException, IllegalStateException { 3         if (!isAvailable()) { 4             throw new IllegalStateException("File has already been moved - cannot be transferred again"); 5         } 6  7         if (dest.exists() && !dest.delete()) { 8             throw new IOException( 9                     "Destination file [" + dest.getAbsolutePath() + "] already exists and could not be deleted");10         }11 12         try {13             this.fileItem.write(dest);14             if (logger.isDebugEnabled()) {15                 String action = "transferred";16                 if (!this.fileItem.isInMemory()) {17                     action = isAvailable() ? "copied" : "moved";18                 }19                 logger.debug("Multipart file '" + getName() + "' with original filename [" +20                         getOriginalFilename() + "], stored " + getStorageDescription() + ": " +21                         action + " to [" + dest.getAbsolutePath() + "]");22             }23         }24         catch (FileUploadException ex) {25             throw new IllegalStateException(ex.getMessage());26         }27         catch (IOException ex) {28             throw ex;29         }30         catch (Exception ex) {31             logger.error("Could not transfer to file", ex);32             throw new IOException("Could not transfer to file: " + ex.getMessage());33         }34     }

isAvailable()方法的代码,发现其需要检查上传的文件是否在内存中,当只有在内存中时,才返回true,否则返回false后抛出异常,因此继续查看isInMemory()方法。

1     /** 2      * Determine whether the multipart content is still available. 3      * If a temporary file has been moved, the content is no longer available. 4      */ 5     protected boolean isAvailable() { 6         // If in memory, it's available. 7         if (this.fileItem.isInMemory()) { 8             return true; 9         }10         // Check actual existence of temporary file.11         if (this.fileItem instanceof DiskFileItem) {12             return ((DiskFileItem) this.fileItem).getStoreLocation().exists();13         }14         // Check whether current file size is different than original one.15         return (this.fileItem.getSize() == this.size);16     }

查看发现isInMemory()方法是commons-fileupload.jar包下接口FileItem中定义的,因此继续查看接口实现类,发现为DiskFileItem,并且查看实现类,发现其首先需要检查缓存文件是否存在,如果不存在调用DeferredFileOutputStream的isInMemory方法继续查询。

1     /** 2      * Provides a hint as to whether or not the file contents will be read 3      * from memory. 4      * 5      * @return true if the file contents will be read 6      *         from memory; false otherwise. 7      */ 8     public boolean isInMemory() { 9         if (cachedContent != null) {10             return true;11         }12         return dfos.isInMemory();13     }

isInMemory方法还会继续调用DeferredFileOutputStream对象dfos的isInMemory方法。

1     /** 2      * Determines whether or not the data for this output stream has been 3      * retained in memory. 4      * 5      * @return true if the data is available in memory; 6      *         false otherwise. 7      */ 8     public boolean isInMemory() 9     {10         return !isThresholdExceeded();11     }

最后发现调用了ThresholdingOutputStream的isThresholdExceeded()方法,具体代码如下,其会检查准备写出到输出流的文件大小,是否超过设定的阈值,这个阈值通过debug发现,就是我们前面配置的参数maxInMemorySize,其默认是10Kib。在本项目中,由于上传的图片都在10Kib大小以上,其都超过了阈值,方法执行返回为true,参数传入到isInMemory方法后,返回false,最终传入到最上层会返回false,从而抛出本次记录的异常。后面将maxInMemorySize设置为10M后,就算有transferTo()方法执行,因上传文件大小分别为20多Kib均为超过阈值,所以能正常上传。

1     /** 2      * Determines whether or not the configured threshold has been exceeded for 3      * this output stream. 4      * 5      * @return true if the threshold has been reached; 6      *         false otherwise. 7      */ 8     public boolean isThresholdExceeded() 9     {10         return written > threshold;11     }

再次验证

为了验证上面的结论,准备了两个文件大小在10Kib以下的文件进行文件上传测试,并且测试不配置maxInMemorySize,同时执行transferTo()方法。测试结果如下:

总结

(1)如果上传文件准备将文件写入到文件夹,抛出异常"java.lang.IllegalStateException: File has been moved - cannot be read again",很有可能跟解析器MultipartResolver的属性maxInMemorySize配置太小有关,由于其默认配置只有10Kib,当上传文件足够大,并且使用了MultipartFile的transferTo()方法写入文件到文件夹时,就会抛出异常。

(2)文件上传时,最好将maxInMemorySize属性配置更大一点。

转载于:https://www.cnblogs.com/youngchaolin/p/10702457.html

你可能感兴趣的文章
2019CSUST集训队选拔赛题解(一)
查看>>
李晓菁201771010114《面向对象程序设计(Java)》第三周学习总结
查看>>
Typedef与Struct
查看>>
Linux常用网络命令整理
查看>>
JMeter学习笔记--使用URL回写来处理用户会话
查看>>
Error creating bean with name 'documentationPluginsBootstrapper' defined in URL
查看>>
Javascript样例之文档章节滚动全版(DOM)
查看>>
C++ 面向对象
查看>>
Maven Nexus
查看>>
js 判断滚动条的滚动方向
查看>>
关于springboot启动时候报错:springboot Failed to parse configuration class [Application]
查看>>
java中Class的使用详解
查看>>
css,js文件后面加一个版本号
查看>>
webpack第一节(2)
查看>>
python之asyncio三种应用方法
查看>>
Laravel 的文件存储 - Storage
查看>>
转:[Server] 在 Windows 上安裝 PHP 5.3 開發環境
查看>>
【IE6的疯狂之二】IE6中PNG Alpha透明(全集)
查看>>
第一个Shell脚本
查看>>
C++ 小笔记
查看>>